it('parses multipart file with annotation', async () => { const body = '--AaB03x\r\n' + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + 'Content-Type: image/jpeg\r\n' + '\r\n' + '... contents of file1.txt ...\r\r\n' + '--AaB03x--\r\n'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'multipart/form-data; boundary="AaB03x"' }; const { payload } = await Subtext.parse(request, null, { parse: true, output: 'data', multipart: { output: 'annotated' } }); expect(payload.pics).to.equal({ payload: Buffer.from('... contents of file1.txt ...\r'), headers: { 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', 'content-type': 'image/jpeg' }, filename: 'file1.txt' }); });
it('errors on invalid upload directory (parse true)', async () => { const request = Wreck.toReadableStream('payload'); request.headers = { 'content-type': 'application/json' }; await expect(Subtext.parse(request, null, { parse: true, output: 'file', uploads: '/a/b/c/no/such/folder' })).to.reject(/ENOENT/); });
it('returns null on empty payload and application/octet-stream', async () => { const body = ''; const request = Wreck.toReadableStream(body); request.headers = {}; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(mime).to.equal('application/octet-stream'); expect(payload).to.be.null(); });
it('emits error on internal processing errors', async () => { const random = Buffer.alloc(5000); const source = Wreck.toReadableStream(random); const stream = new Ammo.Stream({ from: 1000, to: 4000 }); stream._range = null; // Force a processing error await expect(Wreck.read(source.pipe(stream))).to.reject(Error); });
it('returns a subset of a stream', async () => { const random = Buffer.alloc(5000); const source = Wreck.toReadableStream(random); const range = Ammo.header('bytes=1000-4000', 5000); const stream = new Ammo.Stream(range[0]); const buffer = await Wreck.read(source.pipe(stream)); expect(buffer.toString()).to.equal(random.slice(1000, 4001).toString()); });
it('errors on invalid content type header', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'steve' }; await expect(Subtext.parse(request, null, { parse: true, output: 'data' })).to.reject('Invalid content-type header'); });
it('errors on unsupported content type', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'james/bond' }; await expect(Subtext.parse(request, null, { parse: true, output: 'data' })).to.reject('Unsupported Media Type'); });
it('errors on an unallowed content-type (array)', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'text/plain' }; await expect(Subtext.parse(request, null, { parse: true, output: 'data', allow: ['application/json'] })).to.reject('Unsupported Media Type'); });
it('custom default content-type', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = {}; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data', defaultContentType: 'application/json' }); expect(mime).to.equal('application/json'); expect(payload).to.equal(JSON.parse(body)); });
it('defaults to application/octet-stream', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = {}; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(mime).to.equal('application/octet-stream'); expect(Buffer.isBuffer(payload)).to.be.true(); expect(payload.toString()).to.equal(body); });
it('returns an empty string', async () => { const body = ''; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'text/plain' }; const { payload } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(payload).to.equal(''); });
it('errors on invalid JSON payload (__proto__)', async () => { const body = '{"x":1,"y":2,"z":3,"__proto__":{"a":1}}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json' }; const err = await expect(Subtext.parse(request, null, { parse: true, output: 'data' })).to.reject('Invalid request payload JSON format'); expect(err.raw.toString()).to.equal(body); });
it('validates maxBytes when content is within limit', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-length': '50', 'content-type': 'application/json' }; await expect(Subtext.parse(request, null, { parse: false, output: 'data', maxBytes: 100 })).to.not.reject(); });
it('limits maxBytes when content-length header missing', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json' }; request.destroy = function () { }; await expect(Subtext.parse(request, null, { parse: false, output: 'data', maxBytes: 10 })).to.reject('Payload content length greater than maximum allowed: 10'); });
it('errors when content-length header greater than maxBytes', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-length': '50', 'content-type': 'application/json' }; const err = await expect(Subtext.parse(request, null, { parse: false, output: 'data', maxBytes: 10 })).to.reject('Payload content length greater than maximum allowed: 10'); expect(err.output.statusCode).to.equal(413); });
it('parses an allowed content-type (array)', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'text/plain' }; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data', allow: ['text/plain'] }); expect(mime).to.equal('text/plain'); expect(payload).to.equal(body); });
it('returns an empty parsed body', async () => { const body = ''; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json' }; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(mime).to.equal('application/json'); expect(payload).to.equal(null); });
it('errors on malformed zipped payload (parse gunzip only)', async () => { const body = '7d8d78347h8347d58w347hd58w374d58w37h5d8w37hd4'; const request = Wreck.toReadableStream(body); request.headers = { 'content-encoding': 'gzip', 'content-type': 'application/json' }; const err = await expect(Subtext.parse(request, null, { parse: 'gunzip', output: 'data' })).to.reject('Invalid compressed payload'); expect(err.output.statusCode).to.equal(400); });
it('removes __proto__ from input', async () => { const body = '{"x":1,"y":2,"z":3,"__proto__":{"a":1}}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json' }; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data', protoAction: 'remove' }); expect(mime).to.equal('application/json'); expect(payload).to.equal({ x: 1, y: 2, z: 3 }); });
it('returns a parsed body (json-derived media type)', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json-patch+json' }; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(mime).to.equal('application/json-patch+json'); expect(payload).to.equal(JSON.parse(body)); });
it('parses empty form encoded payload', async () => { const body = ''; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/x-www-form-urlencoded' }; const { payload, mime } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(mime).to.equal('application/x-www-form-urlencoded'); expect(payload).to.equal({}); });
it('returns a raw body', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json' }; const { payload, mime } = await Subtext.parse(request, null, { parse: false, output: 'data' }); expect(mime).to.equal('application/json'); expect(Buffer.isBuffer(payload)).to.be.true(); expect(payload.toString()).to.equal(body); });
it('deflates payload without parsing', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const compressed = await internals.compress('deflate', body); const request = Wreck.toReadableStream(compressed); request.headers = { 'content-encoding': 'deflate', 'content-type': 'application/json' }; const { payload } = await Subtext.parse(request, null, { parse: 'gunzip', output: 'data' }); expect(payload.toString()).to.equal(body); });
it('returns a raw body as stream', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'application/json' }; const { payload: stream, mime } = await Subtext.parse(request, null, { parse: false, output: 'stream' }); expect(mime).to.equal('application/json'); const payload = await Wreck.read(stream); expect(payload.toString()).to.equal(body); });
it('unzips payload without parsing (external decoders)', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const compressed = await internals.compress('gzip', body); const request = Wreck.toReadableStream(compressed); request.headers = { 'content-encoding': 'gzip', 'content-type': 'application/json' }; const { payload } = await Subtext.parse(request, null, { parse: 'gunzip', output: 'data', compression: {}, decoders: { gzip: (options) => Zlib.createGunzip(options) } }); expect(payload.toString()).to.equal(body); });
it('parses a gzipped payload', async () => { const body = '{"x":"1","y":"2","z":"3"}'; const compressed = await internals.compress('gzip', body); const request = Wreck.toReadableStream(compressed); request.headers = { 'content-encoding': 'gzip', 'content-type': 'application/json' }; const { payload } = await Subtext.parse(request, null, { parse: true, output: 'data' }); expect(payload).to.equal(JSON.parse(body)); });
it('saves file', async () => { const request = Wreck.toReadableStream('payload'); request.headers = { 'content-type': 'application/json' }; const { payload } = await Subtext.parse(request, null, { parse: false, output: 'file' }); expect(payload).to.only.contain(['path', 'bytes']); const receivedContents = Fs.readFileSync(payload.path); Fs.unlinkSync(payload.path); expect(receivedContents.toString()).to.equal('payload'); });
it('peeks at multipart in stream mode', async () => { const body = '--AaB03x\r\n' + 'content-disposition: form-data; name="x"\r\n' + '\r\n' + 'First\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="x"\r\n' + '\r\n' + 'Second\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="x"\r\n' + '\r\n' + 'Third\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="field1"\r\n' + '\r\n' + 'Joe Blow\r\nalmost tricked you!\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="field1"\r\n' + '\r\n' + 'Repeated name segment\r\n' + '--AaB03x\r\n' + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + 'Content-Type: text/plain\r\n' + '\r\n' + '... contents of file1.txt ...\r\r\n' + '--AaB03x--\r\n'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'multipart/form-data; boundary=AaB03x' }; let raw = ''; const tap = new Stream.Transform(); tap._transform = function (chunk, encoding, callback) { raw = raw + chunk.toString(); this.push(chunk, encoding); callback(); }; const { payload } = await Subtext.parse(request, tap, { parse: true, output: 'stream' }); expect(payload.x).to.equal(['First', 'Second', 'Third']); expect(payload.field1).to.equal(['Joe Blow\r\nalmost tricked you!', 'Repeated name segment']); expect(payload.pics.hapi.filename).to.equal('file1.txt'); expect(raw).to.equal(body); });
it('errors on an invalid multipart payload', async () => { const body = '--AaB03x\r\n' + 'content-disposition: form-data; name="x"\r\n' + '\r\n' + 'First\r\n'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'multipart/form-data; boundary=AaB03x' }; await expect(Subtext.parse(request, null, { parse: true, output: 'data' })).to.reject('Invalid multipart payload format'); });
it('errors on invalid uploads folder while processing multipart payload', async () => { const body = '--AaB03x\r\n' + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n' + '\r\n' + '... contents of file1.txt ...\r\r\n' + '--AaB03x--\r\n'; const request = Wreck.toReadableStream(body); request.headers = { 'content-type': 'multipart/form-data; boundary="AaB03x"' }; await expect(Subtext.parse(request, null, { parse: true, output: 'file', uploads: '/no/such/folder/a/b/c' })).to.reject(/\/no\/such\/folder\/a\/b\/c/); });