UNPKG

3.06 kBJavaScriptView Raw
1import BusBoy from '@fastify/busboy'
2import { createError } from '@middy/util'
3
4const mimePattern = /^multipart\/form-data(;.*)?$/
5const fieldnamePattern = /(.+)\[(.*)]$/
6
7const defaults = {
8 // busboy options as per documentation: https://www.npmjs.com/package/busboy#busboy-methods
9 busboy: {},
10 charset: 'utf8',
11 disableContentTypeError: false
12}
13
14const httpMultipartBodyParserMiddleware = (opts = {}) => {
15 const options = { ...defaults, ...opts }
16
17 const httpMultipartBodyParserMiddlewareBefore = async (request) => {
18 const { headers } = request.event
19
20 const contentType = headers?.['Content-Type'] ?? headers?.['content-type']
21
22 if (!mimePattern.test(contentType)) {
23 if (options.disableContentTypeError) {
24 return
25 }
26 throw createError(415, 'Unsupported Media Type', {
27 cause: { package: '@middy/multipart-body-parser', data: contentType }
28 })
29 }
30
31 return parseMultipartData(request.event, options)
32 .then((multipartData) => {
33 // request.event.rawBody = body
34 request.event.body = multipartData
35 })
36 .catch((err) => {
37 // UnprocessableEntity
38 throw createError(
39 415,
40 'Invalid or malformed multipart/form-data was provided',
41 { cause: { package: '@middy/multipart-body-parser', data: err } }
42 )
43 })
44 }
45
46 return {
47 before: httpMultipartBodyParserMiddlewareBefore
48 }
49}
50
51const parseMultipartData = (event, options) => {
52 const multipartData = {}
53 const charset = event.isBase64Encoded ? 'base64' : options.charset
54 // header must be lowercase (content-type)
55 const busboy = BusBoy({
56 ...options.busboy,
57 headers: {
58 'content-type':
59 event.headers?.['Content-Type'] ?? event.headers?.['content-type']
60 }
61 })
62
63 return new Promise((resolve, reject) => {
64 busboy
65 .on('file', (fieldname, file, filename, encoding, mimetype) => {
66 const attachment = {
67 filename,
68 mimetype,
69 encoding
70 }
71
72 const chunks = []
73
74 file.on('data', (data) => {
75 chunks.push(data)
76 })
77 file.on('end', () => {
78 attachment.truncated = file.truncated
79 attachment.content = Buffer.concat(chunks)
80 if (!multipartData[fieldname]) {
81 multipartData[fieldname] = attachment
82 } else {
83 const current = multipartData[fieldname]
84 multipartData[fieldname] = [attachment].concat(current)
85 }
86 })
87 })
88 .on('field', (fieldname, value) => {
89 const matches = fieldname.match(fieldnamePattern)
90 if (!matches) {
91 multipartData[fieldname] = value
92 } else {
93 if (!multipartData[matches[1]]) {
94 multipartData[matches[1]] = []
95 }
96 multipartData[matches[1]].push(value)
97 }
98 })
99 .on('finish', () => resolve(multipartData))
100 .on('error', (e) => reject(e))
101
102 busboy.write(event.body, charset)
103 busboy.end()
104 })
105}
106export default httpMultipartBodyParserMiddleware