From 06a4c338fc110f48e7b710d0b7cb9dd0b2333d9d Mon Sep 17 00:00:00 2001 From: Adolanium <94890352+Adolanium@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:45:22 +0200 Subject: [PATCH] fix(protobuf): exclude trailing whitespace from gRPC-web trailer values (#7069) RFC 9110 section 5.5 excludes leading and trailing whitespace from a field value. The gRPC-web trailer parser stripped only leading SP/HTAB, so 'grpc-status: 0 ' was reported as '0 '. Trailing SP/HTAB is now excluded too; internal whitespace and the examined byte count are unchanged. --- .../protobuf/ProtobufBodyTrailer.test.ts | 19 +++++++++++++++++++ .../history/protobuf/ProtobufTrailers.ts | 6 +++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts b/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts index 7989c1a673..9f41755773 100644 --- a/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts +++ b/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts @@ -73,6 +73,25 @@ it("retains allowed whitespace, visible text, and valid UTF-8 obs-text", async ( expect(result.examined_bytes).toBe(5 + Buffer.byteLength(text)); }); +it("excludes trailing whitespace from trailer field values", async () => { + const text = + "grpc-status: 0 \r\ngrpc-message: alpha\tbeta \t\r\nx-empty: \t\r\n"; + const result = await decodeProtobufBody( + trailerFrame(text), + "grpc-web", + source, + same, + new ProtobufBudget(), + ); + expect(result.status).toBe("complete"); + expect(result.frames[0]?.trailers).toEqual([ + { name: "grpc-status", value: "0" }, + { name: "grpc-message", value: "alpha\tbeta" }, + { name: "x-empty", value: "" }, + ]); + expect(result.examined_bytes).toBe(5 + Buffer.byteLength(text)); +}); + it.each(["grpc-web", "grpc-web-text"] as const)( "keeps valid trailer fields around an opaque non-UTF-8 value in %s framing", async (framing) => { diff --git a/src/browser/history/protobuf/ProtobufTrailers.ts b/src/browser/history/protobuf/ProtobufTrailers.ts index 1f382f0b27..08a651f63d 100644 --- a/src/browser/history/protobuf/ProtobufTrailers.ts +++ b/src/browser/history/protobuf/ProtobufTrailers.ts @@ -84,9 +84,13 @@ export const parseGrpcWebTrailers = ( "gRPC-web trailer field value contains a forbidden control character", }); } + // RFC 9110 section 5.5: a field value excludes surrounding whitespace. + let valueEnd = lineEnd; + while (valueEnd > valueStart && [9, 32].includes(bytes[valueEnd - 1]!)) + valueEnd -= 1; let value: string; try { - value = decoder.decode(bytes.subarray(valueStart, lineEnd)); + value = decoder.decode(bytes.subarray(valueStart, valueEnd)); } catch (cause) { if (!(cause instanceof TypeError)) throw cause; decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });