Skip to content

Commit 84eaba9

Browse files
BobaFettersdaniel-abramov
authored andcommitted
Fixing issue with non-ASCII header values
Fixing an issue where non-ASCII header values cause websocket handshake to fail.
1 parent 236c121 commit 84eaba9

File tree

1 file changed

+56
-10
lines changed

1 file changed

+56
-10
lines changed

src/handshake/client.rs

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,18 +185,15 @@ pub fn generate_request(mut request: Request) -> Result<(Vec<u8>, String)> {
185185
name = "Origin";
186186
}
187187

188-
writeln!(
189-
req,
190-
"{}: {}\r",
191-
name,
192-
v.to_str().map_err(|err| {
193-
Error::Utf8(format!("{err} for header name '{name}' with value: {v:?}"))
194-
})?
195-
)
196-
.unwrap();
188+
// Write header as raw bytes to support non-ASCII values.
189+
// HTTP headers are defined as octets (RFC 7230), not UTF-8 strings.
190+
req.extend_from_slice(name.as_bytes());
191+
req.extend_from_slice(b": ");
192+
req.extend_from_slice(v.as_bytes());
193+
req.extend_from_slice(b"\r\n");
197194
}
198195

199-
writeln!(req, "\r").unwrap();
196+
req.extend_from_slice(b"\r\n");
200197
trace!("Request: {:?}", String::from_utf8_lossy(&req));
201198
Ok((req, key))
202199
}
@@ -408,4 +405,53 @@ mod tests {
408405
let request = http::Request::builder().method("GET").body(()).unwrap();
409406
assert!(generate_request(request).is_err());
410407
}
408+
409+
#[test]
410+
fn request_with_non_ascii_header() {
411+
use http::header::HeaderValue;
412+
413+
let mut request = "ws://localhost/path".into_client_request().unwrap();
414+
415+
// Add a header with non-ASCII value (UTF-8 encoded "Montréal")
416+
let non_ascii_value = HeaderValue::from_bytes(b"Montr\xc3\xa9al").unwrap();
417+
request.headers_mut().insert("X-City", non_ascii_value);
418+
419+
// This should succeed, not fail with UTF-8 error
420+
let result = generate_request(request);
421+
assert!(result.is_ok(), "generate_request should accept non-ASCII header values");
422+
423+
let (req_bytes, _key) = result.unwrap();
424+
425+
// Verify the complete header with non-ASCII value is preserved in the output
426+
let expected_header = b"x-city: Montr\xc3\xa9al\r\n";
427+
assert!(
428+
req_bytes.windows(expected_header.len()).any(|window| window == expected_header),
429+
"Request should contain the complete non-ASCII header value"
430+
);
431+
}
432+
433+
#[test]
434+
fn request_with_latin1_header() {
435+
use http::header::HeaderValue;
436+
437+
let mut request = "ws://localhost/path".into_client_request().unwrap();
438+
439+
// Add a header with ISO-8859-1 (Latin-1) encoded value
440+
// This is NOT valid UTF-8 but is valid for HTTP headers
441+
let latin1_value = HeaderValue::from_bytes(b"caf\xe9").unwrap(); // "café" in Latin-1
442+
request.headers_mut().insert("X-Test", latin1_value);
443+
444+
// This should succeed
445+
let result = generate_request(request);
446+
assert!(result.is_ok(), "generate_request should accept Latin-1 header values");
447+
448+
let (req_bytes, _key) = result.unwrap();
449+
450+
// Verify the raw bytes are preserved in the output
451+
let expected_header = b"x-test: caf\xe9\r\n";
452+
assert!(
453+
req_bytes.windows(expected_header.len()).any(|window| window == expected_header),
454+
"Request should preserve the raw Latin-1 bytes"
455+
);
456+
}
411457
}

0 commit comments

Comments
 (0)