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 reached size limit.
66    #[display("Payload reached size limit")]
67    Overflow,
68
69    /// Continuation has not started.
70    #[display("Continuation has not started")]
71    ContinuationNotStarted,
72
73    /// Received new continuation but it is already started.
74    #[display("Received new continuation but it has already started")]
75    ContinuationStarted,
76
77    /// Unknown continuation fragment.
78    #[display("Unknown continuation fragment: {}", _0)]
79    ContinuationFragment(#[error(not(source))] OpCode),
80
81    /// I/O error.
82    #[display("I/O error: {}", _0)]
83    Io(io::Error),
84}
85
86/// WebSocket handshake errors
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)]
88pub enum HandshakeError {
89    /// Only get method is allowed.
90    #[display("method not allowed")]
91    GetMethodRequired,
92
93    /// Upgrade header if not set to WebSocket.
94    #[display("WebSocket upgrade is expected")]
95    NoWebsocketUpgrade,
96
97    /// Connection header is not set to upgrade.
98    #[display("connection upgrade is expected")]
99    NoConnectionUpgrade,
100
101    /// WebSocket version header is not set.
102    #[display("WebSocket version header is required")]
103    NoVersionHeader,
104
105    /// Unsupported WebSocket version.
106    #[display("unsupported WebSocket version")]
107    UnsupportedVersion,
108
109    /// WebSocket key is not set or wrong.
110    #[display("unknown WebSocket key")]
111    BadWebsocketKey,
112}
113
114impl From<HandshakeError> for Response<BoxBody> {
115    fn from(err: HandshakeError) -> Self {
116        match err {
117            HandshakeError::GetMethodRequired => {
118                let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
119                #[allow(clippy::declare_interior_mutable_const)]
120                const HV_GET: HeaderValue = HeaderValue::from_static("GET");
121                res.headers_mut().insert(header::ALLOW, HV_GET);
122                res
123            }
124
125            HandshakeError::NoWebsocketUpgrade => {
126                let mut res = Response::bad_request();
127                res.head_mut().reason = Some("No WebSocket Upgrade header found");
128                res
129            }
130
131            HandshakeError::NoConnectionUpgrade => {
132                let mut res = Response::bad_request();
133                res.head_mut().reason = Some("No Connection upgrade");
134                res
135            }
136
137            HandshakeError::NoVersionHeader => {
138                let mut res = Response::bad_request();
139                res.head_mut().reason = Some("WebSocket version header is required");
140                res
141            }
142
143            HandshakeError::UnsupportedVersion => {
144                #[allow(clippy::declare_interior_mutable_const)]
145                const HV_13: HeaderValue = HeaderValue::from_static("13");
146                #[allow(clippy::declare_interior_mutable_const)]
147                const HV_WEBSOCKET: HeaderValue = HeaderValue::from_static("websocket");
148                let mut res = Response::new(StatusCode::UPGRADE_REQUIRED);
149                res.headers_mut().insert(header::UPGRADE, HV_WEBSOCKET);
150                res.headers_mut()
151                    .insert(header::SEC_WEBSOCKET_VERSION, HV_13);
152                res.head_mut().reason = Some("Unsupported WebSocket version");
153                res
154            }
155
156            HandshakeError::BadWebsocketKey => {
157                let mut res = Response::bad_request();
158                res.head_mut().reason = Some("Handshake error");
159                res
160            }
161        }
162    }
163}
164
165impl From<&HandshakeError> for Response<BoxBody> {
166    fn from(err: &HandshakeError) -> Self {
167        (*err).into()
168    }
169}
170
171/// Verify WebSocket handshake request and create handshake response.
172pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
173    verify_handshake(req)?;
174    Ok(handshake_response(req))
175}
176
177/// Verify WebSocket handshake request.
178pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
179    // WebSocket accepts only GET
180    if req.method != Method::GET {
181        return Err(HandshakeError::GetMethodRequired);
182    }
183
184    // Check for "UPGRADE" to WebSocket header
185    let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
186        if let Ok(s) = hdr.to_str() {
187            s.to_ascii_lowercase().contains("websocket")
188        } else {
189            false
190        }
191    } else {
192        false
193    };
194    if !has_hdr {
195        return Err(HandshakeError::NoWebsocketUpgrade);
196    }
197
198    // Upgrade connection
199    if !req.upgrade() {
200        return Err(HandshakeError::NoConnectionUpgrade);
201    }
202
203    // check supported version
204    if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
205        return Err(HandshakeError::NoVersionHeader);
206    }
207    let supported_ver = {
208        if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
209            hdr == "13"
210        } else {
211            false
212        }
213    };
214    if !supported_ver {
215        return Err(HandshakeError::UnsupportedVersion);
216    }
217
218    // check client handshake for validity (RFC 6455 ยง4.2.1 clause 5)
219    let key = match req.headers().get(header::SEC_WEBSOCKET_KEY) {
220        Some(key) => key.as_bytes(),
221        None => return Err(HandshakeError::BadWebsocketKey),
222    };
223
224    let mut decoded = [0u8; 16];
225    if key.len() != 24 || BASE64_STANDARD.decode_slice(key, &mut decoded) != Ok(16) {
226        return Err(HandshakeError::BadWebsocketKey);
227    }
228    Ok(())
229}
230
231/// Create WebSocket handshake response.
232///
233/// This function returns handshake `Response`, ready to send to peer.
234pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
235    let key = {
236        let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
237        proto::hash_key(key.as_ref())
238    };
239
240    Response::build(StatusCode::SWITCHING_PROTOCOLS)
241        .upgrade("websocket")
242        .insert_header((
243            header::SEC_WEBSOCKET_ACCEPT,
244            // key is known to be header value safe ascii
245            HeaderValue::from_bytes(&key).unwrap(),
246        ))
247        .take()
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::{header, test::TestRequest};
254
255    #[test]
256    fn test_handshake() {
257        let req = TestRequest::default().method(Method::POST).finish();
258        assert_eq!(
259            HandshakeError::GetMethodRequired,
260            verify_handshake(req.head()).unwrap_err(),
261        );
262
263        let req = TestRequest::default().finish();
264        assert_eq!(
265            HandshakeError::NoWebsocketUpgrade,
266            verify_handshake(req.head()).unwrap_err(),
267        );
268
269        let req = TestRequest::default()
270            .insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
271            .finish();
272        assert_eq!(
273            HandshakeError::NoWebsocketUpgrade,
274            verify_handshake(req.head()).unwrap_err(),
275        );
276
277        let req = TestRequest::default()
278            .insert_header((
279                header::UPGRADE,
280                header::HeaderValue::from_static("websocket"),
281            ))
282            .finish();
283        assert_eq!(
284            HandshakeError::NoConnectionUpgrade,
285            verify_handshake(req.head()).unwrap_err(),
286        );
287
288        let req = TestRequest::default()
289            .insert_header((
290                header::UPGRADE,
291                header::HeaderValue::from_static("websocket"),
292            ))
293            .insert_header((
294                header::CONNECTION,
295                header::HeaderValue::from_static("upgrade"),
296            ))
297            .finish();
298        assert_eq!(
299            HandshakeError::NoVersionHeader,
300            verify_handshake(req.head()).unwrap_err(),
301        );
302
303        let req = TestRequest::default()
304            .insert_header((
305                header::UPGRADE,
306                header::HeaderValue::from_static("websocket"),
307            ))
308            .insert_header((
309                header::CONNECTION,
310                header::HeaderValue::from_static("upgrade"),
311            ))
312            .insert_header((
313                header::SEC_WEBSOCKET_VERSION,
314                header::HeaderValue::from_static("5"),
315            ))
316            .finish();
317        assert_eq!(
318            HandshakeError::UnsupportedVersion,
319            verify_handshake(req.head()).unwrap_err(),
320        );
321
322        for &ver in &["7", "8"] {
323            let req = TestRequest::default()
324                .insert_header((
325                    header::UPGRADE,
326                    header::HeaderValue::from_static("websocket"),
327                ))
328                .insert_header((
329                    header::CONNECTION,
330                    header::HeaderValue::from_static("upgrade"),
331                ))
332                .insert_header((
333                    header::SEC_WEBSOCKET_VERSION,
334                    header::HeaderValue::from_static(ver),
335                ))
336                .finish();
337            assert_eq!(
338                HandshakeError::UnsupportedVersion,
339                verify_handshake(req.head()).unwrap_err(),
340            );
341        }
342
343        let req = TestRequest::default()
344            .insert_header((
345                header::UPGRADE,
346                header::HeaderValue::from_static("websocket"),
347            ))
348            .insert_header((
349                header::CONNECTION,
350                header::HeaderValue::from_static("upgrade"),
351            ))
352            .insert_header((
353                header::SEC_WEBSOCKET_VERSION,
354                header::HeaderValue::from_static("13"),
355            ))
356            .finish();
357        assert_eq!(
358            HandshakeError::BadWebsocketKey,
359            verify_handshake(req.head()).unwrap_err(),
360        );
361
362        let req = TestRequest::default()
363            .insert_header((
364                header::UPGRADE,
365                header::HeaderValue::from_static("websocket"),
366            ))
367            .insert_header((
368                header::CONNECTION,
369                header::HeaderValue::from_static("keep-alive"),
370            ))
371            .append_header((header::CONNECTION, "Upgrade"))
372            .insert_header((
373                header::SEC_WEBSOCKET_VERSION,
374                header::HeaderValue::from_static("13"),
375            ))
376            .insert_header((
377                header::SEC_WEBSOCKET_KEY,
378                header::HeaderValue::from_static("dGhlIHNhbXBsZSBub25jZQ=="),
379            ))
380            .finish();
381        assert_eq!(
382            StatusCode::SWITCHING_PROTOCOLS,
383            handshake(req.head()).unwrap().finish().status()
384        );
385
386        // Invalid key length / non-16-byte base64 nonce
387        let req = TestRequest::default()
388            .insert_header((
389                header::UPGRADE,
390                header::HeaderValue::from_static("websocket"),
391            ))
392            .insert_header((
393                header::CONNECTION,
394                header::HeaderValue::from_static("upgrade"),
395            ))
396            .insert_header((
397                header::SEC_WEBSOCKET_VERSION,
398                header::HeaderValue::from_static("13"),
399            ))
400            .insert_header((
401                header::SEC_WEBSOCKET_KEY,
402                header::HeaderValue::from_static("13"),
403            ))
404            .finish();
405        assert_eq!(
406            HandshakeError::BadWebsocketKey,
407            verify_handshake(req.head()).unwrap_err(),
408        );
409    }
410
411    #[test]
412    fn test_ws_error_http_response() {
413        let resp: Response<BoxBody> = HandshakeError::GetMethodRequired.into();
414        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
415        let resp: Response<BoxBody> = HandshakeError::NoWebsocketUpgrade.into();
416        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
417        let resp: Response<BoxBody> = HandshakeError::NoConnectionUpgrade.into();
418        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
419        let resp: Response<BoxBody> = HandshakeError::NoVersionHeader.into();
420        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
421        let resp: Response<BoxBody> = HandshakeError::UnsupportedVersion.into();
422        assert_eq!(resp.status(), StatusCode::UPGRADE_REQUIRED);
423        assert!(!resp.upgrade());
424        assert_eq!(resp.headers().get(header::UPGRADE).unwrap(), "websocket");
425        assert_eq!(
426            resp.headers().get(header::SEC_WEBSOCKET_VERSION).unwrap(),
427            "13"
428        );
429        let resp: Response<BoxBody> = HandshakeError::BadWebsocketKey.into();
430        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
431    }
432}