Skip to content

Commit 8e03aaa

Browse files
hueniversejohnbrett
authored andcommitted
Support multipart override (#41)
1 parent 6dc7f75 commit 8e03aaa

File tree

2 files changed

+92
-19
lines changed

2 files changed

+92
-19
lines changed

lib/index.js

+16-19
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ internals.Parser = function (req, tap, options, next) {
4242
this.tap = tap;
4343

4444
this.result = {};
45-
46-
this.next = (err) => {
47-
48-
return next(err, this.result);
49-
};
45+
this.next = (err) => next(err, this.result);
5046
};
5147

5248

@@ -126,6 +122,10 @@ internals.Parser.prototype.parse = function (contentType) {
126122
// Multipart
127123

128124
if (this.result.contentType.mime === 'multipart/form-data') {
125+
if (this.settings.multipart === false) { // Defaults to true
126+
return next(Boom.unsupportedMediaType());
127+
}
128+
129129
return this.multipart(source, contentType);
130130
}
131131

@@ -160,7 +160,7 @@ internals.Parser.prototype.parse = function (contentType) {
160160
return next(err);
161161
}
162162

163-
internals.object(payload, this.result.contentType.mime, this.settings, (err, result) => {
163+
internals.object(payload, this.result.contentType.mime, (err, result) => {
164164

165165
if (err) {
166166
this.result.payload = null;
@@ -243,7 +243,7 @@ internals.Parser.prototype.raw = function () {
243243
};
244244

245245

246-
internals.object = function (payload, mime, options, next) {
246+
internals.object = function (payload, mime, next) {
247247

248248
// Binary
249249

@@ -351,9 +351,11 @@ internals.Parser.prototype.multipart = function (source, contentType) {
351351
let nextId = 0;
352352
let closed = false;
353353

354+
const output = (this.settings.multipart ? this.settings.multipart.output : this.settings.output);
355+
354356
const onPart = (part) => {
355357

356-
if (this.settings.output === 'file') { // Output: 'file'
358+
if (output === 'file') { // Output: 'file'
357359
const id = nextId++;
358360
pendingFiles[id] = true;
359361
this.writeFile(part, (err, path, bytes) => {
@@ -385,7 +387,7 @@ internals.Parser.prototype.multipart = function (source, contentType) {
385387

386388
// Error handled by dispenser.once('error')
387389

388-
if (this.settings.output === 'stream') { // Output: 'stream'
390+
if (output === 'stream') { // Output: 'stream'
389391
const item = Wreck.toReadableStream(payload);
390392

391393
item.hapi = {
@@ -398,29 +400,24 @@ internals.Parser.prototype.multipart = function (source, contentType) {
398400

399401
const ct = part.headers['content-type'] || '';
400402
const mime = ct.split(';')[0].trim().toLowerCase();
403+
const annotate = (value) => set(part.name, output === 'annotated' ? { filename: part.filename, headers: part.headers, payload: value } : value);
401404

402405
if (!mime) {
403-
return set(part.name, payload);
406+
return annotate(payload);
404407
}
405408

406409
if (!payload.length) {
407-
return set(part.name, {});
410+
return annotate({});
408411
}
409412

410-
internals.object(payload, mime, this.settings, (err, result) => {
411-
412-
return set(part.name, err ? payload : result);
413-
});
413+
internals.object(payload, mime, (err, result) => annotate(err ? payload : result));
414414
});
415415
}
416416
};
417417

418418
dispenser.on('part', onPart);
419419

420-
const onField = (name, value) => {
421-
422-
set(name, value);
423-
};
420+
const onField = (name, value) => set(name, value);
424421

425422
dispenser.on('field', onField);
426423

test/index.js

+76
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,28 @@ describe('parse()', () => {
966966
});
967967
});
968968

969+
it('errors on disabled multipart', (done) => {
970+
971+
const payload =
972+
'--AaB03x\r\n' +
973+
'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' +
974+
'Content-Type: text/plain\r\n' +
975+
'\r\n' +
976+
'\r\n' +
977+
'--AaB03x--\r\n';
978+
979+
const request = Wreck.toReadableStream(payload);
980+
request.headers = {
981+
'content-type': 'multipart/form-data; boundary=AaB03x'
982+
};
983+
984+
Subtext.parse(request, null, { parse: true, output: 'data', multipart: false }, (err, parsed) => {
985+
986+
expect(err).to.exist();
987+
done();
988+
});
989+
});
990+
969991
it('errors on an invalid multipart header (missing boundary)', (done) => {
970992

971993
const payload =
@@ -1052,6 +1074,37 @@ describe('parse()', () => {
10521074
});
10531075
});
10541076

1077+
it('parses file with annotation', (done) => {
1078+
1079+
const payload =
1080+
'--AaB03x\r\n' +
1081+
'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' +
1082+
'Content-Type: image/jpeg\r\n' +
1083+
'\r\n' +
1084+
'... contents of file1.txt ...\r\r\n' +
1085+
'--AaB03x--\r\n';
1086+
1087+
const request = Wreck.toReadableStream(payload);
1088+
request.headers = {
1089+
'content-type': 'multipart/form-data; boundary="AaB03x"'
1090+
};
1091+
1092+
Subtext.parse(request, null, { parse: true, output: 'data', multipart: { output: 'annotated' } }, (err, parsed) => {
1093+
1094+
expect(err).to.not.exist();
1095+
expect(parsed.payload.pics).to.equal({
1096+
payload: new Buffer('... contents of file1.txt ...\r'),
1097+
headers: {
1098+
'content-disposition': 'form-data; name="pics"; filename="file1.txt"',
1099+
'content-type': 'image/jpeg'
1100+
},
1101+
filename: 'file1.txt'
1102+
});
1103+
1104+
done();
1105+
});
1106+
});
1107+
10551108
it('errors on invalid uploads folder while processing multipart payload', (done) => {
10561109

10571110
const payload =
@@ -1148,6 +1201,29 @@ describe('parse()', () => {
11481201
});
11491202
});
11501203

1204+
it('parses a multipart file as file (multipart override)', (done) => {
1205+
1206+
const path = Path.join(__dirname, './file/image.jpg');
1207+
const stats = Fs.statSync(path);
1208+
1209+
const form = new FormData();
1210+
form.append('my_file', Fs.createReadStream(path));
1211+
form.headers = form.getHeaders();
1212+
1213+
Subtext.parse(form, null, { parse: true, output: 'data', multipart: { output: 'file' } }, (err, parsed) => {
1214+
1215+
expect(err).to.not.exist();
1216+
1217+
expect(parsed.payload.my_file.bytes).to.equal(stats.size);
1218+
1219+
const sourceContents = Fs.readFileSync(path);
1220+
const receivedContents = Fs.readFileSync(parsed.payload.my_file.path);
1221+
Fs.unlinkSync(parsed.payload.my_file.path);
1222+
expect(sourceContents).to.equal(receivedContents);
1223+
done();
1224+
});
1225+
});
1226+
11511227
it('parses multiple files as files', (done) => {
11521228

11531229
const path = Path.join(__dirname, './file/image.jpg');

0 commit comments

Comments
 (0)