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