Skip to main content

actix_http/ws/
mod.rs

1//! WebSocket protocol implementation.
2//!
3//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a
4//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
5
6use std::io;
7
8use base64::prelude::*;
9use derive_more::{Display, Error, From};
10use http::{header, Method, StatusCode};
11
12use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
13
14mod codec;
15mod dispatcher;
16mod frame;
17mod mask;
18mod proto;
19
20pub use self::{
21    codec::{Codec, Frame, Item, Message},
22    dispatcher::Dispatcher,
23    frame::Parser,
24    proto::{hash_key, CloseCode, CloseReason, OpCode},
25};
26
27/// WebSocket protocol errors.
28#[derive(Debug, Display, Error, From)]
29pub enum ProtocolError {
30    /// Received an unmasked frame from client.
31    #[display("Received an unmasked frame from client")]
32    UnmaskedFrame,
33
34    /// Received a masked frame from server.
35    #[display("Received a masked frame from server")]
36    MaskedFrame,
37
38    /// Encountered invalid opcode.
39    #[display("Invalid opcode ({})", _0)]
40    InvalidOpcode(#[error(not(source))] u8),
41
42    // TODO(semver-major):
43    // /// Received a frame with non-zero reserved bits.
44    // #[display("Received a frame with non-zero reserved bits")]
45    // InvalidReservedBits,
46    //
47    /// Invalid control frame length
48    #[display("Invalid control frame length ({})", _0)]
49    InvalidLength(#[error(not(source))] usize),
50
51    // TODO(semver-major): use in Parser::try_parse_close_payload
52    //
53    // /// Invalid close status code.
54    // #[display("Invalid close status code ({})", _0)]
55    // InvalidCloseCode(#[error(not(source))] u16),
56    //
57    // /// Invalid UTF-8 close reason.
58    // #[display("Invalid UTF-8 close reason")]
59    // InvalidCloseReason,
60    //
61    /// Bad opcode.
62    #[display("Bad opcode")]
63    BadOpCode,
64
65    /// A payload length is invalid, or exceeds the configured size limit.
66    ///
67    /// A 64-bit length with its most significant bit set is invalid (RFC 6455 §5.2), as is one that
68    /// does not fit in `usize`, or one whose addition to the frame offset overflows.
69    #[display("Payload length is invalid or exceeds the size limit")]
70    Overflow,
71
72    /// Continuation has not started.
73    #[display("Continuation has not started")]
74    ContinuationNotStarted,
75
76    /// Received new continuation but it is already started.
77    #[display("Received new continuation but it has already started")]
78    ContinuationStarted,
79
80    /// Unknown continuation fragment.
81    #[display("Unknown continuation fragment: {}", _0)]
82    ContinuationFragment(#[error(not(source))] OpCode),
83
84    /// I/O error.
85    #[display("I/O error: {}", _0)]
86    Io(io::Error),
87}
88
89/// WebSocket handshake errors
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)]
91pub enum HandshakeError {
92    /// Only get method is allowed.
93    #[display("method not allowed")]
94    GetMethodRequired,
95
96    /// Upgrade header if not set to WebSocket.
97    #[display("WebSocket upgrade is expected")]
98    NoWebsocketUpgrade,
99
100    /// Connection header is not set to upgrade.
101    #[display("connection upgrade is expected")]
102    NoConnectionUpgrade,
103
104    /// WebSocket version header is not set.
105    #[display("WebSocket version header is required")]
106    NoVersionHeader,
107
108    /// Unsupported WebSocket version.
109    #[display("unsupported WebSocket version")]
110    UnsupportedVersion,
111
112    /// WebSocket key is not set or wrong.
113    #[display("unknown WebSocket key")]
114    BadWebsocketKey,
115}
116
117impl From<HandshakeError> for Response<BoxBody> {
118    fn from(err: HandshakeError) -> Self {
119        match err {
120            HandshakeError::GetMethodRequired => {
121                let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
122                #[allow(clippy::declare_interior_mutable_const)]
123                const HV_GET: HeaderValue = HeaderValue::from_static("GET");
124                res.headers_mut().insert(header::ALLOW, HV_GET);
125                res
126            }
127
128            HandshakeError::NoWebsocketUpgrade => {
129                let mut res = Response::bad_request();
130                res.head_mut().reason = Some("No WebSocket Upgrade header found");
131                res
132            }
133
134            HandshakeError::NoConnectionUpgrade => {
135                let mut res = Response::bad_request();
136                res.head_mut().reason = Some("No Connection upgrade");
137                res
138            }
139
140            HandshakeError::NoVersionHeader => {
141                let mut res = Response::bad_request();
142                res.head_mut().reason = Some("WebSocket version header is required");
143                res
144            }
145
146            HandshakeError::UnsupportedVersion => {
147                #[allow(clippy::declare_interior_mutable_const)]
148                const HV_13: HeaderValue = HeaderValue::from_static("13");
149                #[allow(clippy::declare_interior_mutable_const)]
150                const HV_WEBSOCKET: HeaderValue = HeaderValue::from_static("websocket");
151                let mut res = Response::new(StatusCode::UPGRADE_REQUIRED);
152                res.headers_mut().insert(header::UPGRADE, HV_WEBSOCKET);
153                res.headers_mut()
154                    .insert(header::SEC_WEBSOCKET_VERSION, HV_13);
155                res.head_mut().reason = Some("Unsupported WebSocket version");
156                res
157            }
158
159            HandshakeError::BadWebsocketKey => {
160                let mut res = Response::bad_request();
161                res.head_mut().reason = Some("Handshake error");
162                res
163            }
164        }
165    }
166}
167
168impl From<&HandshakeError> for Response<BoxBody> {
169    fn from(err: &HandshakeError) -> Self {
170        (*err).into()
171    }
172}
173
174/// Verify WebSocket handshake request and create handshake response.
175pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
176    verify_handshake(req)?;
177    Ok(handshake_response(req))
178}
179
180/// Verify WebSocket handshake request.
181pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
182    // WebSocket accepts only GET
183    if req.method != Method::GET {
184        return Err(HandshakeError::GetMethodRequired);
185    }
186
187    // Check for "UPGRADE" to WebSocket header
188    let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
189        if let Ok(s) = hdr.to_str() {
190            s.to_ascii_lowercase().contains("websocket")
191        } else {
192            false
193        }
194    } else {
195        false
196    };
197    if !has_hdr {
198        return Err(HandshakeError::NoWebsocketUpgrade);
199    }
200
201    // Upgrade connection
202    if !req.upgrade() {
203        return Err(HandshakeError::NoConnectionUpgrade);
204    }
205
206    // check supported version
207    if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
208        return Err(HandshakeError::NoVersionHeader);
209    }
210    let supported_ver = {
211        if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
212            hdr == "13"
213        } else {
214            false
215        }
216    };
217    if !supported_ver {
218        return Err(HandshakeError::UnsupportedVersion);
219    }
220
221    // check client handshake for validity (RFC 6455 §4.2.1 clause 5)
222    let key = match req.headers().get(header::SEC_WEBSOCKET_KEY) {
223        Some(key) => key.as_bytes(),
224        None => return Err(HandshakeError::BadWebsocketKey),
225    };
226
227    let mut decoded = [0u8; 16];
228    if key.len() != 24 || BASE64_STANDARD.decode_slice(key, &mut decoded) != Ok(16) {
229        return Err(HandshakeError::BadWebsocketKey);
230    }
231    Ok(())
232}
233
234/// Create WebSocket handshake response.
235///
236/// This function returns handshake `Response`, ready to send to peer.
237pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
238    let key = {
239        let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
240        proto::hash_key(key.as_ref())
241    };
242
243    Response::build(StatusCode::SWITCHING_PROTOCOLS)
244        .upgrade("websocket")
245        .insert_header((
246            header::SEC_WEBSOCKET_ACCEPT,
247            // key is known to be header value safe ascii
248            HeaderValue::from_bytes(&key).unwrap(),
249        ))
250        .take()
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use crate::{header, test::TestRequest};
257
258    #[test]
259    fn test_handshake() {
260        let req = TestRequest::default().method(Method::POST).finish();
261        assert_eq!(
262            HandshakeError::GetMethodRequired,
263            verify_handshake(req.head()).unwrap_err(),
264        );
265
266        let req = TestRequest::default().finish();
267        assert_eq!(
268            HandshakeError::NoWebsocketUpgrade,
269            verify_handshake(req.head()).unwrap_err(),
270        );
271
272        let req = TestRequest::default()
273            .insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
274            .finish();
275        assert_eq!(
276            HandshakeError::NoWebsocketUpgrade,
277            verify_handshake(req.head()).unwrap_err(),
278        );
279
280        let req = TestRequest::default()
281            .insert_header((
282                header::UPGRADE,
283                header::HeaderValue::from_static("websocket"),
284            ))
285            .finish();
286        assert_eq!(
287            HandshakeError::NoConnectionUpgrade,
288            verify_handshake(req.head()).unwrap_err(),
289        );
290
291        let req = TestRequest::default()
292            .insert_header((
293                header::UPGRADE,
294                header::HeaderValue::from_static("websocket"),
295            ))
296            .insert_header((
297                header::CONNECTION,
298                header::HeaderValue::from_static("upgrade"),
299            ))
300            .finish();
301        assert_eq!(
302            HandshakeError::NoVersionHeader,
303            verify_handshake(req.head()).unwrap_err(),
304        );
305
306        let req = TestRequest::default()
307            .insert_header((
308                header::UPGRADE,
309                header::HeaderValue::from_static("websocket"),
310            ))
311            .insert_header((
312                header::CONNECTION,
313                header::HeaderValue::from_static("upgrade"),
314            ))
315            .insert_header((
316                header::SEC_WEBSOCKET_VERSION,
317                header::HeaderValue::from_static("5"),
318            ))
319            .finish();
320        assert_eq!(
321            HandshakeError::UnsupportedVersion,
322            verify_handshake(req.head()).unwrap_err(),
323        );
324
325        for &ver in &["7", "8"] {
326            let req = TestRequest::default()
327                .insert_header((
328                    header::UPGRADE,
329                    header::HeaderValue::from_static("websocket"),
330                ))
331                .insert_header((
332                    header::CONNECTION,
333                    header::HeaderValue::from_static("upgrade"),
334                ))
335                .insert_header((
336                    header::SEC_WEBSOCKET_VERSION,
337                    header::HeaderValue::from_static(ver),
338                ))
339                .finish();
340            assert_eq!(
341                HandshakeError::UnsupportedVersion,
342                verify_handshake(req.head()).unwrap_err(),
343            );
344        }
345
346        let req = TestRequest::default()
347            .insert_header((
348                header::UPGRADE,
349                header::HeaderValue::from_static("websocket"),
350            ))
351            .insert_header((
352                header::CONNECTION,
353                header::HeaderValue::from_static("upgrade"),
354            ))
355            .insert_header((
356                header::SEC_WEBSOCKET_VERSION,
357                header::HeaderValue::from_static("13"),
358            ))
359            .finish();
360        assert_eq!(
361            HandshakeError::BadWebsocketKey,
362            verify_handshake(req.head()).unwrap_err(),
363        );
364
365        let req = TestRequest::default()
366            .insert_header((
367                header::UPGRADE,
368                header::HeaderValue::from_static("websocket"),
369            ))
370            .insert_header((
371                header::CONNECTION,
372                header::HeaderValue::from_static("keep-alive"),
373            ))
374            .append_header((header::CONNECTION, "Upgrade"))
375            .insert_header((
376                header::SEC_WEBSOCKET_VERSION,
377                header::HeaderValue::from_static("13"),
378            ))
379            .insert_header((
380                header::SEC_WEBSOCKET_KEY,
381                header::HeaderValue::from_static("dGhlIHNhbXBsZSBub25jZQ=="),
382            ))
383            .finish();
384        assert_eq!(
385            StatusCode::SWITCHING_PROTOCOLS,
386            handshake(req.head()).unwrap().finish().status()
387        );
388
389        // Invalid key length / non-16-byte base64 nonce
390        let req = TestRequest::default()
391            .insert_header((
392                header::UPGRADE,
393                header::HeaderValue::from_static("websocket"),
394            ))
395            .insert_header((
396                header::CONNECTION,
397                header::HeaderValue::from_static("upgrade"),
398            ))
399            .insert_header((
400                header::SEC_WEBSOCKET_VERSION,
401                header::HeaderValue::from_static("13"),
402            ))
403            .insert_header((
404                header::SEC_WEBSOCKET_KEY,
405                header::HeaderValue::from_static("13"),
406            ))
407            .finish();
408        assert_eq!(
409            HandshakeError::BadWebsocketKey,
410            verify_handshake(req.head()).unwrap_err(),
411        );
412    }
413
414    #[test]
415    fn test_ws_error_http_response() {
416        let resp: Response<BoxBody> = HandshakeError::GetMethodRequired.into();
417        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
418        let resp: Response<BoxBody> = HandshakeError::NoWebsocketUpgrade.into();
419        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
420        let resp: Response<BoxBody> = HandshakeError::NoConnectionUpgrade.into();
421        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
422        let resp: Response<BoxBody> = HandshakeError::NoVersionHeader.into();
423        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
424        let resp: Response<BoxBody> = HandshakeError::UnsupportedVersion.into();
425        assert_eq!(resp.status(), StatusCode::UPGRADE_REQUIRED);
426        assert!(!resp.upgrade());
427        assert_eq!(resp.headers().get(header::UPGRADE).unwrap(), "websocket");
428        assert_eq!(
429            resp.headers().get(header::SEC_WEBSOCKET_VERSION).unwrap(),
430            "13"
431        );
432        let resp: Response<BoxBody> = HandshakeError::BadWebsocketKey.into();
433        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
434    }
435}