msw
Version:
105 lines (85 loc) • 2.73 kB
text/typescript
import { stringToHeaders } from 'headers-polyfill'
import { DefaultRequestMultipartBody } from '../../handlers/RequestHandler'
interface ParsedContentHeaders {
name: string
filename?: string
contentType: string
}
interface ContentDispositionDirective {
[key: string]: string | undefined
name: string
filename?: string
'form-data': string
}
function parseContentHeaders(headersString: string): ParsedContentHeaders {
const headers = stringToHeaders(headersString)
const contentType = headers.get('content-type') || 'text/plain'
const disposition = headers.get('content-disposition')
if (!disposition) {
throw new Error('"Content-Disposition" header is required.')
}
const directives = disposition.split(';').reduce((acc, chunk) => {
const [name, ...rest] = chunk.trim().split('=')
acc[name] = rest.join('=')
return acc
}, {} as ContentDispositionDirective)
const name = directives.name?.slice(1, -1)
const filename = directives.filename?.slice(1, -1)
return {
name,
filename,
contentType,
}
}
/**
* Parses a given string as a multipart/form-data.
* Does not throw an exception on an invalid multipart string.
*/
export function parseMultipartData<T extends DefaultRequestMultipartBody>(
data: string,
headers?: Headers,
): T | undefined {
const contentType = headers?.get('content-type')
if (!contentType) {
return undefined
}
const [, ...directives] = contentType.split(/; */)
const boundary = directives
.filter((d) => d.startsWith('boundary='))
.map((s) => s.replace(/^boundary=/, ''))[0]
if (!boundary) {
return undefined
}
const boundaryRegExp = new RegExp(`--+${boundary}`)
const fields = data
.split(boundaryRegExp)
.filter((chunk) => chunk.startsWith('\r\n') && chunk.endsWith('\r\n'))
.map((chunk) => chunk.trimStart().replace(/\r\n$/, ''))
if (!fields.length) {
return undefined
}
const parsedBody: DefaultRequestMultipartBody = {}
try {
for (const field of fields) {
const [contentHeaders, ...rest] = field.split('\r\n\r\n')
const contentBody = rest.join('\r\n\r\n')
const { contentType, filename, name } =
parseContentHeaders(contentHeaders)
const value =
filename === undefined
? contentBody
: new File([contentBody], filename, { type: contentType })
const parsedValue = parsedBody[name]
if (parsedValue === undefined) {
parsedBody[name] = value
} else if (Array.isArray(parsedValue)) {
parsedBody[name] = [...parsedValue, value]
} else {
parsedBody[name] = [parsedValue, value]
}
}
return parsedBody as T
} catch {
return undefined
}
}