1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-06-25 06:39:22 +02:00

refactor websocket key hashing (#2035)

This commit is contained in:
Rob Ede
2021-02-28 19:55:34 +00:00
committed by GitHub
parent c836de44af
commit cd652dca75
8 changed files with 234 additions and 76 deletions

View File

@ -1,4 +1,4 @@
//! WebSocket protocol.
//! WebSocket protocol implementation.
//!
//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a
//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
@ -8,9 +8,12 @@ use std::io;
use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
use crate::error::ResponseError;
use crate::message::RequestHead;
use crate::response::{Response, ResponseBuilder};
use crate::{
error::ResponseError,
header::HeaderValue,
message::RequestHead,
response::{Response, ResponseBuilder},
};
mod codec;
mod dispatcher;
@ -89,7 +92,7 @@ pub enum HandshakeError {
NoVersionHeader,
/// Unsupported WebSocket version.
#[display(fmt = "Unsupported version.")]
#[display(fmt = "Unsupported WebSocket version.")]
UnsupportedVersion,
/// WebSocket key is not set or wrong.
@ -105,19 +108,19 @@ impl ResponseError for HandshakeError {
.finish(),
HandshakeError::NoWebsocketUpgrade => Response::BadRequest()
.reason("No WebSocket UPGRADE header found")
.reason("No WebSocket Upgrade header found")
.finish(),
HandshakeError::NoConnectionUpgrade => Response::BadRequest()
.reason("No CONNECTION upgrade")
.reason("No Connection upgrade")
.finish(),
HandshakeError::NoVersionHeader => Response::BadRequest()
.reason("Websocket version header is required")
.reason("WebSocket version header is required")
.finish(),
HandshakeError::UnsupportedVersion => Response::BadRequest()
.reason("Unsupported version")
.reason("Unsupported WebSocket version")
.finish(),
HandshakeError::BadWebsocketKey => {
@ -193,7 +196,11 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
Response::build(StatusCode::SWITCHING_PROTOCOLS)
.upgrade("websocket")
.insert_header((header::TRANSFER_ENCODING, "chunked"))
.insert_header((header::SEC_WEBSOCKET_ACCEPT, key))
.insert_header((
header::SEC_WEBSOCKET_ACCEPT,
// key is known to be header value safe ascii
HeaderValue::from_bytes(&key).unwrap(),
))
.take()
}

View File

@ -1,5 +1,7 @@
use std::convert::{From, Into};
use std::fmt;
use std::{
convert::{From, Into},
fmt,
};
/// Operation codes as part of RFC6455.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
@ -28,8 +30,9 @@ pub enum OpCode {
impl fmt::Display for OpCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::OpCode::*;
match *self {
use OpCode::*;
match self {
Continue => write!(f, "CONTINUE"),
Text => write!(f, "TEXT"),
Binary => write!(f, "BINARY"),
@ -44,6 +47,7 @@ impl fmt::Display for OpCode {
impl From<OpCode> for u8 {
fn from(op: OpCode) -> u8 {
use self::OpCode::*;
match op {
Continue => 0,
Text => 1,
@ -62,6 +66,7 @@ impl From<OpCode> for u8 {
impl From<u8> for OpCode {
fn from(byte: u8) -> OpCode {
use self::OpCode::*;
match byte {
0 => Continue,
1 => Text,
@ -77,63 +82,66 @@ impl From<u8> for OpCode {
/// Status code used to indicate why an endpoint is closing the WebSocket connection.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum CloseCode {
/// Indicates a normal closure, meaning that the purpose for
/// which the connection was established has been fulfilled.
/// Indicates a normal closure, meaning that the purpose for which the connection was
/// established has been fulfilled.
Normal,
/// Indicates that an endpoint is "going away", such as a server
/// going down or a browser having navigated away from a page.
/// Indicates that an endpoint is "going away", such as a server going down or a browser having
/// navigated away from a page.
Away,
/// Indicates that an endpoint is terminating the connection due
/// to a protocol error.
/// Indicates that an endpoint is terminating the connection due to a protocol error.
Protocol,
/// Indicates that an endpoint is terminating the connection
/// because it has received a type of data it cannot accept (e.g., an
/// endpoint that understands only text data MAY send this if it
/// Indicates that an endpoint is terminating the connection because it has received a type of
/// data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it
/// receives a binary message).
Unsupported,
/// Indicates an abnormal closure. If the abnormal closure was due to an
/// error, this close code will not be used. Instead, the `on_error` method
/// of the handler will be called with the error. However, if the connection
/// is simply dropped, without an error, this close code will be sent to the
/// handler.
/// Indicates an abnormal closure. If the abnormal closure was due to an error, this close code
/// will not be used. Instead, the `on_error` method of the handler will be called with
/// the error. However, if the connection is simply dropped, without an error, this close code
/// will be sent to the handler.
Abnormal,
/// Indicates that an endpoint is terminating the connection
/// because it has received data within a message that was not
/// consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\]
/// Indicates that an endpoint is terminating the connection because it has received data within
/// a message that was not consistent with the type of the message (e.g., non-UTF-8 \[RFC3629\]
/// data within a text message).
Invalid,
/// Indicates that an endpoint is terminating the connection
/// because it has received a message that violates its policy. This
/// is a generic status code that can be returned when there is no
/// other more suitable status code (e.g., Unsupported or Size) or if there
/// is a need to hide specific details about the policy.
/// Indicates that an endpoint is terminating the connection because it has received a message
/// that violates its policy. This is a generic status code that can be returned when there is
/// no other more suitable status code (e.g., Unsupported or Size) or if there is a need to hide
/// specific details about the policy.
Policy,
/// Indicates that an endpoint is terminating the connection
/// because it has received a message that is too big for it to
/// process.
/// Indicates that an endpoint is terminating the connection because it has received a message
/// that is too big for it to process.
Size,
/// Indicates that an endpoint (client) is terminating the
/// connection because it has expected the server to negotiate one or
/// more extension, but the server didn't return them in the response
/// message of the WebSocket handshake. The list of extensions that
/// are needed should be given as the reason for closing.
/// Note that this status code is not used by the server, because it
/// can fail the WebSocket handshake instead.
/// Indicates that an endpoint (client) is terminating the connection because it has expected
/// the server to negotiate one or more extension, but the server didn't return them in the
/// response message of the WebSocket handshake. The list of extensions that are needed should
/// be given as the reason for closing. Note that this status code is not used by the server,
/// because it can fail the WebSocket handshake instead.
Extension,
/// Indicates that a server is terminating the connection because
/// it encountered an unexpected condition that prevented it from
/// fulfilling the request.
/// Indicates that a server is terminating the connection because it encountered an unexpected
/// condition that prevented it from fulfilling the request.
Error,
/// Indicates that the server is restarting. A client may choose to
/// reconnect, and if it does, it should use a randomized delay of 5-30
/// seconds between attempts.
/// Indicates that the server is restarting. A client may choose to reconnect, and if it does,
/// it should use a randomized delay of 5-30 seconds between attempts.
Restart,
/// Indicates that the server is overloaded and the client should either
/// connect to a different IP (when multiple targets exist), or
/// reconnect to the same IP when a user has performed an action.
/// Indicates that the server is overloaded and the client should either connect to a different
/// IP (when multiple targets exist), or reconnect to the same IP when a user has performed
/// an action.
Again,
#[doc(hidden)]
Tls,
#[doc(hidden)]
Other(u16),
}
@ -141,6 +149,7 @@ pub enum CloseCode {
impl From<CloseCode> for u16 {
fn from(code: CloseCode) -> u16 {
use self::CloseCode::*;
match code {
Normal => 1000,
Away => 1001,
@ -163,6 +172,7 @@ impl From<CloseCode> for u16 {
impl From<u16> for CloseCode {
fn from(code: u16) -> CloseCode {
use self::CloseCode::*;
match code {
1000 => Normal,
1001 => Away,
@ -210,17 +220,29 @@ impl<T: Into<String>> From<(CloseCode, T)> for CloseReason {
}
}
static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
/// The WebSocket GUID as stated in the spec. See https://tools.ietf.org/html/rfc6455#section-1.3.
static WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// TODO: hash is always same size, we don't need String
pub fn hash_key(key: &[u8]) -> String {
use sha1::Digest;
let mut hasher = sha1::Sha1::new();
/// Hashes the `Sec-WebSocket-Key` header according to the WebSocket spec.
///
/// Result is a Base64 encoded byte array. `base64(sha1(input))` is always 28 bytes.
pub fn hash_key(key: &[u8]) -> [u8; 28] {
let hash = {
use sha1::Digest as _;
hasher.update(key);
hasher.update(WS_GUID.as_bytes());
let mut hasher = sha1::Sha1::new();
base64::encode(&hasher.finalize())
hasher.update(key);
hasher.update(WS_GUID);
hasher.finalize()
};
let mut hash_b64 = [0; 28];
let n = base64::encode_config_slice(&hash, base64::STANDARD, &mut hash_b64);
assert_eq!(n, 28);
hash_b64
}
#[cfg(test)]
@ -288,11 +310,11 @@ mod test {
#[test]
fn test_hash_key() {
let hash = hash_key(b"hello actix-web");
assert_eq!(&hash, "cR1dlyUUJKp0s/Bel25u5TgvC3E=");
assert_eq!(&hash, b"cR1dlyUUJKp0s/Bel25u5TgvC3E=");
}
#[test]
fn closecode_from_u16() {
fn close_code_from_u16() {
assert_eq!(CloseCode::from(1000u16), CloseCode::Normal);
assert_eq!(CloseCode::from(1001u16), CloseCode::Away);
assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol);
@ -310,7 +332,7 @@ mod test {
}
#[test]
fn closecode_into_u16() {
fn close_code_into_u16() {
assert_eq!(1000u16, Into::<u16>::into(CloseCode::Normal));
assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));