+5
-0
@@ -31,2 +31,7 @@ /** | ||
| * Each message payload is a ReadableStream for true streaming | ||
| * | ||
| * Uses background chunk processing to prevent deadlocks when message payloads | ||
| * span multiple network chunks. The parser must continue reading chunks even | ||
| * while consumers are processing messages, ensuring payload streams receive | ||
| * data and can close properly. | ||
| */ | ||
@@ -33,0 +38,0 @@ declare function parseMultipartStream(response: Response, options?: ParserOptions): AsyncGenerator<MultipartMessage, void, unknown>; |
+5
-0
@@ -31,2 +31,7 @@ /** | ||
| * Each message payload is a ReadableStream for true streaming | ||
| * | ||
| * Uses background chunk processing to prevent deadlocks when message payloads | ||
| * span multiple network chunks. The parser must continue reading chunks even | ||
| * while consumers are processing messages, ensuring payload streams receive | ||
| * data and can close properly. | ||
| */ | ||
@@ -33,0 +38,0 @@ declare function parseMultipartStream(response: Response, options?: ParserOptions): AsyncGenerator<MultipartMessage, void, unknown>; |
+58
-30
@@ -123,40 +123,67 @@ "use strict"; | ||
| const reader = stream.getReader(); | ||
| const messageQueue = []; | ||
| let messageIndex = 0; | ||
| try { | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| if (this.buffer !== null && this.buffer.length > 0) { | ||
| this.write(new Uint8Array(0), messageQueue); | ||
| } | ||
| if (this.state !== 4 /* Done */) { | ||
| if (this.state === 0 /* Start */) { | ||
| throw new MultipartParseError( | ||
| "Invalid multipart stream: missing initial boundary" | ||
| ); | ||
| const pendingMessages = []; | ||
| let readerDone = false; | ||
| let readerError = null; | ||
| let cancelled = false; | ||
| const readerTask = (async () => { | ||
| try { | ||
| while (!cancelled) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| if (this.buffer !== null && this.buffer.length > 0) { | ||
| const messages2 = this.write(new Uint8Array(0)); | ||
| pendingMessages.push(...messages2); | ||
| } | ||
| throw new MultipartParseError("Unexpected end of stream"); | ||
| if (this.state !== 4 /* Done */) { | ||
| if (this.state === 0 /* Start */) { | ||
| throw new MultipartParseError( | ||
| "Invalid multipart stream: missing initial boundary" | ||
| ); | ||
| } | ||
| throw new MultipartParseError("Unexpected end of stream"); | ||
| } | ||
| break; | ||
| } | ||
| break; | ||
| const messages = this.write(value); | ||
| pendingMessages.push(...messages); | ||
| } | ||
| this.write(value, messageQueue); | ||
| while (messageIndex < messageQueue.length) { | ||
| const message = await messageQueue[messageIndex++]; | ||
| if (message) { | ||
| yield message; | ||
| } catch (error) { | ||
| readerError = error; | ||
| } finally { | ||
| readerDone = true; | ||
| reader.releaseLock(); | ||
| } | ||
| })(); | ||
| try { | ||
| while (!cancelled) { | ||
| if (readerError) { | ||
| throw readerError; | ||
| } | ||
| if (pendingMessages.length > 0) { | ||
| yield pendingMessages.shift(); | ||
| } else if (readerDone) { | ||
| if (readerError) { | ||
| throw readerError; | ||
| } | ||
| break; | ||
| } else { | ||
| await new Promise((resolve) => setImmediate(resolve)); | ||
| } | ||
| } | ||
| while (messageIndex < messageQueue.length) { | ||
| const message = await messageQueue[messageIndex++]; | ||
| if (message) { | ||
| yield message; | ||
| } | ||
| } | ||
| } finally { | ||
| reader.releaseLock(); | ||
| cancelled = true; | ||
| reader.cancel().catch(() => { | ||
| }); | ||
| await readerTask; | ||
| } | ||
| } | ||
| write(chunk, messageQueue) { | ||
| /** | ||
| * Process a chunk of data through the state machine and return any complete messages. | ||
| * | ||
| * Returns an array because a single chunk can contain multiple complete messages | ||
| * when small messages with headers + body + boundary all fit in one network chunk. | ||
| * All messages must be captured and queued to maintain proper message ordering. | ||
| */ | ||
| write(chunk) { | ||
| const newMessages = []; | ||
| if (this.state === 4 /* Done */) { | ||
@@ -279,3 +306,3 @@ throw new MultipartParseError("Unexpected data after end of stream"); | ||
| const message = this.createStreamingMessage(); | ||
| messageQueue.push(Promise.resolve(message)); | ||
| newMessages.push(message); | ||
| index = headerEndIndex + headerEndOffset; | ||
@@ -305,2 +332,3 @@ this.state = 3 /* Body */; | ||
| } | ||
| return newMessages; | ||
| } | ||
@@ -307,0 +335,0 @@ createStreamingMessage() { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/parser.ts"],"sourcesContent":["// Export main parser functionality\nexport {\n parseMultipartStream,\n extractBoundary,\n MultipartParseError,\n type MultipartMessage,\n type ParserOptions,\n} from \"./parser\";\n","/**\n * Multipart message part containing headers and payload stream\n */\nexport interface MultipartMessage {\n headers: Record<string, string>;\n payload: ReadableStream<Uint8Array>;\n}\n\n/**\n * Error thrown during multipart parsing\n */\nexport class MultipartParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MultipartParseError\";\n }\n}\n\n/**\n * Parser configuration options\n */\nexport interface ParserOptions {\n /** Maximum size for header buffer (default: 64KB) */\n maxHeaderSize?: number;\n /** Maximum size for boundary buffer (default: 8KB) */\n maxBoundaryBuffer?: number;\n}\n\n/**\n * Parser states\n */\nconst enum ParserState {\n Start = 0,\n AfterBoundary = 1,\n Header = 2,\n Body = 3,\n Done = 4,\n}\n\n/**\n * Create an efficient search function for finding patterns in Uint8Array\n * Uses Node.js Buffer.indexOf for optimal performance\n */\nfunction createSearch(\n pattern: string,\n): (haystack: Uint8Array, start?: number) => number {\n const needle = new TextEncoder().encode(pattern);\n\n return (haystack: Uint8Array, start = 0) =>\n Buffer.prototype.indexOf.call(haystack, needle, start);\n}\n\n/**\n * Create search function for partial tail matches (when boundary spans chunks)\n * Optimized for Node.js environment\n */\nfunction createPartialTailSearch(\n pattern: string,\n): (haystack: Uint8Array) => number {\n const needle = new TextEncoder().encode(pattern);\n const byteIndexes: Record<number, number[]> = {};\n\n for (let i = 0; i < needle.length; ++i) {\n const byte = needle[i];\n if (byteIndexes[byte] === undefined) byteIndexes[byte] = [];\n byteIndexes[byte].push(i);\n }\n\n return function (haystack: Uint8Array): number {\n const haystackEnd = haystack.length - 1;\n if (haystack[haystackEnd] in byteIndexes) {\n const indexes = byteIndexes[haystack[haystackEnd]];\n for (let i = indexes.length - 1; i >= 0; --i) {\n for (\n let j = indexes[i], k = haystackEnd;\n j >= 0 && haystack[k] === needle[j];\n --j, --k\n ) {\n if (j === 0) return k;\n }\n }\n }\n return -1;\n };\n}\n\n/**\n * Parse headers from a multipart part\n */\nfunction parseHeaders(headerText: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = headerText.trim().split(\"\\n\");\n\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n headers[key.toLowerCase()] = value;\n }\n }\n\n return headers;\n}\n\n/**\n * Extract boundary from Content-Type header\n */\nexport function extractBoundary(contentType: string): string {\n const boundaryMatch = contentType.match(/boundary=(?:\"([^\"]+)\"|([^;]+))/i);\n if (!boundaryMatch) {\n throw new MultipartParseError(\"No boundary found in Content-Type header\");\n }\n return boundaryMatch[1] ?? boundaryMatch[2];\n}\n\n/**\n * Streaming multipart/mixed parser using async generators\n * Parses multipart messages as they arrive without loading everything into memory\n * Each message payload is a ReadableStream for true streaming\n */\nexport async function* parseMultipartStream(\n response: Response,\n options?: ParserOptions,\n): AsyncGenerator<MultipartMessage, void, unknown> {\n if (!response.body) {\n throw new MultipartParseError(\"Response body is null\");\n }\n\n const contentType = response.headers.get(\"content-type\");\n if (!contentType) {\n throw new MultipartParseError(\"Missing Content-Type header\");\n }\n\n const boundary = extractBoundary(contentType);\n const parser = new StreamingMultipartParser(boundary, options);\n\n // Create async generator from the parser\n yield* parser.parseStream(response.body);\n}\n\n/**\n * High-performance streaming multipart parser with proper state machine\n * Yields messages immediately as headers are parsed, with streaming payloads\n * Includes safety limits to prevent unbounded memory growth\n */\nclass StreamingMultipartParser {\n private readonly boundary: string;\n private readonly findOpeningBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly openingBoundaryLength: number;\n private readonly findBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly findPartialTailBoundary: (haystack: Uint8Array) => number;\n private readonly boundaryLength: number;\n private readonly findDoubleNewline: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n\n // Safety limits\n private readonly maxHeaderSize: number;\n private readonly maxBoundaryBuffer: number;\n\n private state = ParserState.Start;\n private buffer: Uint8Array | null = null;\n private currentHeaders: Record<string, string> = {};\n private currentPayloadController: ReadableStreamDefaultController<Uint8Array> | null =\n null;\n\n constructor(boundary: string, options: ParserOptions = {}) {\n this.boundary = boundary;\n this.findOpeningBoundary = createSearch(`--${boundary}`);\n this.openingBoundaryLength = 2 + boundary.length;\n this.findBoundary = createSearch(`\\r\\n--${boundary}`);\n this.findPartialTailBoundary = createPartialTailSearch(`\\r\\n--${boundary}`);\n this.boundaryLength = 4 + boundary.length;\n this.findDoubleNewline = createSearch(\"\\r\\n\\r\\n\");\n\n // Set safety limits\n this.maxHeaderSize = options.maxHeaderSize ?? 65536; // 64KB\n this.maxBoundaryBuffer = options.maxBoundaryBuffer ?? 8192; // 8KB\n }\n\n async *parseStream(\n stream: ReadableStream<Uint8Array>,\n ): AsyncGenerator<MultipartMessage, void, unknown> {\n const reader = stream.getReader();\n const messageQueue: Promise<MultipartMessage | null>[] = [];\n let messageIndex = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n // Process any remaining buffered content\n if (this.buffer !== null && this.buffer.length > 0) {\n this.write(new Uint8Array(0), messageQueue);\n }\n\n if (this.state !== ParserState.Done) {\n if (this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n throw new MultipartParseError(\"Unexpected end of stream\");\n }\n break;\n }\n\n this.write(value, messageQueue);\n\n // Yield any completed messages\n while (messageIndex < messageQueue.length) {\n const message = await messageQueue[messageIndex++];\n if (message) {\n yield message;\n }\n }\n }\n\n // Yield any remaining messages\n while (messageIndex < messageQueue.length) {\n const message = await messageQueue[messageIndex++];\n if (message) {\n yield message;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n private write(\n chunk: Uint8Array,\n messageQueue: Promise<MultipartMessage | null>[],\n ): void {\n if (this.state === ParserState.Done) {\n throw new MultipartParseError(\"Unexpected data after end of stream\");\n }\n\n let index = 0;\n let chunkLength = chunk.length;\n\n // Merge with existing buffer if present, with safety checks\n if (this.buffer !== null) {\n // Check for reasonable buffer size limits\n const newSize = this.buffer.length + chunkLength;\n const maxAllowedSize =\n this.state === ParserState.Header\n ? this.maxHeaderSize\n : this.maxBoundaryBuffer;\n\n if (newSize > maxAllowedSize) {\n throw new MultipartParseError(\n `Buffer size limit exceeded: ${newSize} bytes > ${maxAllowedSize} bytes. ` +\n `This may indicate malformed multipart data with ${this.state === ParserState.Header ? \"oversized headers\" : \"invalid boundaries\"}.`,\n );\n }\n\n const newChunk = new Uint8Array(newSize);\n newChunk.set(this.buffer, 0);\n newChunk.set(chunk, this.buffer.length);\n chunk = newChunk;\n chunkLength = chunk.length;\n this.buffer = null;\n }\n\n // Handle special case: empty chunk at end of stream with buffered content\n if (chunkLength === 0 && this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n while (true) {\n if (this.state === ParserState.Body) {\n if (chunkLength - index < this.boundaryLength) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n const boundaryIndex = this.findBoundary(chunk, index);\n if (boundaryIndex === -1) {\n const partialTailIndex = this.findPartialTailBoundary(chunk);\n if (partialTailIndex === -1) {\n this.writeBody(index === 0 ? chunk : chunk.subarray(index));\n } else {\n this.writeBody(chunk.subarray(index, partialTailIndex));\n const partialBoundary = chunk.subarray(partialTailIndex);\n if (partialBoundary.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Partial boundary too large: ${partialBoundary.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = partialBoundary;\n }\n break;\n }\n\n this.writeBody(chunk.subarray(index, boundaryIndex));\n this.finishMessage();\n index = boundaryIndex + this.boundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n\n if (this.state === ParserState.AfterBoundary) {\n if (chunkLength - index < 2) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `After-boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Check for final boundary\n if (chunk[index] === 45 && chunk[index + 1] === 45) {\n // '--'\n this.state = ParserState.Done;\n break;\n }\n\n // Skip line ending after boundary - handle both CRLF and LF\n if (chunk[index] === 13 && chunk[index + 1] === 10) {\n // CRLF\n index += 2;\n } else if (chunk[index] === 10) {\n // LF only\n index += 1;\n } else {\n throw new MultipartParseError(\n `Invalid character after boundary: expected CRLF or LF, got 0x${chunk[index].toString(16)}`,\n );\n }\n this.state = ParserState.Header;\n }\n\n if (this.state === ParserState.Header) {\n if (chunkLength - index < 4) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Header buffer limit exceeded: ${remainingData.length} > ${this.maxHeaderSize}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Try to find double newline with both CRLF and LF patterns\n let headerEndIndex = this.findDoubleNewline(chunk, index);\n let headerEndOffset = 4; // for \\r\\n\\r\\n\n\n if (headerEndIndex === -1) {\n // Try LF-only pattern\n const lfDoubleNewline = createSearch(\"\\n\\n\");\n headerEndIndex = lfDoubleNewline(chunk, index);\n headerEndOffset = 2; // for \\n\\n\n }\n\n if (headerEndIndex === -1) {\n const headerData = chunk.subarray(index);\n if (headerData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Headers too large: ${headerData.length} > ${this.maxHeaderSize} bytes`,\n );\n }\n this.buffer = headerData;\n break;\n }\n\n const headerBytes = chunk.subarray(index, headerEndIndex);\n const headerText = new TextDecoder().decode(headerBytes);\n this.currentHeaders = parseHeaders(headerText);\n\n // Create a message with streaming payload and add it to the queue\n const message = this.createStreamingMessage();\n messageQueue.push(Promise.resolve(message));\n\n index = headerEndIndex + headerEndOffset;\n this.state = ParserState.Body;\n continue;\n }\n\n if (this.state === ParserState.Start) {\n if (chunkLength < this.openingBoundaryLength) {\n if (chunk.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Initial chunk too large for boundary detection: ${chunk.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = chunk;\n break;\n }\n\n const boundaryIndex = this.findOpeningBoundary(chunk);\n\n if (boundaryIndex !== 0) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n index = this.openingBoundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n }\n }\n\n private createStreamingMessage(): MultipartMessage {\n const headers = { ...this.currentHeaders };\n\n const payload = new ReadableStream<Uint8Array>({\n start: (controller) => {\n this.currentPayloadController = controller;\n },\n });\n\n // Reset headers for next message\n this.currentHeaders = {};\n\n return {\n headers,\n payload,\n };\n }\n\n private writeBody(chunk: Uint8Array): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.enqueue(chunk);\n }\n }\n\n private finishMessage(): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.close();\n this.currentPayloadController = null;\n }\n }\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/index.ts","../src/parser.ts"],"sourcesContent":["// Export main parser functionality\nexport {\n parseMultipartStream,\n extractBoundary,\n MultipartParseError,\n type MultipartMessage,\n type ParserOptions,\n} from \"./parser\";\n","/**\n * Multipart message part containing headers and payload stream\n */\nexport interface MultipartMessage {\n headers: Record<string, string>;\n payload: ReadableStream<Uint8Array>;\n}\n\n/**\n * Error thrown during multipart parsing\n */\nexport class MultipartParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MultipartParseError\";\n }\n}\n\n/**\n * Parser configuration options\n */\nexport interface ParserOptions {\n /** Maximum size for header buffer (default: 64KB) */\n maxHeaderSize?: number;\n /** Maximum size for boundary buffer (default: 8KB) */\n maxBoundaryBuffer?: number;\n}\n\n/**\n * Parser states\n */\nconst enum ParserState {\n Start = 0,\n AfterBoundary = 1,\n Header = 2,\n Body = 3,\n Done = 4,\n}\n\n/**\n * Create an efficient search function for finding patterns in Uint8Array\n * Uses Node.js Buffer.indexOf for optimal performance\n */\nfunction createSearch(\n pattern: string,\n): (haystack: Uint8Array, start?: number) => number {\n const needle = new TextEncoder().encode(pattern);\n\n return (haystack: Uint8Array, start = 0) =>\n Buffer.prototype.indexOf.call(haystack, needle, start);\n}\n\n/**\n * Create search function for partial tail matches (when boundary spans chunks)\n * Optimized for Node.js environment\n */\nfunction createPartialTailSearch(\n pattern: string,\n): (haystack: Uint8Array) => number {\n const needle = new TextEncoder().encode(pattern);\n const byteIndexes: Record<number, number[]> = {};\n\n for (let i = 0; i < needle.length; ++i) {\n const byte = needle[i];\n if (byteIndexes[byte] === undefined) byteIndexes[byte] = [];\n byteIndexes[byte].push(i);\n }\n\n return function (haystack: Uint8Array): number {\n const haystackEnd = haystack.length - 1;\n if (haystack[haystackEnd] in byteIndexes) {\n const indexes = byteIndexes[haystack[haystackEnd]];\n for (let i = indexes.length - 1; i >= 0; --i) {\n for (\n let j = indexes[i], k = haystackEnd;\n j >= 0 && haystack[k] === needle[j];\n --j, --k\n ) {\n if (j === 0) return k;\n }\n }\n }\n return -1;\n };\n}\n\n/**\n * Parse headers from a multipart part\n */\nfunction parseHeaders(headerText: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = headerText.trim().split(\"\\n\");\n\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n headers[key.toLowerCase()] = value;\n }\n }\n\n return headers;\n}\n\n/**\n * Extract boundary from Content-Type header\n */\nexport function extractBoundary(contentType: string): string {\n const boundaryMatch = contentType.match(/boundary=(?:\"([^\"]+)\"|([^;]+))/i);\n if (!boundaryMatch) {\n throw new MultipartParseError(\"No boundary found in Content-Type header\");\n }\n return boundaryMatch[1] ?? boundaryMatch[2];\n}\n\n/**\n * Streaming multipart/mixed parser using async generators\n * Parses multipart messages as they arrive without loading everything into memory\n * Each message payload is a ReadableStream for true streaming\n *\n * Uses background chunk processing to prevent deadlocks when message payloads\n * span multiple network chunks. The parser must continue reading chunks even\n * while consumers are processing messages, ensuring payload streams receive\n * data and can close properly.\n */\nexport async function* parseMultipartStream(\n response: Response,\n options?: ParserOptions,\n): AsyncGenerator<MultipartMessage, void, unknown> {\n if (!response.body) {\n throw new MultipartParseError(\"Response body is null\");\n }\n\n const contentType = response.headers.get(\"content-type\");\n if (!contentType) {\n throw new MultipartParseError(\"Missing Content-Type header\");\n }\n\n const boundary = extractBoundary(contentType);\n const parser = new StreamingMultipartParser(boundary, options);\n\n // Create async generator from the parser\n yield* parser.parseStream(response.body);\n}\n\n/**\n * High-performance streaming multipart parser with proper state machine\n * Yields messages immediately as headers are parsed, with streaming payloads\n * Includes safety limits to prevent unbounded memory growth\n */\nclass StreamingMultipartParser {\n private readonly boundary: string;\n private readonly findOpeningBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly openingBoundaryLength: number;\n private readonly findBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly findPartialTailBoundary: (haystack: Uint8Array) => number;\n private readonly boundaryLength: number;\n private readonly findDoubleNewline: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n\n // Safety limits\n private readonly maxHeaderSize: number;\n private readonly maxBoundaryBuffer: number;\n\n private state = ParserState.Start;\n private buffer: Uint8Array | null = null;\n private currentHeaders: Record<string, string> = {};\n private currentPayloadController: ReadableStreamDefaultController<Uint8Array> | null =\n null;\n\n constructor(boundary: string, options: ParserOptions = {}) {\n this.boundary = boundary;\n this.findOpeningBoundary = createSearch(`--${boundary}`);\n this.openingBoundaryLength = 2 + boundary.length;\n this.findBoundary = createSearch(`\\r\\n--${boundary}`);\n this.findPartialTailBoundary = createPartialTailSearch(`\\r\\n--${boundary}`);\n this.boundaryLength = 4 + boundary.length;\n this.findDoubleNewline = createSearch(\"\\r\\n\\r\\n\");\n\n // Set safety limits\n this.maxHeaderSize = options.maxHeaderSize ?? 65536; // 64KB\n this.maxBoundaryBuffer = options.maxBoundaryBuffer ?? 8192; // 8KB\n }\n\n async *parseStream(\n stream: ReadableStream<Uint8Array>,\n ): AsyncGenerator<MultipartMessage, void, unknown> {\n const reader = stream.getReader();\n const pendingMessages: MultipartMessage[] = [];\n let readerDone = false;\n let readerError: Error | null = null;\n let cancelled = false;\n\n /**\n * Background chunk processing to prevent coordination deadlocks\n *\n * When message payloads span multiple network chunks, we must avoid this deadlock:\n *\n * 1. Headers arrive in chunk 1 → Message created and yielded with empty payload stream\n * 2. Consumer starts reading the payload stream\n * 3. If parser waits for `yield` to complete before reading more chunks\n * 4. Body data in chunks 2-N are never read from the network\n * 5. Closing boundary is never found → Payload stream never closes\n * 6. Consumer hangs forever waiting for payload data\n *\n * By decoupling chunk reading from message yielding with a background task,\n * chunks continue to be read and processed even while messages are being\n * consumed by the application, preventing the deadlock.\n */\n const readerTask = (async () => {\n try {\n while (!cancelled) {\n const { done, value } = await reader.read();\n\n if (done) {\n // Process any remaining buffered content\n if (this.buffer !== null && this.buffer.length > 0) {\n const messages = this.write(new Uint8Array(0));\n pendingMessages.push(...messages);\n }\n\n if (this.state !== ParserState.Done) {\n if (this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n throw new MultipartParseError(\"Unexpected end of stream\");\n }\n break;\n }\n\n // Process chunk and queue any messages that become ready\n // Note: write() can return multiple messages when a single chunk\n // contains multiple complete messages (headers + body + boundary)\n const messages = this.write(value);\n pendingMessages.push(...messages);\n }\n } catch (error) {\n readerError = error as Error;\n } finally {\n readerDone = true;\n reader.releaseLock();\n }\n })();\n\n try {\n // Yield messages as they become available from the background processor\n // This loop runs independently of chunk reading, preventing deadlock\n while (!cancelled) {\n if (readerError) {\n throw readerError;\n }\n\n if (pendingMessages.length > 0) {\n yield pendingMessages.shift()!;\n } else if (readerDone) {\n // Final error check after all chunks have been processed\n if (readerError) {\n throw readerError;\n }\n break;\n } else {\n // Wait for more messages to be processed by the background task\n await new Promise((resolve) => setImmediate(resolve));\n }\n }\n } finally {\n // Cleanup: Cancel the reader if generator is terminated early (e.g., break in for-await loop)\n // This prevents resource leaks and ensures proper stream cancellation\n cancelled = true;\n reader.cancel().catch(() => {});\n // Wait for reader to finish processing\n await readerTask;\n }\n }\n\n /**\n * Process a chunk of data through the state machine and return any complete messages.\n *\n * Returns an array because a single chunk can contain multiple complete messages\n * when small messages with headers + body + boundary all fit in one network chunk.\n * All messages must be captured and queued to maintain proper message ordering.\n */\n private write(chunk: Uint8Array): MultipartMessage[] {\n const newMessages: MultipartMessage[] = [];\n if (this.state === ParserState.Done) {\n throw new MultipartParseError(\"Unexpected data after end of stream\");\n }\n\n let index = 0;\n let chunkLength = chunk.length;\n\n // Merge with existing buffer if present, with safety checks\n if (this.buffer !== null) {\n // Check for reasonable buffer size limits\n const newSize = this.buffer.length + chunkLength;\n const maxAllowedSize =\n this.state === ParserState.Header\n ? this.maxHeaderSize\n : this.maxBoundaryBuffer;\n\n if (newSize > maxAllowedSize) {\n throw new MultipartParseError(\n `Buffer size limit exceeded: ${newSize} bytes > ${maxAllowedSize} bytes. ` +\n `This may indicate malformed multipart data with ${this.state === ParserState.Header ? \"oversized headers\" : \"invalid boundaries\"}.`,\n );\n }\n\n const newChunk = new Uint8Array(newSize);\n newChunk.set(this.buffer, 0);\n newChunk.set(chunk, this.buffer.length);\n chunk = newChunk;\n chunkLength = chunk.length;\n this.buffer = null;\n }\n\n // Handle special case: empty chunk at end of stream with buffered content\n if (chunkLength === 0 && this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n while (true) {\n if (this.state === ParserState.Body) {\n if (chunkLength - index < this.boundaryLength) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n const boundaryIndex = this.findBoundary(chunk, index);\n if (boundaryIndex === -1) {\n const partialTailIndex = this.findPartialTailBoundary(chunk);\n if (partialTailIndex === -1) {\n this.writeBody(index === 0 ? chunk : chunk.subarray(index));\n } else {\n this.writeBody(chunk.subarray(index, partialTailIndex));\n const partialBoundary = chunk.subarray(partialTailIndex);\n if (partialBoundary.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Partial boundary too large: ${partialBoundary.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = partialBoundary;\n }\n break;\n }\n\n this.writeBody(chunk.subarray(index, boundaryIndex));\n this.finishMessage();\n index = boundaryIndex + this.boundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n\n if (this.state === ParserState.AfterBoundary) {\n if (chunkLength - index < 2) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `After-boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Check for final boundary\n if (chunk[index] === 45 && chunk[index + 1] === 45) {\n // '--'\n this.state = ParserState.Done;\n break;\n }\n\n // Skip line ending after boundary - handle both CRLF and LF\n if (chunk[index] === 13 && chunk[index + 1] === 10) {\n // CRLF\n index += 2;\n } else if (chunk[index] === 10) {\n // LF only\n index += 1;\n } else {\n throw new MultipartParseError(\n `Invalid character after boundary: expected CRLF or LF, got 0x${chunk[index].toString(16)}`,\n );\n }\n this.state = ParserState.Header;\n }\n\n if (this.state === ParserState.Header) {\n if (chunkLength - index < 4) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Header buffer limit exceeded: ${remainingData.length} > ${this.maxHeaderSize}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Try to find double newline with both CRLF and LF patterns\n let headerEndIndex = this.findDoubleNewline(chunk, index);\n let headerEndOffset = 4; // for \\r\\n\\r\\n\n\n if (headerEndIndex === -1) {\n // Try LF-only pattern\n const lfDoubleNewline = createSearch(\"\\n\\n\");\n headerEndIndex = lfDoubleNewline(chunk, index);\n headerEndOffset = 2; // for \\n\\n\n }\n\n if (headerEndIndex === -1) {\n const headerData = chunk.subarray(index);\n if (headerData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Headers too large: ${headerData.length} > ${this.maxHeaderSize} bytes`,\n );\n }\n this.buffer = headerData;\n break;\n }\n\n const headerBytes = chunk.subarray(index, headerEndIndex);\n const headerText = new TextDecoder().decode(headerBytes);\n this.currentHeaders = parseHeaders(headerText);\n\n // Create a message with streaming payload\n const message = this.createStreamingMessage();\n newMessages.push(message);\n\n index = headerEndIndex + headerEndOffset;\n this.state = ParserState.Body;\n continue;\n }\n\n if (this.state === ParserState.Start) {\n if (chunkLength < this.openingBoundaryLength) {\n if (chunk.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Initial chunk too large for boundary detection: ${chunk.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = chunk;\n break;\n }\n\n const boundaryIndex = this.findOpeningBoundary(chunk);\n\n if (boundaryIndex !== 0) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n index = this.openingBoundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n }\n\n return newMessages;\n }\n\n private createStreamingMessage(): MultipartMessage {\n const headers = { ...this.currentHeaders };\n\n const payload = new ReadableStream<Uint8Array>({\n start: (controller) => {\n this.currentPayloadController = controller;\n },\n });\n\n // Reset headers for next message\n this.currentHeaders = {};\n\n return {\n headers,\n payload,\n };\n }\n\n private writeBody(chunk: Uint8Array): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.enqueue(chunk);\n }\n }\n\n private finishMessage(): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.close();\n this.currentPayloadController = null;\n }\n }\n}\n"],"mappings":"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","names":["messages"]} |
+58
-30
@@ -95,40 +95,67 @@ // src/parser.ts | ||
| const reader = stream.getReader(); | ||
| const messageQueue = []; | ||
| let messageIndex = 0; | ||
| try { | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| if (this.buffer !== null && this.buffer.length > 0) { | ||
| this.write(new Uint8Array(0), messageQueue); | ||
| } | ||
| if (this.state !== 4 /* Done */) { | ||
| if (this.state === 0 /* Start */) { | ||
| throw new MultipartParseError( | ||
| "Invalid multipart stream: missing initial boundary" | ||
| ); | ||
| const pendingMessages = []; | ||
| let readerDone = false; | ||
| let readerError = null; | ||
| let cancelled = false; | ||
| const readerTask = (async () => { | ||
| try { | ||
| while (!cancelled) { | ||
| const { done, value } = await reader.read(); | ||
| if (done) { | ||
| if (this.buffer !== null && this.buffer.length > 0) { | ||
| const messages2 = this.write(new Uint8Array(0)); | ||
| pendingMessages.push(...messages2); | ||
| } | ||
| throw new MultipartParseError("Unexpected end of stream"); | ||
| if (this.state !== 4 /* Done */) { | ||
| if (this.state === 0 /* Start */) { | ||
| throw new MultipartParseError( | ||
| "Invalid multipart stream: missing initial boundary" | ||
| ); | ||
| } | ||
| throw new MultipartParseError("Unexpected end of stream"); | ||
| } | ||
| break; | ||
| } | ||
| break; | ||
| const messages = this.write(value); | ||
| pendingMessages.push(...messages); | ||
| } | ||
| this.write(value, messageQueue); | ||
| while (messageIndex < messageQueue.length) { | ||
| const message = await messageQueue[messageIndex++]; | ||
| if (message) { | ||
| yield message; | ||
| } catch (error) { | ||
| readerError = error; | ||
| } finally { | ||
| readerDone = true; | ||
| reader.releaseLock(); | ||
| } | ||
| })(); | ||
| try { | ||
| while (!cancelled) { | ||
| if (readerError) { | ||
| throw readerError; | ||
| } | ||
| if (pendingMessages.length > 0) { | ||
| yield pendingMessages.shift(); | ||
| } else if (readerDone) { | ||
| if (readerError) { | ||
| throw readerError; | ||
| } | ||
| break; | ||
| } else { | ||
| await new Promise((resolve) => setImmediate(resolve)); | ||
| } | ||
| } | ||
| while (messageIndex < messageQueue.length) { | ||
| const message = await messageQueue[messageIndex++]; | ||
| if (message) { | ||
| yield message; | ||
| } | ||
| } | ||
| } finally { | ||
| reader.releaseLock(); | ||
| cancelled = true; | ||
| reader.cancel().catch(() => { | ||
| }); | ||
| await readerTask; | ||
| } | ||
| } | ||
| write(chunk, messageQueue) { | ||
| /** | ||
| * Process a chunk of data through the state machine and return any complete messages. | ||
| * | ||
| * Returns an array because a single chunk can contain multiple complete messages | ||
| * when small messages with headers + body + boundary all fit in one network chunk. | ||
| * All messages must be captured and queued to maintain proper message ordering. | ||
| */ | ||
| write(chunk) { | ||
| const newMessages = []; | ||
| if (this.state === 4 /* Done */) { | ||
@@ -251,3 +278,3 @@ throw new MultipartParseError("Unexpected data after end of stream"); | ||
| const message = this.createStreamingMessage(); | ||
| messageQueue.push(Promise.resolve(message)); | ||
| newMessages.push(message); | ||
| index = headerEndIndex + headerEndOffset; | ||
@@ -277,2 +304,3 @@ this.state = 3 /* Body */; | ||
| } | ||
| return newMessages; | ||
| } | ||
@@ -279,0 +307,0 @@ createStreamingMessage() { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/parser.ts"],"sourcesContent":["/**\n * Multipart message part containing headers and payload stream\n */\nexport interface MultipartMessage {\n headers: Record<string, string>;\n payload: ReadableStream<Uint8Array>;\n}\n\n/**\n * Error thrown during multipart parsing\n */\nexport class MultipartParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MultipartParseError\";\n }\n}\n\n/**\n * Parser configuration options\n */\nexport interface ParserOptions {\n /** Maximum size for header buffer (default: 64KB) */\n maxHeaderSize?: number;\n /** Maximum size for boundary buffer (default: 8KB) */\n maxBoundaryBuffer?: number;\n}\n\n/**\n * Parser states\n */\nconst enum ParserState {\n Start = 0,\n AfterBoundary = 1,\n Header = 2,\n Body = 3,\n Done = 4,\n}\n\n/**\n * Create an efficient search function for finding patterns in Uint8Array\n * Uses Node.js Buffer.indexOf for optimal performance\n */\nfunction createSearch(\n pattern: string,\n): (haystack: Uint8Array, start?: number) => number {\n const needle = new TextEncoder().encode(pattern);\n\n return (haystack: Uint8Array, start = 0) =>\n Buffer.prototype.indexOf.call(haystack, needle, start);\n}\n\n/**\n * Create search function for partial tail matches (when boundary spans chunks)\n * Optimized for Node.js environment\n */\nfunction createPartialTailSearch(\n pattern: string,\n): (haystack: Uint8Array) => number {\n const needle = new TextEncoder().encode(pattern);\n const byteIndexes: Record<number, number[]> = {};\n\n for (let i = 0; i < needle.length; ++i) {\n const byte = needle[i];\n if (byteIndexes[byte] === undefined) byteIndexes[byte] = [];\n byteIndexes[byte].push(i);\n }\n\n return function (haystack: Uint8Array): number {\n const haystackEnd = haystack.length - 1;\n if (haystack[haystackEnd] in byteIndexes) {\n const indexes = byteIndexes[haystack[haystackEnd]];\n for (let i = indexes.length - 1; i >= 0; --i) {\n for (\n let j = indexes[i], k = haystackEnd;\n j >= 0 && haystack[k] === needle[j];\n --j, --k\n ) {\n if (j === 0) return k;\n }\n }\n }\n return -1;\n };\n}\n\n/**\n * Parse headers from a multipart part\n */\nfunction parseHeaders(headerText: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = headerText.trim().split(\"\\n\");\n\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n headers[key.toLowerCase()] = value;\n }\n }\n\n return headers;\n}\n\n/**\n * Extract boundary from Content-Type header\n */\nexport function extractBoundary(contentType: string): string {\n const boundaryMatch = contentType.match(/boundary=(?:\"([^\"]+)\"|([^;]+))/i);\n if (!boundaryMatch) {\n throw new MultipartParseError(\"No boundary found in Content-Type header\");\n }\n return boundaryMatch[1] ?? boundaryMatch[2];\n}\n\n/**\n * Streaming multipart/mixed parser using async generators\n * Parses multipart messages as they arrive without loading everything into memory\n * Each message payload is a ReadableStream for true streaming\n */\nexport async function* parseMultipartStream(\n response: Response,\n options?: ParserOptions,\n): AsyncGenerator<MultipartMessage, void, unknown> {\n if (!response.body) {\n throw new MultipartParseError(\"Response body is null\");\n }\n\n const contentType = response.headers.get(\"content-type\");\n if (!contentType) {\n throw new MultipartParseError(\"Missing Content-Type header\");\n }\n\n const boundary = extractBoundary(contentType);\n const parser = new StreamingMultipartParser(boundary, options);\n\n // Create async generator from the parser\n yield* parser.parseStream(response.body);\n}\n\n/**\n * High-performance streaming multipart parser with proper state machine\n * Yields messages immediately as headers are parsed, with streaming payloads\n * Includes safety limits to prevent unbounded memory growth\n */\nclass StreamingMultipartParser {\n private readonly boundary: string;\n private readonly findOpeningBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly openingBoundaryLength: number;\n private readonly findBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly findPartialTailBoundary: (haystack: Uint8Array) => number;\n private readonly boundaryLength: number;\n private readonly findDoubleNewline: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n\n // Safety limits\n private readonly maxHeaderSize: number;\n private readonly maxBoundaryBuffer: number;\n\n private state = ParserState.Start;\n private buffer: Uint8Array | null = null;\n private currentHeaders: Record<string, string> = {};\n private currentPayloadController: ReadableStreamDefaultController<Uint8Array> | null =\n null;\n\n constructor(boundary: string, options: ParserOptions = {}) {\n this.boundary = boundary;\n this.findOpeningBoundary = createSearch(`--${boundary}`);\n this.openingBoundaryLength = 2 + boundary.length;\n this.findBoundary = createSearch(`\\r\\n--${boundary}`);\n this.findPartialTailBoundary = createPartialTailSearch(`\\r\\n--${boundary}`);\n this.boundaryLength = 4 + boundary.length;\n this.findDoubleNewline = createSearch(\"\\r\\n\\r\\n\");\n\n // Set safety limits\n this.maxHeaderSize = options.maxHeaderSize ?? 65536; // 64KB\n this.maxBoundaryBuffer = options.maxBoundaryBuffer ?? 8192; // 8KB\n }\n\n async *parseStream(\n stream: ReadableStream<Uint8Array>,\n ): AsyncGenerator<MultipartMessage, void, unknown> {\n const reader = stream.getReader();\n const messageQueue: Promise<MultipartMessage | null>[] = [];\n let messageIndex = 0;\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n // Process any remaining buffered content\n if (this.buffer !== null && this.buffer.length > 0) {\n this.write(new Uint8Array(0), messageQueue);\n }\n\n if (this.state !== ParserState.Done) {\n if (this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n throw new MultipartParseError(\"Unexpected end of stream\");\n }\n break;\n }\n\n this.write(value, messageQueue);\n\n // Yield any completed messages\n while (messageIndex < messageQueue.length) {\n const message = await messageQueue[messageIndex++];\n if (message) {\n yield message;\n }\n }\n }\n\n // Yield any remaining messages\n while (messageIndex < messageQueue.length) {\n const message = await messageQueue[messageIndex++];\n if (message) {\n yield message;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n private write(\n chunk: Uint8Array,\n messageQueue: Promise<MultipartMessage | null>[],\n ): void {\n if (this.state === ParserState.Done) {\n throw new MultipartParseError(\"Unexpected data after end of stream\");\n }\n\n let index = 0;\n let chunkLength = chunk.length;\n\n // Merge with existing buffer if present, with safety checks\n if (this.buffer !== null) {\n // Check for reasonable buffer size limits\n const newSize = this.buffer.length + chunkLength;\n const maxAllowedSize =\n this.state === ParserState.Header\n ? this.maxHeaderSize\n : this.maxBoundaryBuffer;\n\n if (newSize > maxAllowedSize) {\n throw new MultipartParseError(\n `Buffer size limit exceeded: ${newSize} bytes > ${maxAllowedSize} bytes. ` +\n `This may indicate malformed multipart data with ${this.state === ParserState.Header ? \"oversized headers\" : \"invalid boundaries\"}.`,\n );\n }\n\n const newChunk = new Uint8Array(newSize);\n newChunk.set(this.buffer, 0);\n newChunk.set(chunk, this.buffer.length);\n chunk = newChunk;\n chunkLength = chunk.length;\n this.buffer = null;\n }\n\n // Handle special case: empty chunk at end of stream with buffered content\n if (chunkLength === 0 && this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n while (true) {\n if (this.state === ParserState.Body) {\n if (chunkLength - index < this.boundaryLength) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n const boundaryIndex = this.findBoundary(chunk, index);\n if (boundaryIndex === -1) {\n const partialTailIndex = this.findPartialTailBoundary(chunk);\n if (partialTailIndex === -1) {\n this.writeBody(index === 0 ? chunk : chunk.subarray(index));\n } else {\n this.writeBody(chunk.subarray(index, partialTailIndex));\n const partialBoundary = chunk.subarray(partialTailIndex);\n if (partialBoundary.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Partial boundary too large: ${partialBoundary.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = partialBoundary;\n }\n break;\n }\n\n this.writeBody(chunk.subarray(index, boundaryIndex));\n this.finishMessage();\n index = boundaryIndex + this.boundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n\n if (this.state === ParserState.AfterBoundary) {\n if (chunkLength - index < 2) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `After-boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Check for final boundary\n if (chunk[index] === 45 && chunk[index + 1] === 45) {\n // '--'\n this.state = ParserState.Done;\n break;\n }\n\n // Skip line ending after boundary - handle both CRLF and LF\n if (chunk[index] === 13 && chunk[index + 1] === 10) {\n // CRLF\n index += 2;\n } else if (chunk[index] === 10) {\n // LF only\n index += 1;\n } else {\n throw new MultipartParseError(\n `Invalid character after boundary: expected CRLF or LF, got 0x${chunk[index].toString(16)}`,\n );\n }\n this.state = ParserState.Header;\n }\n\n if (this.state === ParserState.Header) {\n if (chunkLength - index < 4) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Header buffer limit exceeded: ${remainingData.length} > ${this.maxHeaderSize}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Try to find double newline with both CRLF and LF patterns\n let headerEndIndex = this.findDoubleNewline(chunk, index);\n let headerEndOffset = 4; // for \\r\\n\\r\\n\n\n if (headerEndIndex === -1) {\n // Try LF-only pattern\n const lfDoubleNewline = createSearch(\"\\n\\n\");\n headerEndIndex = lfDoubleNewline(chunk, index);\n headerEndOffset = 2; // for \\n\\n\n }\n\n if (headerEndIndex === -1) {\n const headerData = chunk.subarray(index);\n if (headerData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Headers too large: ${headerData.length} > ${this.maxHeaderSize} bytes`,\n );\n }\n this.buffer = headerData;\n break;\n }\n\n const headerBytes = chunk.subarray(index, headerEndIndex);\n const headerText = new TextDecoder().decode(headerBytes);\n this.currentHeaders = parseHeaders(headerText);\n\n // Create a message with streaming payload and add it to the queue\n const message = this.createStreamingMessage();\n messageQueue.push(Promise.resolve(message));\n\n index = headerEndIndex + headerEndOffset;\n this.state = ParserState.Body;\n continue;\n }\n\n if (this.state === ParserState.Start) {\n if (chunkLength < this.openingBoundaryLength) {\n if (chunk.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Initial chunk too large for boundary detection: ${chunk.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = chunk;\n break;\n }\n\n const boundaryIndex = this.findOpeningBoundary(chunk);\n\n if (boundaryIndex !== 0) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n index = this.openingBoundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n }\n }\n\n private createStreamingMessage(): MultipartMessage {\n const headers = { ...this.currentHeaders };\n\n const payload = new ReadableStream<Uint8Array>({\n start: (controller) => {\n this.currentPayloadController = controller;\n },\n });\n\n // Reset headers for next message\n this.currentHeaders = {};\n\n return {\n headers,\n payload,\n };\n }\n\n private writeBody(chunk: Uint8Array): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.enqueue(chunk);\n }\n }\n\n private finishMessage(): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.close();\n this.currentPayloadController = null;\n }\n }\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/parser.ts"],"sourcesContent":["/**\n * Multipart message part containing headers and payload stream\n */\nexport interface MultipartMessage {\n headers: Record<string, string>;\n payload: ReadableStream<Uint8Array>;\n}\n\n/**\n * Error thrown during multipart parsing\n */\nexport class MultipartParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"MultipartParseError\";\n }\n}\n\n/**\n * Parser configuration options\n */\nexport interface ParserOptions {\n /** Maximum size for header buffer (default: 64KB) */\n maxHeaderSize?: number;\n /** Maximum size for boundary buffer (default: 8KB) */\n maxBoundaryBuffer?: number;\n}\n\n/**\n * Parser states\n */\nconst enum ParserState {\n Start = 0,\n AfterBoundary = 1,\n Header = 2,\n Body = 3,\n Done = 4,\n}\n\n/**\n * Create an efficient search function for finding patterns in Uint8Array\n * Uses Node.js Buffer.indexOf for optimal performance\n */\nfunction createSearch(\n pattern: string,\n): (haystack: Uint8Array, start?: number) => number {\n const needle = new TextEncoder().encode(pattern);\n\n return (haystack: Uint8Array, start = 0) =>\n Buffer.prototype.indexOf.call(haystack, needle, start);\n}\n\n/**\n * Create search function for partial tail matches (when boundary spans chunks)\n * Optimized for Node.js environment\n */\nfunction createPartialTailSearch(\n pattern: string,\n): (haystack: Uint8Array) => number {\n const needle = new TextEncoder().encode(pattern);\n const byteIndexes: Record<number, number[]> = {};\n\n for (let i = 0; i < needle.length; ++i) {\n const byte = needle[i];\n if (byteIndexes[byte] === undefined) byteIndexes[byte] = [];\n byteIndexes[byte].push(i);\n }\n\n return function (haystack: Uint8Array): number {\n const haystackEnd = haystack.length - 1;\n if (haystack[haystackEnd] in byteIndexes) {\n const indexes = byteIndexes[haystack[haystackEnd]];\n for (let i = indexes.length - 1; i >= 0; --i) {\n for (\n let j = indexes[i], k = haystackEnd;\n j >= 0 && haystack[k] === needle[j];\n --j, --k\n ) {\n if (j === 0) return k;\n }\n }\n }\n return -1;\n };\n}\n\n/**\n * Parse headers from a multipart part\n */\nfunction parseHeaders(headerText: string): Record<string, string> {\n const headers: Record<string, string> = {};\n const lines = headerText.trim().split(\"\\n\");\n\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const key = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n headers[key.toLowerCase()] = value;\n }\n }\n\n return headers;\n}\n\n/**\n * Extract boundary from Content-Type header\n */\nexport function extractBoundary(contentType: string): string {\n const boundaryMatch = contentType.match(/boundary=(?:\"([^\"]+)\"|([^;]+))/i);\n if (!boundaryMatch) {\n throw new MultipartParseError(\"No boundary found in Content-Type header\");\n }\n return boundaryMatch[1] ?? boundaryMatch[2];\n}\n\n/**\n * Streaming multipart/mixed parser using async generators\n * Parses multipart messages as they arrive without loading everything into memory\n * Each message payload is a ReadableStream for true streaming\n *\n * Uses background chunk processing to prevent deadlocks when message payloads\n * span multiple network chunks. The parser must continue reading chunks even\n * while consumers are processing messages, ensuring payload streams receive\n * data and can close properly.\n */\nexport async function* parseMultipartStream(\n response: Response,\n options?: ParserOptions,\n): AsyncGenerator<MultipartMessage, void, unknown> {\n if (!response.body) {\n throw new MultipartParseError(\"Response body is null\");\n }\n\n const contentType = response.headers.get(\"content-type\");\n if (!contentType) {\n throw new MultipartParseError(\"Missing Content-Type header\");\n }\n\n const boundary = extractBoundary(contentType);\n const parser = new StreamingMultipartParser(boundary, options);\n\n // Create async generator from the parser\n yield* parser.parseStream(response.body);\n}\n\n/**\n * High-performance streaming multipart parser with proper state machine\n * Yields messages immediately as headers are parsed, with streaming payloads\n * Includes safety limits to prevent unbounded memory growth\n */\nclass StreamingMultipartParser {\n private readonly boundary: string;\n private readonly findOpeningBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly openingBoundaryLength: number;\n private readonly findBoundary: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n private readonly findPartialTailBoundary: (haystack: Uint8Array) => number;\n private readonly boundaryLength: number;\n private readonly findDoubleNewline: (\n haystack: Uint8Array,\n start?: number,\n ) => number;\n\n // Safety limits\n private readonly maxHeaderSize: number;\n private readonly maxBoundaryBuffer: number;\n\n private state = ParserState.Start;\n private buffer: Uint8Array | null = null;\n private currentHeaders: Record<string, string> = {};\n private currentPayloadController: ReadableStreamDefaultController<Uint8Array> | null =\n null;\n\n constructor(boundary: string, options: ParserOptions = {}) {\n this.boundary = boundary;\n this.findOpeningBoundary = createSearch(`--${boundary}`);\n this.openingBoundaryLength = 2 + boundary.length;\n this.findBoundary = createSearch(`\\r\\n--${boundary}`);\n this.findPartialTailBoundary = createPartialTailSearch(`\\r\\n--${boundary}`);\n this.boundaryLength = 4 + boundary.length;\n this.findDoubleNewline = createSearch(\"\\r\\n\\r\\n\");\n\n // Set safety limits\n this.maxHeaderSize = options.maxHeaderSize ?? 65536; // 64KB\n this.maxBoundaryBuffer = options.maxBoundaryBuffer ?? 8192; // 8KB\n }\n\n async *parseStream(\n stream: ReadableStream<Uint8Array>,\n ): AsyncGenerator<MultipartMessage, void, unknown> {\n const reader = stream.getReader();\n const pendingMessages: MultipartMessage[] = [];\n let readerDone = false;\n let readerError: Error | null = null;\n let cancelled = false;\n\n /**\n * Background chunk processing to prevent coordination deadlocks\n *\n * When message payloads span multiple network chunks, we must avoid this deadlock:\n *\n * 1. Headers arrive in chunk 1 → Message created and yielded with empty payload stream\n * 2. Consumer starts reading the payload stream\n * 3. If parser waits for `yield` to complete before reading more chunks\n * 4. Body data in chunks 2-N are never read from the network\n * 5. Closing boundary is never found → Payload stream never closes\n * 6. Consumer hangs forever waiting for payload data\n *\n * By decoupling chunk reading from message yielding with a background task,\n * chunks continue to be read and processed even while messages are being\n * consumed by the application, preventing the deadlock.\n */\n const readerTask = (async () => {\n try {\n while (!cancelled) {\n const { done, value } = await reader.read();\n\n if (done) {\n // Process any remaining buffered content\n if (this.buffer !== null && this.buffer.length > 0) {\n const messages = this.write(new Uint8Array(0));\n pendingMessages.push(...messages);\n }\n\n if (this.state !== ParserState.Done) {\n if (this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n throw new MultipartParseError(\"Unexpected end of stream\");\n }\n break;\n }\n\n // Process chunk and queue any messages that become ready\n // Note: write() can return multiple messages when a single chunk\n // contains multiple complete messages (headers + body + boundary)\n const messages = this.write(value);\n pendingMessages.push(...messages);\n }\n } catch (error) {\n readerError = error as Error;\n } finally {\n readerDone = true;\n reader.releaseLock();\n }\n })();\n\n try {\n // Yield messages as they become available from the background processor\n // This loop runs independently of chunk reading, preventing deadlock\n while (!cancelled) {\n if (readerError) {\n throw readerError;\n }\n\n if (pendingMessages.length > 0) {\n yield pendingMessages.shift()!;\n } else if (readerDone) {\n // Final error check after all chunks have been processed\n if (readerError) {\n throw readerError;\n }\n break;\n } else {\n // Wait for more messages to be processed by the background task\n await new Promise((resolve) => setImmediate(resolve));\n }\n }\n } finally {\n // Cleanup: Cancel the reader if generator is terminated early (e.g., break in for-await loop)\n // This prevents resource leaks and ensures proper stream cancellation\n cancelled = true;\n reader.cancel().catch(() => {});\n // Wait for reader to finish processing\n await readerTask;\n }\n }\n\n /**\n * Process a chunk of data through the state machine and return any complete messages.\n *\n * Returns an array because a single chunk can contain multiple complete messages\n * when small messages with headers + body + boundary all fit in one network chunk.\n * All messages must be captured and queued to maintain proper message ordering.\n */\n private write(chunk: Uint8Array): MultipartMessage[] {\n const newMessages: MultipartMessage[] = [];\n if (this.state === ParserState.Done) {\n throw new MultipartParseError(\"Unexpected data after end of stream\");\n }\n\n let index = 0;\n let chunkLength = chunk.length;\n\n // Merge with existing buffer if present, with safety checks\n if (this.buffer !== null) {\n // Check for reasonable buffer size limits\n const newSize = this.buffer.length + chunkLength;\n const maxAllowedSize =\n this.state === ParserState.Header\n ? this.maxHeaderSize\n : this.maxBoundaryBuffer;\n\n if (newSize > maxAllowedSize) {\n throw new MultipartParseError(\n `Buffer size limit exceeded: ${newSize} bytes > ${maxAllowedSize} bytes. ` +\n `This may indicate malformed multipart data with ${this.state === ParserState.Header ? \"oversized headers\" : \"invalid boundaries\"}.`,\n );\n }\n\n const newChunk = new Uint8Array(newSize);\n newChunk.set(this.buffer, 0);\n newChunk.set(chunk, this.buffer.length);\n chunk = newChunk;\n chunkLength = chunk.length;\n this.buffer = null;\n }\n\n // Handle special case: empty chunk at end of stream with buffered content\n if (chunkLength === 0 && this.state === ParserState.Start) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n while (true) {\n if (this.state === ParserState.Body) {\n if (chunkLength - index < this.boundaryLength) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n const boundaryIndex = this.findBoundary(chunk, index);\n if (boundaryIndex === -1) {\n const partialTailIndex = this.findPartialTailBoundary(chunk);\n if (partialTailIndex === -1) {\n this.writeBody(index === 0 ? chunk : chunk.subarray(index));\n } else {\n this.writeBody(chunk.subarray(index, partialTailIndex));\n const partialBoundary = chunk.subarray(partialTailIndex);\n if (partialBoundary.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Partial boundary too large: ${partialBoundary.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = partialBoundary;\n }\n break;\n }\n\n this.writeBody(chunk.subarray(index, boundaryIndex));\n this.finishMessage();\n index = boundaryIndex + this.boundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n\n if (this.state === ParserState.AfterBoundary) {\n if (chunkLength - index < 2) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `After-boundary buffer limit exceeded: ${remainingData.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Check for final boundary\n if (chunk[index] === 45 && chunk[index + 1] === 45) {\n // '--'\n this.state = ParserState.Done;\n break;\n }\n\n // Skip line ending after boundary - handle both CRLF and LF\n if (chunk[index] === 13 && chunk[index + 1] === 10) {\n // CRLF\n index += 2;\n } else if (chunk[index] === 10) {\n // LF only\n index += 1;\n } else {\n throw new MultipartParseError(\n `Invalid character after boundary: expected CRLF or LF, got 0x${chunk[index].toString(16)}`,\n );\n }\n this.state = ParserState.Header;\n }\n\n if (this.state === ParserState.Header) {\n if (chunkLength - index < 4) {\n const remainingData = chunk.subarray(index);\n if (remainingData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Header buffer limit exceeded: ${remainingData.length} > ${this.maxHeaderSize}`,\n );\n }\n this.buffer = remainingData;\n break;\n }\n\n // Try to find double newline with both CRLF and LF patterns\n let headerEndIndex = this.findDoubleNewline(chunk, index);\n let headerEndOffset = 4; // for \\r\\n\\r\\n\n\n if (headerEndIndex === -1) {\n // Try LF-only pattern\n const lfDoubleNewline = createSearch(\"\\n\\n\");\n headerEndIndex = lfDoubleNewline(chunk, index);\n headerEndOffset = 2; // for \\n\\n\n }\n\n if (headerEndIndex === -1) {\n const headerData = chunk.subarray(index);\n if (headerData.length > this.maxHeaderSize) {\n throw new MultipartParseError(\n `Headers too large: ${headerData.length} > ${this.maxHeaderSize} bytes`,\n );\n }\n this.buffer = headerData;\n break;\n }\n\n const headerBytes = chunk.subarray(index, headerEndIndex);\n const headerText = new TextDecoder().decode(headerBytes);\n this.currentHeaders = parseHeaders(headerText);\n\n // Create a message with streaming payload\n const message = this.createStreamingMessage();\n newMessages.push(message);\n\n index = headerEndIndex + headerEndOffset;\n this.state = ParserState.Body;\n continue;\n }\n\n if (this.state === ParserState.Start) {\n if (chunkLength < this.openingBoundaryLength) {\n if (chunk.length > this.maxBoundaryBuffer) {\n throw new MultipartParseError(\n `Initial chunk too large for boundary detection: ${chunk.length} > ${this.maxBoundaryBuffer}`,\n );\n }\n this.buffer = chunk;\n break;\n }\n\n const boundaryIndex = this.findOpeningBoundary(chunk);\n\n if (boundaryIndex !== 0) {\n throw new MultipartParseError(\n \"Invalid multipart stream: missing initial boundary\",\n );\n }\n\n index = this.openingBoundaryLength;\n this.state = ParserState.AfterBoundary;\n }\n }\n\n return newMessages;\n }\n\n private createStreamingMessage(): MultipartMessage {\n const headers = { ...this.currentHeaders };\n\n const payload = new ReadableStream<Uint8Array>({\n start: (controller) => {\n this.currentPayloadController = controller;\n },\n });\n\n // Reset headers for next message\n this.currentHeaders = {};\n\n return {\n headers,\n payload,\n };\n }\n\n private writeBody(chunk: Uint8Array): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.enqueue(chunk);\n }\n }\n\n private finishMessage(): void {\n if (this.currentPayloadController) {\n this.currentPayloadController.close();\n this.currentPayloadController = null;\n }\n }\n}\n"],"mappings":"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","names":["messages"]} |
+1
-1
| { | ||
| "name": "mixpart", | ||
| "version": "0.0.2", | ||
| "version": "0.0.3", | ||
| "description": "High-performance streaming multipart/mixed parser for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
84985
11%731
9.1%