From 0121bf968258990d0a3a9cbb88f5830d82cff411 Mon Sep 17 00:00:00 2001 From: "Adam Bezecny (DHL IT Services)" Date: Thu, 26 Nov 2020 20:51:50 +0100 Subject: [PATCH 01/10] WebSocketConfig extended to allow accepting unmasked client frames --- examples/srv_accept_unmasked_frames.rs | 50 ++++++++++++++++++++++++++ src/protocol/mod.rs | 25 +++++++++++-- 2 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 examples/srv_accept_unmasked_frames.rs diff --git a/examples/srv_accept_unmasked_frames.rs b/examples/srv_accept_unmasked_frames.rs new file mode 100644 index 0000000..04acbfc --- /dev/null +++ b/examples/srv_accept_unmasked_frames.rs @@ -0,0 +1,50 @@ +use std::{net::TcpListener, thread::spawn}; +use tungstenite::{ + handshake::server::{Request, Response}, + protocol::WebSocketConfig, + server::accept_hdr_with_config, +}; + +fn main() { + env_logger::init(); + let server = TcpListener::bind("127.0.0.1:3012").unwrap(); + for stream in server.incoming() { + spawn(move || { + let callback = |req: &Request, mut response: Response| { + println!("Received a new ws handshake"); + println!("The request's path is: {}", req.uri().path()); + println!("The request's headers are:"); + for (ref header, _value) in req.headers() { + println!("* {}", header); + } + + // Let's add an additional header to our response to the client. + let headers = response.headers_mut(); + headers.append("MyCustomHeader", ":)".parse().unwrap()); + headers.append("SOME_TUNGSTENITE_HEADER", "header_value".parse().unwrap()); + + Ok(response) + }; + + let config = Some(WebSocketConfig { + max_send_queue: None, + max_message_size: None, + max_frame_size: None, + // This setting allows to accept client frames which are not masked + // This is not in compliance with RFC 6455 but might be handy in some + // rare cases where it is necessary to integrate with existing/legacy + // clients which are sending unmasked frames + server_allow_unmasked: Some(true), + }); + + let mut websocket = accept_hdr_with_config(stream.unwrap(), callback, config).unwrap(); + + loop { + let msg = websocket.read_message().unwrap(); + if msg.is_binary() || msg.is_text() { + println!("received message {}", msg); + } + } + }); + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 72485e9..6714005 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -50,6 +50,11 @@ pub struct WebSocketConfig { /// be reasonably big for all normal use-cases but small enough to prevent memory eating /// by a malicious user. pub max_frame_size: Option, + /// If set to true it will allow the websocket server to accept unmasked frames from client. + /// Even though this behaviour is not in compliance with RFC 6455 (which requires the server + /// to close the connection when unmasked frame from client is received) it might be handy in some cases + /// as there are existing applications sending unmasked client frames. + pub server_allow_unmasked: Option, } impl Default for WebSocketConfig { @@ -58,6 +63,7 @@ impl Default for WebSocketConfig { max_send_queue: None, max_message_size: Some(64 << 20), max_frame_size: Some(16 << 20), + server_allow_unmasked: None, } } } @@ -449,9 +455,22 @@ impl WebSocketContext { } else { // The server MUST close the connection upon receiving a // frame that is not masked. (RFC 6455) - return Err(Error::Protocol( - "Received an unmasked frame from client".into(), - )); + // The only exception here is if the user explicitly accepts given + // stream (by tungstenite::server::accept_with_config or tungstenite::server::accept_hdr_with_config) + // with WebSocketConfig.server_allow_unmasked set to Some(true) + if let Some(server_allow_unmasked_val) = + self.get_config().server_allow_unmasked + { + if server_allow_unmasked_val == false { + return Err(Error::Protocol( + "Received an unmasked frame from client".into(), + )); + } + } else { + return Err(Error::Protocol( + "Received an unmasked frame from client".into(), + )); + } } } Role::Client => { From a9adb42343df447e74071f55e408966b073f0419 Mon Sep 17 00:00:00 2001 From: "Adam Bezecny (DHL IT Services)" Date: Fri, 27 Nov 2020 21:38:33 +0100 Subject: [PATCH 02/10] server_allow_unmasked changed from Option to bool --- examples/srv_accept_unmasked_frames.rs | 2 +- src/protocol/mod.rs | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/examples/srv_accept_unmasked_frames.rs b/examples/srv_accept_unmasked_frames.rs index 04acbfc..3710803 100644 --- a/examples/srv_accept_unmasked_frames.rs +++ b/examples/srv_accept_unmasked_frames.rs @@ -34,7 +34,7 @@ fn main() { // This is not in compliance with RFC 6455 but might be handy in some // rare cases where it is necessary to integrate with existing/legacy // clients which are sending unmasked frames - server_allow_unmasked: Some(true), + server_allow_unmasked: true, }); let mut websocket = accept_hdr_with_config(stream.unwrap(), callback, config).unwrap(); diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 6714005..e9730e8 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -54,7 +54,7 @@ pub struct WebSocketConfig { /// Even though this behaviour is not in compliance with RFC 6455 (which requires the server /// to close the connection when unmasked frame from client is received) it might be handy in some cases /// as there are existing applications sending unmasked client frames. - pub server_allow_unmasked: Option, + pub server_allow_unmasked: bool, } impl Default for WebSocketConfig { @@ -63,7 +63,7 @@ impl Default for WebSocketConfig { max_send_queue: None, max_message_size: Some(64 << 20), max_frame_size: Some(16 << 20), - server_allow_unmasked: None, + server_allow_unmasked: false, } } } @@ -457,16 +457,8 @@ impl WebSocketContext { // frame that is not masked. (RFC 6455) // The only exception here is if the user explicitly accepts given // stream (by tungstenite::server::accept_with_config or tungstenite::server::accept_hdr_with_config) - // with WebSocketConfig.server_allow_unmasked set to Some(true) - if let Some(server_allow_unmasked_val) = - self.get_config().server_allow_unmasked - { - if server_allow_unmasked_val == false { - return Err(Error::Protocol( - "Received an unmasked frame from client".into(), - )); - } - } else { + // with WebSocketConfig.server_allow_unmasked set to true + if self.get_config().server_allow_unmasked == false { return Err(Error::Protocol( "Received an unmasked frame from client".into(), )); From 4f7dab5d7b150da3f7bc333ed6f2bb601c26f6af Mon Sep 17 00:00:00 2001 From: "Adam Bezecny (DHL IT Services)" Date: Mon, 30 Nov 2020 19:25:55 +0100 Subject: [PATCH 03/10] documentation for server_allow_unmasked improved --- src/protocol/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index e9730e8..c095a4a 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -53,7 +53,9 @@ pub struct WebSocketConfig { /// If set to true it will allow the websocket server to accept unmasked frames from client. /// Even though this behaviour is not in compliance with RFC 6455 (which requires the server /// to close the connection when unmasked frame from client is received) it might be handy in some cases - /// as there are existing applications sending unmasked client frames. + /// as there are existing applications sending unmasked client frames. By default (i.e. unless not explicitly specified) + /// this flag is set to false which means violation of RFC 6455 is not allowed and unmasked client frames will be rejected + ///by websocket server. pub server_allow_unmasked: bool, } From 9f292d21fc411d9c2fb76d872be19912024c03c8 Mon Sep 17 00:00:00 2001 From: "Adam Bezecny (DHL IT Services)" Date: Tue, 1 Dec 2020 19:19:45 +0100 Subject: [PATCH 04/10] minor fixes --- examples/srv_accept_unmasked_frames.rs | 2 +- src/protocol/mod.rs | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/srv_accept_unmasked_frames.rs b/examples/srv_accept_unmasked_frames.rs index 3710803..953f9f1 100644 --- a/examples/srv_accept_unmasked_frames.rs +++ b/examples/srv_accept_unmasked_frames.rs @@ -34,7 +34,7 @@ fn main() { // This is not in compliance with RFC 6455 but might be handy in some // rare cases where it is necessary to integrate with existing/legacy // clients which are sending unmasked frames - server_allow_unmasked: true, + accept_unmasked_frames: true, }); let mut websocket = accept_hdr_with_config(stream.unwrap(), callback, config).unwrap(); diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index c095a4a..54870d9 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -50,13 +50,12 @@ pub struct WebSocketConfig { /// be reasonably big for all normal use-cases but small enough to prevent memory eating /// by a malicious user. pub max_frame_size: Option, - /// If set to true it will allow the websocket server to accept unmasked frames from client. - /// Even though this behaviour is not in compliance with RFC 6455 (which requires the server - /// to close the connection when unmasked frame from client is received) it might be handy in some cases - /// as there are existing applications sending unmasked client frames. By default (i.e. unless not explicitly specified) - /// this flag is set to false which means violation of RFC 6455 is not allowed and unmasked client frames will be rejected - ///by websocket server. - pub server_allow_unmasked: bool, + /// When set to `true`, the server will accept and handle unmasked frames + /// from the client. According to the RFC 6455, the server must close the + /// connection to the client in such cases, however it seems like there are + /// some popular libraries that are sending unmasked frames, ignoring the RFC. + /// By default this option is set to `false`, i.e. according to RFC 6455. + pub accept_unmasked_frames: bool, } impl Default for WebSocketConfig { @@ -65,7 +64,7 @@ impl Default for WebSocketConfig { max_send_queue: None, max_message_size: Some(64 << 20), max_frame_size: Some(16 << 20), - server_allow_unmasked: false, + accept_unmasked_frames: false, } } } @@ -459,8 +458,8 @@ impl WebSocketContext { // frame that is not masked. (RFC 6455) // The only exception here is if the user explicitly accepts given // stream (by tungstenite::server::accept_with_config or tungstenite::server::accept_hdr_with_config) - // with WebSocketConfig.server_allow_unmasked set to true - if self.get_config().server_allow_unmasked == false { + // with WebSocketConfig.accept_unmasked_frames set to true + if !self.config.accept_unmasked_frames { return Err(Error::Protocol( "Received an unmasked frame from client".into(), )); From 2e55ed0fcccf3bef2e59487d36ce7d3487bc4e59 Mon Sep 17 00:00:00 2001 From: "Adam Bezecny (DHL IT Services)" Date: Tue, 1 Dec 2020 19:21:45 +0100 Subject: [PATCH 05/10] comment streamlined --- src/protocol/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 54870d9..9184700 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -457,8 +457,7 @@ impl WebSocketContext { // The server MUST close the connection upon receiving a // frame that is not masked. (RFC 6455) // The only exception here is if the user explicitly accepts given - // stream (by tungstenite::server::accept_with_config or tungstenite::server::accept_hdr_with_config) - // with WebSocketConfig.accept_unmasked_frames set to true + // stream by setting WebSocketConfig.accept_unmasked_frames to true if !self.config.accept_unmasked_frames { return Err(Error::Protocol( "Received an unmasked frame from client".into(), From c8c236056b1464dee2fb2fbf5e1e68b03b72c7aa Mon Sep 17 00:00:00 2001 From: "Adam Bezecny (DHL IT Services)" Date: Wed, 2 Dec 2020 22:43:47 +0100 Subject: [PATCH 06/10] if streamlined --- src/protocol/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 9184700..63763f0 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -453,16 +453,14 @@ impl WebSocketContext { // A server MUST remove masking for data frames received from a client // as described in Section 5.3. (RFC 6455) frame.apply_mask() - } else { + } else if !self.config.accept_unmasked_frames { // The server MUST close the connection upon receiving a // frame that is not masked. (RFC 6455) // The only exception here is if the user explicitly accepts given // stream by setting WebSocketConfig.accept_unmasked_frames to true - if !self.config.accept_unmasked_frames { - return Err(Error::Protocol( - "Received an unmasked frame from client".into(), - )); - } + return Err(Error::Protocol( + "Received an unmasked frame from client".into(), + )); } } Role::Client => { From 82e09ea025c50cf4aa8d5163803651781a09d59c Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Sat, 2 Jan 2021 16:45:50 +0900 Subject: [PATCH 07/10] Remove remaining dynamic dispatch usage --- src/handshake/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handshake/server.rs b/src/handshake/server.rs index e60acb5..53227ab 100644 --- a/src/handshake/server.rs +++ b/src/handshake/server.rs @@ -94,7 +94,7 @@ pub fn create_response_with_body( } // Assumes that this is a valid response -fn write_response(w: &mut dyn io::Write, response: &HttpResponse) -> Result<()> { +fn write_response(mut w: impl io::Write, response: &HttpResponse) -> Result<()> { writeln!( w, "{version:?} {status}\r", From d24e3e4734f4f9ac5ef459f3c6fffea907fdb843 Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Fri, 8 Jan 2021 10:29:45 +0900 Subject: [PATCH 08/10] Update input_buffer to use the official version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 173292e..394e720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ byteorder = "1.3.2" bytes = "1.0" http = "0.2" httparse = "1.3.4" -input_buffer = { version = "0.4.0", git = "https://github.com/snapview/input_buffer.git" } +input_buffer = "0.4.0" log = "0.4.8" rand = "0.8.0" sha-1 = "0.9" From 6087e07aeddba86d62865ce1f5df63f9743381d4 Mon Sep 17 00:00:00 2001 From: Daniel Abramov Date: Fri, 8 Jan 2021 13:15:38 +0100 Subject: [PATCH 09/10] Bump version --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 394e720..9ec4595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,9 @@ authors = ["Alexey Galakhov", "Daniel Abramov"] license = "MIT/Apache-2.0" readme = "README.md" homepage = "https://github.com/snapview/tungstenite-rs" -documentation = "https://docs.rs/tungstenite/0.11.1" +documentation = "https://docs.rs/tungstenite/0.12.0" repository = "https://github.com/snapview/tungstenite-rs" -version = "0.11.1" +version = "0.12.0" edition = "2018" [features] From 7265a8560cea928b204fb4ee944d400db4892362 Mon Sep 17 00:00:00 2001 From: Daniel Abramov Date: Fri, 8 Jan 2021 13:17:35 +0100 Subject: [PATCH 10/10] Add a changelog Fixes #45 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b78c43b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# 0.12.0 + +- Add facilities to allow clients to follow HTTP 3XX redirects. +- Allow accepting unmasked clients on the server side to be compatible with some legacy / invalid clients. +- Update of dependencies and documentation fixes.