UNPKG

@azure/functions

Version:
94 lines (81 loc) 3.55 kB
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. import { FormPart } from '@azure/functions'; import { HeaderName } from '../constants'; import { AzFuncSystemError } from '../errors'; import { getHeaderValue, parseContentDisposition } from './parseHeader'; const carriageReturn = Buffer.from('\r')[0]; const newline = Buffer.from('\n')[0]; // multipart/form-data specification https://datatracker.ietf.org/doc/html/rfc7578 export function parseMultipartForm(chunk: Buffer, boundary: string): [string, FormPart][] { const result: [string, FormPart][] = []; let currentName: string | undefined; let currentPart: FormPart | undefined; let inHeaders = false; const boundaryBuffer = Buffer.from(`--${boundary}`); const endBoundaryBuffer = Buffer.from(`--${boundary}--`); let lineStart = 0; let lineEnd = 0; let partValueStart = 0; let partValueEnd = 0; for (let index = 0; index < chunk.length; index++) { let line: Buffer; if (chunk[index] === newline) { lineEnd = chunk[index - 1] === carriageReturn ? index - 1 : index; line = chunk.slice(lineStart, lineEnd); lineStart = index + 1; } else { continue; } const isBoundary = line.equals(boundaryBuffer); const isBoundaryEnd = line.equals(endBoundaryBuffer); if (isBoundary || isBoundaryEnd) { if (currentPart) { currentPart.value = chunk.slice(partValueStart, partValueEnd); } if (isBoundaryEnd) { break; } currentPart = { value: Buffer.from(''), }; inHeaders = true; } else if (inHeaders) { if (!currentPart) { throw new AzFuncSystemError(`Expected form data to start with boundary "${boundary}".`); } const lineAsString = line.toString(); if (!lineAsString) { // A blank line means we're done with the headers for this part inHeaders = false; if (!currentName) { throw new AzFuncSystemError( `Expected part to have header "${HeaderName.contentDisposition}" with parameter "name".` ); } else { partValueStart = lineStart; partValueEnd = lineStart; result.push([currentName, currentPart]); } } else { const contentDisposition = getHeaderValue(lineAsString, HeaderName.contentDisposition); if (contentDisposition) { const [, dispositionParts] = parseContentDisposition(contentDisposition); currentName = dispositionParts.get('name'); // filename is optional, even for files if (dispositionParts.has('fileName')) { currentPart.fileName = dispositionParts.get('fileName'); } } else { const contentType = getHeaderValue(lineAsString, HeaderName.contentType); if (contentType) { currentPart.contentType = contentType; } } } } else { partValueEnd = lineEnd; } } return result; }