Skip to content

Commit e50bb8c

Browse files
committed
fix(multipart): reject a file part without a filename instead of passing undefined through
1 parent 63ef37d commit e50bb8c

2 files changed

Lines changed: 25 additions & 1 deletion

File tree

‎apps/sim/lib/core/utils/multipart.test.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { isMultipartError, type MultipartError, readMultipart } from '@/lib/core
55
type Part =
66
| { name: string; value: string }
77
| { name: string; filename: string; value: string; contentType?: string }
8+
| { name: string; value: string; contentType: string }
89

910
const BOUNDARY = '----testboundary1234'
1011

@@ -14,6 +15,8 @@ function buildBody(parts: Part[], boundary = BOUNDARY): Buffer {
1415
let header = `--${boundary}\r\nContent-Disposition: form-data; name="${part.name}"`
1516
if ('filename' in part) {
1617
header += `; filename="${part.filename}"\r\nContent-Type: ${part.contentType ?? 'text/csv'}`
18+
} else if ('contentType' in part) {
19+
header += `\r\nContent-Type: ${part.contentType}`
1720
}
1821
header += '\r\n\r\n'
1922
segments.push(Buffer.from(header, 'utf8'), Buffer.from(part.value, 'utf8'), Buffer.from('\r\n'))
@@ -121,6 +124,18 @@ describe('readMultipart', () => {
121124
)
122125
})
123126

127+
it('rejects NO_FILE when the file part carries no filename', async () => {
128+
const request = makeRequest([
129+
{ name: 'file', value: 'name\nAlice\n', contentType: 'application/octet-stream' },
130+
])
131+
await readMultipart(request, { maxFileBytes: 1024 }).then(
132+
() => {
133+
throw new Error('expected rejection')
134+
},
135+
(err) => expectCode(err, 'NO_FILE')
136+
)
137+
})
138+
124139
it('rejects NOT_MULTIPART for a non-multipart content type', async () => {
125140
const request = {
126141
headers: new Headers({ 'content-type': 'application/json' }),

‎apps/sim/lib/core/utils/multipart.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ export function readMultipart(
178178
return
179179
}
180180

181+
// busboy treats an `application/octet-stream` part without a filename as a file.
182+
const { filename } = info
183+
if (filename === undefined) {
184+
stream.resume()
185+
nodeStream.destroy()
186+
settle(() => reject(new MultipartError('NO_FILE', 'File part has no filename')))
187+
return
188+
}
189+
181190
const missing = requiredFieldsBeforeFile.filter((field) => !(field in fields))
182191
if (missing.length > 0) {
183192
stream.resume()
@@ -218,7 +227,7 @@ export function readMultipart(
218227
}
219228
resolve({
220229
fields,
221-
file: { fieldName: name, filename: info.filename, mimeType: info.mimeType, stream },
230+
file: { fieldName: name, filename, mimeType: info.mimeType, stream },
222231
})
223232
})
224233
})

0 commit comments

Comments
 (0)