mcp: fix SSE handling of CRLF and harden (#246773)

Fixes #246753
This commit is contained in:
Connor Peet
2025-04-16 16:16:29 -07:00
committed by GitHub
parent 18f823841d
commit dbbebf2623
2 changed files with 39 additions and 1 deletions

View File

@@ -55,6 +55,7 @@ export class SSEParser {
private lastEventIdBuffer?: string;
private reconnectionTime?: number;
private buffer: Uint8Array[] = [];
private endedOnCR = false;
private readonly onEventHandler: SSEEventHandler;
private readonly decoder: TextDecoder;
/**
@@ -84,8 +85,18 @@ export class SSEParser {
* @param chunk The chunk to parse as a Uint8Array of UTF-8 encoded data.
*/
public feed(chunk: Uint8Array): void {
if (chunk.length === 0) {
return;
}
let offset = 0;
// If the data stream was bifurcated between a CR and LF, avoid processing the CR as an extra newline
if (this.endedOnCR && chunk[0] === Chr.LF) {
offset++;
}
this.endedOnCR = false;
// Process complete lines from the buffer
while (offset < chunk.length) {
const indexCR = chunk.indexOf(Chr.CR, offset);
@@ -103,12 +114,14 @@ export class SSEParser {
this.processLine(str);
this.buffer.length = 0;
offset = index + (chunk[offset] === Chr.CR && chunk[offset + 1] === Chr.LF ? 2 : 1);
offset = index + (chunk[index] === Chr.CR && chunk[index + 1] === Chr.LF ? 2 : 1);
}
if (offset < chunk.length) {
this.buffer.push(chunk.subarray(offset));
} else {
this.endedOnCR = chunk[chunk.length - 1] === Chr.CR;
}
}
/**

View File

@@ -42,6 +42,31 @@ suite('SSEParser', () => {
assert.strictEqual(receivedEvents[0].type, 'custom');
assert.strictEqual(receivedEvents[0].data, 'hello world');
});
test('handles events with explicit event type (CRLF)', () => {
parser.feed(toUint8Array('event: custom\r\ndata: hello world\r\n\r\n'));
assert.strictEqual(receivedEvents.length, 1);
assert.strictEqual(receivedEvents[0].type, 'custom');
assert.strictEqual(receivedEvents[0].data, 'hello world');
});
test('stream processing chunks', () => {
for (const lf of ['\n', '\r\n', '\r']) {
const message = toUint8Array(`event: custom${lf}data: hello world${lf}${lf}event: custom2${lf}data: hello world2${lf}${lf}`);
for (let chunkSize = 1; chunkSize < 5; chunkSize++) {
receivedEvents.length = 0;
for (let i = 0; i < message.length; i += chunkSize) {
const chunk = message.slice(i, i + chunkSize);
parser.feed(chunk);
}
assert.deepStrictEqual(receivedEvents, [
{ type: 'custom', data: 'hello world' },
{ type: 'custom2', data: 'hello world2' }
], `Failed for chunk size ${chunkSize} and line ending ${JSON.stringify(lf)}`);
}
}
});
test('handles events with ID', () => {
parser.feed(toUint8Array('event: custom\ndata: hello\nid: 123\n\n'));