mirror of
https://github.com/fafhrd91/actix-web
synced 2025-02-22 12:13:16 +01:00
Add tests
This commit is contained in:
parent
5bab156700
commit
a2e3b62926
@ -3,7 +3,7 @@
|
|||||||
//! ## Crate Features
|
//! ## Crate Features
|
||||||
//!
|
//!
|
||||||
//! | Feature | Functionality |
|
//! | Feature | Functionality |
|
||||||
//! | ------------------- | ------------------------------------------- |
|
//! | --------------------- | ------------------------------------------- |
|
||||||
//! | `http2` | HTTP/2 support via [h2]. |
|
//! | `http2` | HTTP/2 support via [h2]. |
|
||||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||||
//! | `rustls-0_20` | TLS support via rustls 0.20. |
|
//! | `rustls-0_20` | TLS support via rustls 0.20. |
|
||||||
@ -13,6 +13,7 @@
|
|||||||
//! | `compress-brotli` | Payload compression support: Brotli. |
|
//! | `compress-brotli` | Payload compression support: Brotli. |
|
||||||
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
|
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
|
||||||
//! | `compress-zstd` | Payload compression support: Zstd. |
|
//! | `compress-zstd` | Payload compression support: Zstd. |
|
||||||
|
//! | `compress-ws-deflate` | WebSocket DEFLATE compression support. |
|
||||||
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
|
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
|
||||||
//!
|
//!
|
||||||
//! [h2]: https://crates.io/crates/h2
|
//! [h2]: https://crates.io/crates/h2
|
||||||
|
@ -6,7 +6,7 @@ use tracing::error;
|
|||||||
|
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
use super::deflate::{
|
use super::deflate::{
|
||||||
DeflateCompressionContext, DeflateContext, DeflateDecompressionContext, RSV_BIT_DEFLATE_FLAG,
|
DeflateCompressionContext, DeflateDecompressionContext, RSV_BIT_DEFLATE_FLAG,
|
||||||
};
|
};
|
||||||
use super::{
|
use super::{
|
||||||
frame::Parser,
|
frame::Parser,
|
||||||
@ -100,6 +100,8 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create new WebSocket frames encoder with `permessage-deflate` extension support.
|
/// Create new WebSocket frames encoder with `permessage-deflate` extension support.
|
||||||
|
/// Compression context can be made from
|
||||||
|
/// [`DeflateSessionParameters::create_context`](super::DeflateSessionParameters::create_context).
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
pub fn new_deflate(compress: DeflateCompressionContext) -> Encoder {
|
pub fn new_deflate(compress: DeflateCompressionContext) -> Encoder {
|
||||||
Encoder {
|
Encoder {
|
||||||
@ -109,7 +111,11 @@ impl Encoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_client_mode(mut self) -> Self {
|
/// Set encoder to client mode.
|
||||||
|
///
|
||||||
|
/// By default encoder works in server mode.
|
||||||
|
#[must_use = "This returns the a new Encoder, without modifying the original."]
|
||||||
|
pub fn client_mode(mut self) -> Self {
|
||||||
self.flags = Flags::empty();
|
self.flags = Flags::empty();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -305,6 +311,8 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create new WebSocket frames decoder with `permessage-deflate` extension support.
|
/// Create new WebSocket frames decoder with `permessage-deflate` extension support.
|
||||||
|
/// Decompression context can be made from
|
||||||
|
/// [`DeflateSessionParameters::create_context`](super::DeflateSessionParameters::create_context).
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
pub fn new_deflate(decompress: DeflateDecompressionContext) -> Decoder {
|
pub fn new_deflate(decompress: DeflateDecompressionContext) -> Decoder {
|
||||||
Decoder {
|
Decoder {
|
||||||
@ -315,7 +323,20 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_client_mode(mut self) -> Self {
|
/// Set max frame size.
|
||||||
|
///
|
||||||
|
/// By default max size is set to 64KiB.
|
||||||
|
#[must_use = "This returns the a new Decoder, without modifying the original."]
|
||||||
|
pub fn max_size(mut self, size: usize) -> Self {
|
||||||
|
self.max_size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set decoder to client mode.
|
||||||
|
///
|
||||||
|
/// By default decoder works in server mode.
|
||||||
|
#[must_use = "This returns the a new Decoder, without modifying the original."]
|
||||||
|
pub fn client_mode(mut self) -> Self {
|
||||||
self.flags = Flags::empty();
|
self.flags = Flags::empty();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -333,11 +354,6 @@ impl Decoder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_max_size(mut self, size: usize) -> Self {
|
|
||||||
self.max_size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
fn process_payload(
|
fn process_payload(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -461,10 +477,12 @@ impl codec::Decoder for Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// WebSocket protocol codec.
|
/// WebSocket protocol codec.
|
||||||
|
/// This is essentially a combination of [`Encoder`] and [`Decoder`] and
|
||||||
|
/// actual conversion behaviors are defined in both structs respectively.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
/// Cloning [`Codec`] creates a new codec with existing configurations
|
/// Cloning [`Codec`] creates a new codec with existing configurations
|
||||||
/// and will not preserve the current context.
|
/// and will not preserve the context information.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Codec {
|
pub struct Codec {
|
||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
@ -510,13 +528,13 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create new WebSocket frames codec with DEFLATE compression.
|
/// Create new WebSocket frames codec with DEFLATE compression.
|
||||||
|
/// Both compression and decompression contexts can be made from
|
||||||
|
/// [`DeflateSessionParameters::create_context`](super::DeflateSessionParameters::create_context).
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
pub fn new_deflate(context: DeflateContext) -> Codec {
|
pub fn new_deflate(
|
||||||
let DeflateContext {
|
compress: DeflateCompressionContext,
|
||||||
compress,
|
decompress: DeflateDecompressionContext,
|
||||||
decompress,
|
) -> Codec {
|
||||||
} = context;
|
|
||||||
|
|
||||||
Codec {
|
Codec {
|
||||||
encoder: Encoder::new_deflate(compress),
|
encoder: Encoder::new_deflate(compress),
|
||||||
decoder: Decoder::new_deflate(decompress),
|
decoder: Decoder::new_deflate(decompress),
|
||||||
@ -532,13 +550,13 @@ impl Codec {
|
|||||||
|
|
||||||
Codec {
|
Codec {
|
||||||
encoder,
|
encoder,
|
||||||
decoder: decoder.set_max_size(size),
|
decoder: decoder.max_size(size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set decoder to client mode.
|
/// Set codec to client mode.
|
||||||
///
|
///
|
||||||
/// By default decoder works in server mode.
|
/// By default codec works in server mode.
|
||||||
#[must_use = "This returns the a new Codec, without modifying the original."]
|
#[must_use = "This returns the a new Codec, without modifying the original."]
|
||||||
pub fn client_mode(self) -> Self {
|
pub fn client_mode(self) -> Self {
|
||||||
let Self {
|
let Self {
|
||||||
@ -546,8 +564,8 @@ impl Codec {
|
|||||||
mut decoder,
|
mut decoder,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
encoder = encoder.set_client_mode();
|
encoder = encoder.client_mode();
|
||||||
decoder = decoder.set_client_mode();
|
decoder = decoder.client_mode();
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
{
|
{
|
||||||
if let Some(decoder) = &decoder.deflate_decompress {
|
if let Some(decoder) = &decoder.deflate_decompress {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
//! WebSocket permessage-deflate compression implementation.
|
//! WebSocket permessage-deflate compression implementation.
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
|
||||||
@ -13,7 +11,7 @@ use crate::header::{HeaderName, HeaderValue, TryIntoHeaderPair, SEC_WEBSOCKET_EX
|
|||||||
// NOTE: according to [RFC 7692 §7.1.2.1] window bit size should be within 8..=15
|
// NOTE: according to [RFC 7692 §7.1.2.1] window bit size should be within 8..=15
|
||||||
// but we have to limit the range to 9..=15 because [flate2] only supports window bit within 9..=15.
|
// but we have to limit the range to 9..=15 because [flate2] only supports window bit within 9..=15.
|
||||||
//
|
//
|
||||||
// [RFC 6792]: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1.2.1
|
// [RFC 6792 §7.1.2.1]: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1.2.1
|
||||||
// [flate2]: https://docs.rs/flate2/latest/flate2/struct.Compress.html#method.new_with_window_bits
|
// [flate2]: https://docs.rs/flate2/latest/flate2/struct.Compress.html#method.new_with_window_bits
|
||||||
const MAX_WINDOW_BITS_RANGE: std::ops::RangeInclusive<u8> = 9..=15;
|
const MAX_WINDOW_BITS_RANGE: std::ops::RangeInclusive<u8> = 9..=15;
|
||||||
const DEFAULT_WINDOW_BITS: u8 = 15;
|
const DEFAULT_WINDOW_BITS: u8 = 15;
|
||||||
@ -64,7 +62,7 @@ impl std::fmt::Display for DeflateHandshakeError {
|
|||||||
impl std::error::Error for DeflateHandshakeError {}
|
impl std::error::Error for DeflateHandshakeError {}
|
||||||
|
|
||||||
/// Maximum size of client's DEFLATE sliding window.
|
/// Maximum size of client's DEFLATE sliding window.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ClientMaxWindowBits {
|
pub enum ClientMaxWindowBits {
|
||||||
/// Unspecified. Indicates server should decide its size.
|
/// Unspecified. Indicates server should decide its size.
|
||||||
NotSpecified,
|
NotSpecified,
|
||||||
@ -76,7 +74,7 @@ pub enum ClientMaxWindowBits {
|
|||||||
/// At client side, it can be used to pass desired configuration to server.
|
/// At client side, it can be used to pass desired configuration to server.
|
||||||
/// At server side, negotiated parameter will be sent to client with this.
|
/// At server side, negotiated parameter will be sent to client with this.
|
||||||
/// This can be represented in HTTP header form as it implements [`TryIntoHeaderPair`] trait.
|
/// This can be represented in HTTP header form as it implements [`TryIntoHeaderPair`] trait.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
pub struct DeflateSessionParameters {
|
pub struct DeflateSessionParameters {
|
||||||
/// Disallow server from take over context.
|
/// Disallow server from take over context.
|
||||||
pub server_no_context_takeover: bool,
|
pub server_no_context_takeover: bool,
|
||||||
@ -133,7 +131,9 @@ impl DeflateSessionParameters {
|
|||||||
let mut unknown_parameters = vec![];
|
let mut unknown_parameters = vec![];
|
||||||
|
|
||||||
for fragment in extension_frags {
|
for fragment in extension_frags {
|
||||||
if fragment == "client_max_window_bits" {
|
if fragment.is_empty() {
|
||||||
|
continue;
|
||||||
|
} else if fragment == "client_max_window_bits" {
|
||||||
if client_max_window_bits.is_some() {
|
if client_max_window_bits.is_some() {
|
||||||
return Err(DeflateHandshakeError::DuplicateParameter(
|
return Err(DeflateHandshakeError::DuplicateParameter(
|
||||||
"client_max_window_bits",
|
"client_max_window_bits",
|
||||||
@ -197,6 +197,9 @@ impl DeflateSessionParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse desired parameters from `Sec-WebSocket-Extensions` header.
|
||||||
|
/// The result may contain multiple values as it's possible to pass multiple parameters
|
||||||
|
/// separated with comma.
|
||||||
pub fn from_extension_header(header_value: &str) -> Vec<Result<Self, DeflateHandshakeError>> {
|
pub fn from_extension_header(header_value: &str) -> Vec<Result<Self, DeflateHandshakeError>> {
|
||||||
let mut results = vec![];
|
let mut results = vec![];
|
||||||
for extension in header_value.split(',').map(str::trim) {
|
for extension in header_value.split(',').map(str::trim) {
|
||||||
@ -209,11 +212,12 @@ impl DeflateSessionParameters {
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create compression and decompression context based on the parameter.
|
||||||
pub fn create_context(
|
pub fn create_context(
|
||||||
&self,
|
&self,
|
||||||
compression_level: Option<DeflateCompressionLevel>,
|
compression_level: Option<DeflateCompressionLevel>,
|
||||||
is_client_mode: bool,
|
is_client_mode: bool,
|
||||||
) -> DeflateContext {
|
) -> (DeflateCompressionContext, DeflateDecompressionContext) {
|
||||||
let client_max_window_bits =
|
let client_max_window_bits =
|
||||||
if let Some(ClientMaxWindowBits::Specified(value)) = self.client_max_window_bits {
|
if let Some(ClientMaxWindowBits::Specified(value)) = self.client_max_window_bits {
|
||||||
value
|
value
|
||||||
@ -234,33 +238,76 @@ impl DeflateSessionParameters {
|
|||||||
(self.server_no_context_takeover, server_max_window_bits)
|
(self.server_no_context_takeover, server_max_window_bits)
|
||||||
};
|
};
|
||||||
|
|
||||||
DeflateContext {
|
(
|
||||||
compress: DeflateCompressionContext::new(
|
DeflateCompressionContext::new(
|
||||||
compression_level,
|
compression_level,
|
||||||
remote_no_context_takeover,
|
remote_no_context_takeover,
|
||||||
remote_max_window_bits,
|
remote_max_window_bits,
|
||||||
),
|
),
|
||||||
decompress: DeflateDecompressionContext::new(
|
DeflateDecompressionContext::new(local_no_context_takeover, local_max_window_bits),
|
||||||
local_no_context_takeover,
|
)
|
||||||
local_max_window_bits,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Server-side DEFLATE configuration.
|
/// Server-side DEFLATE configuration.
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct DeflateServerConfig {
|
pub struct DeflateServerConfig {
|
||||||
/// DEFLATE compression level. See [`flate2::`]
|
/// DEFLATE compression level. See [`flate2::Compression`] for details.
|
||||||
pub compression_level: Option<DeflateCompressionLevel>,
|
pub compression_level: Option<DeflateCompressionLevel>,
|
||||||
|
/// Disallow server from take over context. Default is false.
|
||||||
pub server_no_context_takeover: bool,
|
pub server_no_context_takeover: bool,
|
||||||
|
/// Disallow client from take over context. Default is false.
|
||||||
pub client_no_context_takeover: bool,
|
pub client_no_context_takeover: bool,
|
||||||
|
/// Maximum size of server's DEFLATE sliding window in bits, between 9 and 15. Default is 15.
|
||||||
pub server_max_window_bits: Option<u8>,
|
pub server_max_window_bits: Option<u8>,
|
||||||
|
/// Maximum size of client's DEFLATE sliding window in bits, between 9 and 15. Default is 15.
|
||||||
pub client_max_window_bits: Option<u8>,
|
pub client_max_window_bits: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeflateServerConfig {
|
impl DeflateServerConfig {
|
||||||
|
/// Negotiate context parameters.
|
||||||
|
/// Since parameters from the client may be incompatible with the server configuration,
|
||||||
|
/// actual parameters could be adjusted here. Conversion rules are as follows:
|
||||||
|
///
|
||||||
|
/// ## server_no_context_takeover
|
||||||
|
///
|
||||||
|
/// | Config | Request | Response |
|
||||||
|
/// | ------ | ------- | --------- |
|
||||||
|
/// | false | false | false |
|
||||||
|
/// | false | true | true |
|
||||||
|
/// | true | false | true |
|
||||||
|
/// | true | true | true |
|
||||||
|
///
|
||||||
|
/// ## client_no_context_takeover
|
||||||
|
///
|
||||||
|
/// | Config | Request | Response |
|
||||||
|
/// | ------ | ------- | --------- |
|
||||||
|
/// | false | false | false |
|
||||||
|
/// | false | true | true |
|
||||||
|
/// | true | false | true |
|
||||||
|
/// | true | true | true |
|
||||||
|
///
|
||||||
|
/// ## server_max_window_bits
|
||||||
|
///
|
||||||
|
/// | Config | Request | Response |
|
||||||
|
/// | ------------ | ------------ | -------- |
|
||||||
|
/// | None | None | None |
|
||||||
|
/// | None | 9 <= R <= 15 | R |
|
||||||
|
/// | 9 <= C <= 15 | None | C |
|
||||||
|
/// | 9 <= C <= 15 | 9 <= R <= C | R |
|
||||||
|
/// | 9 <= C <= 15 | C <= R <= 15 | C |
|
||||||
|
///
|
||||||
|
/// ## client_max_window_bits
|
||||||
|
///
|
||||||
|
/// | Config | Request | Response |
|
||||||
|
/// | ------------ | ------------ | -------- |
|
||||||
|
/// | None | None | None |
|
||||||
|
/// | None | Unspecified | None |
|
||||||
|
/// | None | 9 <= R <= 15 | R |
|
||||||
|
/// | 9 <= C <= 15 | None | None |
|
||||||
|
/// | 9 <= C <= 15 | Unspecified | C |
|
||||||
|
/// | 9 <= C <= 15 | 9 <= R <= C | R |
|
||||||
|
/// | 9 <= C <= 15 | C <= R <= 15 | C |
|
||||||
pub fn negotiate(&self, params: DeflateSessionParameters) -> DeflateSessionParameters {
|
pub fn negotiate(&self, params: DeflateSessionParameters) -> DeflateSessionParameters {
|
||||||
let server_no_context_takeover =
|
let server_no_context_takeover =
|
||||||
if self.server_no_context_takeover && !params.server_no_context_takeover {
|
if self.server_no_context_takeover && !params.server_no_context_takeover {
|
||||||
@ -313,6 +360,7 @@ impl DeflateServerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DEFLATE decompression context.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DeflateDecompressionContext {
|
pub struct DeflateDecompressionContext {
|
||||||
pub(super) local_no_context_takeover: bool,
|
pub(super) local_no_context_takeover: bool,
|
||||||
@ -347,7 +395,7 @@ impl DeflateDecompressionContext {
|
|||||||
*self = Self::new(local_no_context_takeover, local_max_window_bits);
|
*self = Self::new(local_no_context_takeover, local_max_window_bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decompress(
|
pub(super) fn decompress(
|
||||||
&mut self,
|
&mut self,
|
||||||
fin: bool,
|
fin: bool,
|
||||||
opcode: OpCode,
|
opcode: OpCode,
|
||||||
@ -418,13 +466,14 @@ impl DeflateDecompressionContext {
|
|||||||
Ok(output.into())
|
Ok(output.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.decompress.reset(false);
|
self.decompress.reset(false);
|
||||||
self.total_bytes_read = 0;
|
self.total_bytes_read = 0;
|
||||||
self.total_bytes_written = 0;
|
self.total_bytes_written = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DEFLATE compression context.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DeflateCompressionContext {
|
pub struct DeflateCompressionContext {
|
||||||
pub(super) compression_level: flate2::Compression,
|
pub(super) compression_level: flate2::Compression,
|
||||||
@ -474,7 +523,7 @@ impl DeflateCompressionContext {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compress(&mut self, fin: bool, payload: Bytes) -> Result<Bytes, ProtocolError> {
|
pub(super) fn compress(&mut self, fin: bool, payload: Bytes) -> Result<Bytes, ProtocolError> {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut buf = [0u8; BUF_SIZE];
|
let mut buf = [0u8; BUF_SIZE];
|
||||||
|
|
||||||
@ -526,8 +575,271 @@ impl DeflateCompressionContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[cfg(test)]
|
||||||
pub struct DeflateContext {
|
mod tests {
|
||||||
pub compress: DeflateCompressionContext,
|
use crate::body::MessageBody;
|
||||||
pub decompress: DeflateDecompressionContext,
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_session_parameters() {
|
||||||
|
let extension = "abc, def, permessage-deflate";
|
||||||
|
assert_eq!(
|
||||||
|
DeflateSessionParameters::from_extension_header(&extension),
|
||||||
|
vec![Ok(DeflateSessionParameters::default())]
|
||||||
|
);
|
||||||
|
|
||||||
|
let extension = "permessage-deflate; unknown_parameter";
|
||||||
|
assert_eq!(
|
||||||
|
DeflateSessionParameters::from_extension_header(&extension),
|
||||||
|
vec![Err(DeflateHandshakeError::UnknownWebSocketParameters)]
|
||||||
|
);
|
||||||
|
|
||||||
|
let extension = "permessage-deflate; client_max_window_bits=9; client_max_window_bits=10";
|
||||||
|
assert_eq!(
|
||||||
|
DeflateSessionParameters::from_extension_header(&extension),
|
||||||
|
vec![Err(DeflateHandshakeError::DuplicateParameter(
|
||||||
|
"client_max_window_bits"
|
||||||
|
))]
|
||||||
|
);
|
||||||
|
|
||||||
|
let extension = "permessage-deflate; server_max_window_bits=8";
|
||||||
|
assert_eq!(
|
||||||
|
DeflateSessionParameters::from_extension_header(&extension),
|
||||||
|
vec![Err(DeflateHandshakeError::MaxWindowBitsOutOfRange)]
|
||||||
|
);
|
||||||
|
|
||||||
|
let extension = "permessage-deflate; server_max_window_bits=16";
|
||||||
|
assert_eq!(
|
||||||
|
DeflateSessionParameters::from_extension_header(&extension),
|
||||||
|
vec![Err(DeflateHandshakeError::MaxWindowBitsOutOfRange)]
|
||||||
|
);
|
||||||
|
|
||||||
|
let extension = "permessage-deflate; client_max_window_bits; server_max_window_bits=15; \
|
||||||
|
client_no_context_takeover; server_no_context_takeover, \
|
||||||
|
permessage-deflate; client_max_window_bits=10";
|
||||||
|
assert_eq!(
|
||||||
|
DeflateSessionParameters::from_extension_header(&extension),
|
||||||
|
vec![
|
||||||
|
Ok(DeflateSessionParameters {
|
||||||
|
server_no_context_takeover: true,
|
||||||
|
client_no_context_takeover: true,
|
||||||
|
server_max_window_bits: Some(15),
|
||||||
|
client_max_window_bits: Some(ClientMaxWindowBits::NotSpecified)
|
||||||
|
}),
|
||||||
|
Ok(DeflateSessionParameters {
|
||||||
|
server_no_context_takeover: false,
|
||||||
|
client_no_context_takeover: false,
|
||||||
|
server_max_window_bits: None,
|
||||||
|
client_max_window_bits: Some(ClientMaxWindowBits::Specified(10))
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_compress() {
|
||||||
|
// With context takeover
|
||||||
|
|
||||||
|
let mut compress = DeflateCompressionContext::new(None, false, 15);
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(true, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(true, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2@0\x01\0")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Without context takeover
|
||||||
|
|
||||||
|
let mut compress = DeflateCompressionContext::new(None, true, 15);
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(true, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(true, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
);
|
||||||
|
|
||||||
|
// With continuation
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(false, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
);
|
||||||
|
// Continuation keeps context.
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(true, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2@0\x01\0")
|
||||||
|
);
|
||||||
|
// after continuation, context resets
|
||||||
|
assert_eq!(
|
||||||
|
compress
|
||||||
|
.compress(true, "Hello World".try_into_bytes().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decompress() {
|
||||||
|
// With context takeover
|
||||||
|
|
||||||
|
let mut decompress = DeflateDecompressionContext::new(false, 15);
|
||||||
|
|
||||||
|
// Without RSV1 bit, decompression does not happen.
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::empty(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Control frames (such as ping/pong) are not decompressed
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Ping,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Successful decompression
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Success subsequent decompression
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2@0\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Invalid compression payload
|
||||||
|
assert!(decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
// When there was error, context is reset.
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Without context takeover
|
||||||
|
|
||||||
|
let mut decompress = DeflateDecompressionContext::new(true, 15);
|
||||||
|
|
||||||
|
// Successful decompression
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Context has been reset.
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
|
||||||
|
// With continuation
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
false,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
// Continuation keeps context.
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
true,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2@0\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
// When continuation has finished, context is reset.
|
||||||
|
assert_eq!(
|
||||||
|
decompress
|
||||||
|
.decompress(
|
||||||
|
false,
|
||||||
|
OpCode::Text,
|
||||||
|
RsvBits::RSV1,
|
||||||
|
Bytes::from_static(b"\xf2H\xcd\xc9\xc9W\x08\xcf/\xcaI\x01\0")
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
Bytes::from_static(b"Hello World")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ mod proto;
|
|||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
pub use self::deflate::{DeflateCompressionLevel, DeflateServerConfig, DeflateSessionParameters};
|
pub use self::deflate::{DeflateCompressionLevel, DeflateServerConfig, DeflateSessionParameters};
|
||||||
pub use self::{
|
pub use self::{
|
||||||
codec::{Codec, Frame, Item, Message},
|
codec::{Codec, Decoder, Encoder, Frame, Item, Message},
|
||||||
dispatcher::Dispatcher,
|
dispatcher::Dispatcher,
|
||||||
frame::Parser,
|
frame::Parser,
|
||||||
proto::{hash_key, CloseCode, CloseReason, OpCode, RsvBits},
|
proto::{hash_key, CloseCode, CloseReason, OpCode, RsvBits},
|
||||||
@ -169,10 +169,19 @@ pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
|||||||
|
|
||||||
/// Verify WebSocket handshake request with DEFLATE compression configurations.
|
/// Verify WebSocket handshake request with DEFLATE compression configurations.
|
||||||
#[cfg(feature = "compress-ws-deflate")]
|
#[cfg(feature = "compress-ws-deflate")]
|
||||||
pub fn handshake_with_deflate(
|
pub fn handshake_deflate(
|
||||||
req: &RequestHead,
|
|
||||||
config: &deflate::DeflateServerConfig,
|
config: &deflate::DeflateServerConfig,
|
||||||
) -> Result<(ResponseBuilder, Option<deflate::DeflateContext>), HandshakeError> {
|
req: &RequestHead,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
ResponseBuilder,
|
||||||
|
Option<(
|
||||||
|
deflate::DeflateCompressionContext,
|
||||||
|
deflate::DeflateDecompressionContext,
|
||||||
|
)>,
|
||||||
|
),
|
||||||
|
HandshakeError,
|
||||||
|
> {
|
||||||
verify_handshake(req)?;
|
verify_handshake(req)?;
|
||||||
|
|
||||||
let mut available_configurations = vec![];
|
let mut available_configurations = vec![];
|
||||||
@ -212,9 +221,9 @@ pub fn handshake_with_deflate(
|
|||||||
|
|
||||||
if let Some(selected_config) = selected_config {
|
if let Some(selected_config) = selected_config {
|
||||||
let param = config.negotiate(selected_config);
|
let param = config.negotiate(selected_config);
|
||||||
let context = param.create_context(config.compression_level, false);
|
let contexts = param.create_context(config.compression_level, false);
|
||||||
response.insert_header(param);
|
response.insert_header(param);
|
||||||
Ok((response, Some(context)))
|
Ok((response, Some(contexts)))
|
||||||
} else {
|
} else {
|
||||||
Ok((response, None))
|
Ok((response, None))
|
||||||
}
|
}
|
||||||
|
@ -226,7 +226,7 @@ bitflags::bitflags! {
|
|||||||
/// RSV bits defined in [RFC 6455 §5.2].
|
/// RSV bits defined in [RFC 6455 §5.2].
|
||||||
/// Reserved for extensions and should be set to zero if no extensions are applicable.
|
/// Reserved for extensions and should be set to zero if no extensions are applicable.
|
||||||
///
|
///
|
||||||
/// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
/// [RFC 6455 §5.2]: https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||||
pub struct RsvBits: u8 {
|
pub struct RsvBits: u8 {
|
||||||
const RSV1 = 0b0000_0100;
|
const RSV1 = 0b0000_0100;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user