Skip to content

Instantly share code, notes, and snippets.

@kentcdodds
Created July 14, 2021 21:13
Show Gist options
  • Save kentcdodds/4eb9c6365377ef82e4397635facbae20 to your computer and use it in GitHub Desktop.
Save kentcdodds/4eb9c6365377ef82e4397635facbae20 to your computer and use it in GitHub Desktop.
I thought I needed this but turns out I didn't. None of the existing solutions worked well for me so I built this. Maybe it'll be useful someday...
import {typedBoolean} from './misc'
function headerValuesAsObject(headerValue: string) {
const valuesAsObject: Record<string, string> = Object.fromEntries(
headerValue
.split(', ')
.filter(h => h.includes('='))
.map(h => {
const [key, valueString] = h.split('=')
if (!valueString) return []
const unwrappedValue = valueString.replace(/^"(.*)"$/, '$1')
return [key, unwrappedValue]
}),
)
return valuesAsObject
}
function parse(body: string, contentType: string) {
if (!contentType.startsWith('multipart/form-data;')) {
throw new Error(`Unsupported content type: ${contentType}`)
}
const contentTypeHeaderValue = contentType
.replace('multipart/form-data;', '')
.trim()
const {boundary} = headerValuesAsObject(contentTypeHeaderValue)
if (!boundary) {
throw new Error('No boundary found in content-type header')
}
const trailerlessBody = body.split(`--${boundary}--`)[0]?.trim() // get rid of the trailer
if (!trailerlessBody) {
throw new Error('No multipart body found')
}
const rawParts = trailerlessBody
.split(`--${boundary}`)
.map(l => l.trim())
.filter(typedBoolean)
const parts = rawParts.map(rawPart => {
const match = rawPart.match(
/^(?<rawHeaderLines>.*?)(?<separator>(\r\n|\n){2})(?<partBody>.*?)$/s,
)
if (
!match?.groups ||
!match.groups.rawHeaderLines ||
!match.groups.partBody
) {
throw new Error('No header or body found in part')
}
const {rawHeaderLines, partBody} = match.groups
const headers = new Headers()
for (const rawHeaderLine of rawHeaderLines.split('\n')) {
const [key, values] = rawHeaderLine.trim().split(':')
if (key) {
for (const value of values?.split(';') ?? []) {
headers.append(key, value.trim())
}
}
}
const contentDisposition = headerValuesAsObject(
headers.get('Content-Disposition') ?? '',
)
const parsed = {
contentDisposition,
headers,
body: partBody.trim(),
}
return parsed
})
return parts
}
export {parse}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment