Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 61 additions & 14 deletions tonic-web/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,20 +418,17 @@ fn decode_trailers_frame(mut buf: Bytes) -> Result<Option<HeaderMap>, Status> {
}

for trailer in trailers {
let mut s = trailer.split(|b| b == &b':');
let key = s
.next()
.ok_or_else(|| Status::internal("trailers couldn't parse key"))?;
let value = s
.next()
.ok_or_else(|| Status::internal("trailers couldn't parse value"))?;

let value = value
.split(|b| b == &b'\r')
.next()
.ok_or_else(|| Status::internal("trailers was not escaped"))?
.strip_prefix(b" ")
.unwrap_or(value);
let Some((key, value)) = trailer
.iter()
.position(|b| *b == b':')
.map(|pos| trailer.split_at(pos))
else {
return Err(Status::internal("trailers couldn't parse key/value"));
};

// Skip the ':' separator and trim leading OWS (spaces/tabs) from the value
let value = &value[1..]; // skip ':'
let value = trim_ascii_start(value);

let header_key = HeaderName::try_from(key)
.map_err(|e| Status::internal(format!("Unable to parse HeaderName: {e}")))?;
Expand All @@ -443,6 +440,14 @@ fn decode_trailers_frame(mut buf: Bytes) -> Result<Option<HeaderMap>, Status> {
Ok(Some(map))
}

fn trim_ascii_start(bytes: &[u8]) -> &[u8] {
let start = bytes
.iter()
.position(|b| !b.is_ascii_whitespace())
.unwrap_or(bytes.len());
&bytes[start..]
}

fn make_trailers_frame(trailers: HeaderMap) -> Bytes {
let trailers = encode_trailers(trailers);
let len = trailers.len();
Expand Down Expand Up @@ -659,4 +664,46 @@ mod tests {

assert_eq!(trailers, expected);
}

#[test]
fn decode_trailers_space_after_colon() {
// connect-rpc and standard HTTP use "key: value" (space after colon)
let trailers_bytes = b"grpc-status: 0\r\ngrpc-message: this is a message\r\n";
let len = trailers_bytes.len();

let mut frame = BytesMut::new();
frame.put_u8(GRPC_WEB_TRAILERS_BIT);
frame.put_u32(len as u32);
frame.put_slice(&trailers_bytes[..]);

let map = decode_trailers_frame(frame.freeze()).unwrap().unwrap();

let mut expected = HeaderMap::new();
expected.insert(Status::GRPC_STATUS, HeaderValue::from_static("0"));
expected.insert(
Status::GRPC_MESSAGE,
HeaderValue::from_static("this is a message"),
);

assert_eq!(map, expected);
}

#[test]
fn decode_trailers_value_with_colons() {
let trailers_bytes = b"grpc-status: 0\r\ngrpc-message: error: something: went wrong\r\n";
let len = trailers_bytes.len();

let mut frame = BytesMut::new();
frame.put_u8(GRPC_WEB_TRAILERS_BIT);
frame.put_u32(len as u32);
frame.put_slice(&trailers_bytes[..]);

let map = decode_trailers_frame(frame.freeze()).unwrap().unwrap();

assert_eq!(map.get("grpc-status").unwrap(), "0");
assert_eq!(
map.get("grpc-message").unwrap(),
"error: something: went wrong"
);
}
}
Loading