UNPKG

mixpart

Version:

High-performance streaming multipart/mixed parser for Node.js

1 lines 29.9 kB
{"version":3,"sources":["../src/parser.ts"],"sourcesContent":["/**\n * Multipart message part containing headers and payload stream\n */\nexport interface MultipartMessage {\n headers: Headers;\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 raw header bytes\n */\nfunction parseHeaders(headerBytes: Uint8Array): Headers {\n // HTTP headers should be ISO-8859-1 compatible, not UTF-8\n const headerText = new TextDecoder(\"iso-8859-1\").decode(headerBytes);\n\n // Parse header lines into name-value pairs\n const lines = headerText.trim().split(/\\r?\\n/);\n const headerInit: [string, string][] = [];\n\n for (const line of lines) {\n const colonIndex = line.indexOf(\":\");\n if (colonIndex > 0) {\n const name = line.slice(0, colonIndex).trim();\n const value = line.slice(colonIndex + 1).trim();\n headerInit.push([name, value]);\n }\n }\n\n return new Headers(headerInit);\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 * Async message queue for producer-consumer coordination\n * Provides clean separation between chunk reading/parsing and message yielding\n */\nclass AsyncMessageQueue {\n private queue: MultipartMessage[] = [];\n private waiters: Array<{\n resolve: (value: MultipartMessage | null) => void;\n reject: (error: Error) => void;\n }> = [];\n private finished = false;\n private cancelled = false;\n private error: Error | null = null;\n\n /**\n * Producer: Enqueue a message for consumption\n */\n enqueue(message: MultipartMessage): void {\n if (this.finished || this.cancelled) return;\n\n if (this.waiters.length > 0) {\n // Immediate delivery to waiting consumer\n const waiter = this.waiters.shift()!;\n waiter.resolve(message);\n } else {\n // Queue for later consumption\n this.queue.push(message);\n }\n }\n\n /**\n * Producer: Signal completion (with optional error)\n */\n finish(error?: Error): void {\n if (this.finished) return;\n\n this.finished = true;\n this.error = error || null;\n\n // Notify all waiting consumers\n while (this.waiters.length > 0) {\n const waiter = this.waiters.shift()!;\n if (error) {\n waiter.reject(error);\n } else {\n waiter.resolve(null);\n }\n }\n }\n\n /**\n * Consumer: Cancel the queue (stops accepting new messages and notifies waiters)\n */\n cancel(): void {\n if (this.cancelled || this.finished) return;\n\n this.cancelled = true;\n\n // Notify all waiting consumers of cancellation\n while (this.waiters.length > 0) {\n const waiter = this.waiters.shift()!;\n waiter.resolve(null);\n }\n }\n\n /**\n * Consumer: Dequeue next message (or null if finished/cancelled)\n */\n async dequeue(): Promise<MultipartMessage | null> {\n // Return queued message if available\n if (this.queue.length > 0) {\n return this.queue.shift()!;\n }\n\n // Return immediately if finished or cancelled\n if (this.finished || this.cancelled) {\n if (this.error) throw this.error;\n return null;\n }\n\n // Wait for next message\n return new Promise((resolve, reject) => {\n this.waiters.push({ resolve, reject });\n });\n }\n\n /**\n * Check if the queue is in a terminal state\n */\n get isTerminal(): boolean {\n return this.finished || this.cancelled;\n }\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 clean producer-consumer pattern to prevent deadlocks when message payloads\n * span multiple network chunks. The producer continuously reads and parses chunks\n * while the consumer yields messages, ensuring payload streams receive data 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: Headers = new Headers();\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 = new AsyncMessageQueue();\n\n /**\n * Producer: Read chunks and parse messages\n *\n * Runs independently from consumer to prevent deadlocks when message payloads\n * span multiple network chunks. This ensures chunks continue to be read even\n * while consumers are processing messages.\n */\n const producer = this.startProducer(reader, messageQueue);\n\n try {\n // Consumer: Yield messages from the queue\n yield* this.consumeMessages(messageQueue);\n } finally {\n // Cleanup: Cancel queue and stream, close any open payload controllers\n messageQueue.cancel();\n this.closeCurrentPayload();\n\n try {\n await reader.cancel();\n } catch (error) {\n // Ignore cancellation errors - stream might already be closed\n }\n\n // Wait for producer to finish cleanup\n await producer;\n }\n }\n\n /**\n * Producer: Continuously read chunks and parse messages\n */\n private async startProducer(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n messageQueue: AsyncMessageQueue,\n ): Promise<void> {\n try {\n while (!messageQueue.isTerminal) {\n let result;\n try {\n result = await reader.read();\n } catch (readError) {\n // Handle network interruptions, aborted streams, timeouts, etc.\n if (\n readError instanceof Error &&\n (readError.name === \"AbortError\" ||\n readError.constructor.name === \"AbortError\" ||\n readError.name === \"TimeoutError\" ||\n readError.constructor.name === \"TimeoutError\")\n ) {\n // Stream was cancelled or timed out, exit cleanly\n break;\n }\n throw readError;\n }\n\n const { done, value } = result;\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 for (const message of messages) {\n if (messageQueue.isTerminal) break;\n messageQueue.enqueue(message);\n }\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 // Validate chunk data\n if (!(value instanceof Uint8Array)) {\n throw new MultipartParseError(\n `Invalid chunk type: expected Uint8Array, got ${typeof value}`,\n );\n }\n\n // Process chunk and enqueue any messages that become ready\n const messages = this.write(value);\n for (const message of messages) {\n if (messageQueue.isTerminal) break;\n messageQueue.enqueue(message);\n }\n }\n\n // Signal successful completion (unless already cancelled)\n if (!messageQueue.isTerminal) {\n messageQueue.finish();\n }\n } catch (error) {\n // Ensure any open payload controller receives the error\n this.closeCurrentPayload(error as Error);\n\n // Signal error to consumer (unless already cancelled)\n if (!messageQueue.isTerminal) {\n messageQueue.finish(error as Error);\n }\n } finally {\n try {\n reader.releaseLock();\n } catch (error) {\n // Ignore errors during cleanup\n }\n }\n }\n\n /**\n * Consumer: Yield messages from the queue\n */\n private async *consumeMessages(\n messageQueue: AsyncMessageQueue,\n ): AsyncGenerator<MultipartMessage, void, unknown> {\n while (true) {\n const message = await messageQueue.dequeue();\n if (message === null) {\n // Producer finished\n break;\n }\n yield message;\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 this.currentHeaders = parseHeaders(headerBytes);\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 = new 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 = new Headers();\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 /**\n * Close current payload controller if open (used during cleanup)\n * If an error is provided, forwards it to the payload consumer\n */\n private closeCurrentPayload(error?: Error): void {\n if (this.currentPayloadController) {\n try {\n if (error) {\n this.currentPayloadController.error(error);\n } else {\n this.currentPayloadController.close();\n }\n } catch (controllerError) {\n // Ignore errors if controller is already closed/errored\n }\n this.currentPayloadController = null;\n }\n }\n}\n"],"mappings":"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","names":["messages"]}