@azure/functions
Version:
Microsoft Azure Functions NodeJS Framework
94 lines (81 loc) • 3.55 kB
text/typescript
// 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;
}