diff --git a/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts b/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts index 8afa7fbffb..f81bb2e78a 100644 --- a/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts +++ b/src/browser/history/protobuf/ProtobufBodyTrailer.test.ts @@ -25,6 +25,21 @@ const dataFrame = (payload: Uint8Array) => { return frame; }; +it("does not interpret fields after a blank trailer line", async () => { + const result = await decodeProtobufBody( + trailerFrame("grpc-status: 0\r\n\r\ngrpc-status: 7\r\n"), + "grpc-web", + source, + same, + new ProtobufBudget(), + ); + expect(result.status).toBe("partial"); + expect(result.frames[0]?.trailers).toEqual([ + { name: "grpc-status", value: "0" }, + ]); + expect(result.frames[0]?.examined_bytes).toBe(18); +}); + it("rejects forbidden trailer controls while retaining valid earlier trailers", async () => { for (const code of [ ...Array.from({ length: 32 }, (_, index) => index), diff --git a/src/browser/history/protobuf/ProtobufTrailers.ts b/src/browser/history/protobuf/ProtobufTrailers.ts index 08a651f63d..e0f81ea98f 100644 --- a/src/browser/history/protobuf/ProtobufTrailers.ts +++ b/src/browser/history/protobuf/ProtobufTrailers.ts @@ -7,7 +7,11 @@ interface TrailerFacts { } type TrailerFailure = TrailerFacts & { - readonly kind: "invalid-utf8-value" | "invalid-name" | "invalid-value"; + readonly kind: + | "invalid-utf8-value" + | "invalid-name" + | "invalid-value" + | "invalid-boundary"; readonly reason: string; }; @@ -36,9 +40,15 @@ export const parseGrpcWebTrailers = ( const lineExaminedBytes = hasSeparator ? end + 2 : lineEnd; if (lineEnd === start) { if (!hasSeparator) break; + if (lineExaminedBytes < bytes.length) + return err({ + ...facts(), + examined_bytes: lineExaminedBytes, + kind: "invalid-boundary", + reason: "gRPC-web trailer block has bytes after a blank line", + }); examinedBytes = lineExaminedBytes; - start = end + 2; - continue; + break; } let colon = start; while (colon < lineEnd && bytes[colon] !== 58) colon += 1;