Skip to content

Commit 4e115f4

Browse files
divybotlittledivy
andcommitted
fix: handle manual masks in fragment collector
Co-Authored-By: Divy Srivastava <me@littledivy.com>
1 parent 5f9c62c commit 4e115f4

4 files changed

Lines changed: 139 additions & 4 deletions

File tree

src/fragment.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,17 @@ impl Fragments {
214214

215215
pub fn accumulate<'f>(
216216
&mut self,
217-
frame: Frame<'f>,
217+
mut frame: Frame<'f>,
218218
) -> Result<Option<Frame<'f>>, WebSocketError> {
219219
match frame.opcode {
220220
OpCode::Text | OpCode::Binary => {
221221
if frame.fin {
222222
if self.fragments.is_some() {
223223
return Err(WebSocketError::InvalidFragment);
224224
}
225-
return Ok(Some(Frame::new(true, frame.opcode, None, frame.payload)));
225+
return Ok(Some(frame));
226226
} else {
227+
frame.unmask();
227228
self.fragments = match frame.opcode {
228229
OpCode::Text => match utf8::decode(&frame.payload) {
229230
Ok(text) => Some(Fragment::Text(None, text.as_bytes().to_vec())),
@@ -249,6 +250,7 @@ impl Fragments {
249250
return Err(WebSocketError::InvalidContinuationFrame);
250251
}
251252
Some(Fragment::Text(data, input)) => {
253+
frame.unmask();
252254
let mut tail = &frame.payload[..];
253255
if let Some(mut incomplete) = data.take() {
254256
if let Some((result, rest)) =
@@ -296,6 +298,7 @@ impl Fragments {
296298
}
297299
}
298300
Some(Fragment::Binary(data)) => {
301+
frame.unmask();
299302
data.extend_from_slice(&frame.payload);
300303
if frame.fin {
301304
return Ok(Some(Frame::new(

src/frame.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,15 @@ impl<'f> Frame<'f> {
261261
///
262262
/// Note: By default, the frame payload is unmasked by `WebSocket::read_frame`.
263263
pub fn unmask(&mut self) {
264-
if let Some(mask) = self.mask {
264+
if let Some(mask) = self.mask.take() {
265265
crate::mask::unmask(self.payload.to_mut(), mask);
266266
}
267267
}
268268

269+
pub(crate) fn is_masked(&self) -> bool {
270+
self.mask.is_some()
271+
}
272+
269273
/// Formats the frame header into the head buffer. Returns the size of the length field.
270274
///
271275
/// # Panics

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ impl ReadHalf {
646646
(Ok(None), Some(Frame::pong(frame.payload)))
647647
}
648648
OpCode::Text => {
649-
if frame.fin && !frame.is_utf8() {
649+
if frame.fin && !frame.is_masked() && !frame.is_utf8() {
650650
(Err(WebSocketError::InvalidUTF8), None)
651651
} else {
652652
(Ok(Some(frame)), None)
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2023-2026 Divy Srivastava <dj.srivastava23@gmail.com>
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
use anyhow::Result;
15+
use fastwebsockets::FragmentCollector;
16+
use fastwebsockets::Frame;
17+
use fastwebsockets::OpCode;
18+
use fastwebsockets::Role;
19+
use fastwebsockets::WebSocket;
20+
use tokio::io::AsyncWriteExt;
21+
use tokio::io::DuplexStream;
22+
23+
async fn write_masked_frame(
24+
stream: &mut DuplexStream,
25+
fin: bool,
26+
opcode: OpCode,
27+
mask: [u8; 4],
28+
payload: &[u8],
29+
) -> Result<()> {
30+
let mut masked = payload.to_vec();
31+
fastwebsockets::unmask(&mut masked, mask);
32+
33+
let mut frame = Frame::new(fin, opcode, Some(mask), masked.into());
34+
let mut buf = Vec::new();
35+
stream.write_all(frame.write(&mut buf)).await?;
36+
Ok(())
37+
}
38+
39+
#[tokio::test]
40+
async fn unfragmented_masked_text_can_be_manually_unmasked() -> Result<()> {
41+
let (mut client, server) = tokio::io::duplex(1024);
42+
let mut ws = WebSocket::after_handshake(server, Role::Server);
43+
ws.set_auto_apply_mask(false);
44+
let mut ws = FragmentCollector::new(ws);
45+
46+
write_masked_frame(
47+
&mut client,
48+
true,
49+
OpCode::Text,
50+
[0xff, 0xff, 0xff, 0xff],
51+
b"hello",
52+
)
53+
.await?;
54+
55+
let mut frame = ws.read_frame().await?;
56+
assert_eq!(frame.opcode, OpCode::Text);
57+
assert_ne!(&frame.payload[..], b"hello");
58+
59+
frame.unmask();
60+
assert_eq!(&frame.payload[..], b"hello");
61+
62+
Ok(())
63+
}
64+
65+
#[tokio::test]
66+
async fn fragmented_masked_text_is_unmasked_before_validation() -> Result<()> {
67+
let (mut client, server) = tokio::io::duplex(1024);
68+
let mut ws = WebSocket::after_handshake(server, Role::Server);
69+
ws.set_auto_apply_mask(false);
70+
let mut ws = FragmentCollector::new(ws);
71+
72+
write_masked_frame(
73+
&mut client,
74+
false,
75+
OpCode::Text,
76+
[0xff, 0xff, 0xff, 0xff],
77+
b"hello ",
78+
)
79+
.await?;
80+
write_masked_frame(
81+
&mut client,
82+
true,
83+
OpCode::Continuation,
84+
[0x80, 0x80, 0x80, 0x80],
85+
b"world",
86+
)
87+
.await?;
88+
89+
let mut frame = ws.read_frame().await?;
90+
assert_eq!(frame.opcode, OpCode::Text);
91+
assert_eq!(&frame.payload[..], b"hello world");
92+
93+
frame.unmask();
94+
assert_eq!(&frame.payload[..], b"hello world");
95+
96+
Ok(())
97+
}
98+
99+
#[tokio::test]
100+
async fn fragmented_masked_binary_is_unmasked() -> Result<()> {
101+
let (mut client, server) = tokio::io::duplex(1024);
102+
let mut ws = WebSocket::after_handshake(server, Role::Server);
103+
ws.set_auto_apply_mask(false);
104+
let mut ws = FragmentCollector::new(ws);
105+
106+
write_masked_frame(
107+
&mut client,
108+
false,
109+
OpCode::Binary,
110+
[0x7f, 0x7f, 0x7f, 0x7f],
111+
&[0, 1, 2, 3],
112+
)
113+
.await?;
114+
write_masked_frame(
115+
&mut client,
116+
true,
117+
OpCode::Continuation,
118+
[0x55, 0x55, 0x55, 0x55],
119+
&[4, 5, 6, 7],
120+
)
121+
.await?;
122+
123+
let frame = ws.read_frame().await?;
124+
assert_eq!(frame.opcode, OpCode::Binary);
125+
assert_eq!(&frame.payload[..], &[0, 1, 2, 3, 4, 5, 6, 7]);
126+
127+
Ok(())
128+
}

0 commit comments

Comments
 (0)