Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Node 4.3.1 or higer is required for proper multipart parse
Until Node commit nodejs/node#4738 Buffer.byteLength function did not work correctly. (This commit was entered in Node 4.3.1 https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V4.md#4.3.1) On hapijs/pez project, the commit hapijs/pez@9f0042b#diff-6d186b954a58d5bb740f73d84fe39073R162 was depending on Buffer.byteLength to work correctly. This resulted in a misbehaving multipart parsing when the payload maxBytes is close to the uploaded binary file (Inside the multipart form post payload). Meaning that Subtext library parsing a multipart payload with a binary file that its content-length is smaller than the maxBytes parameter but is close enough than the inner hapijs/pez libary will count mistakenly for a bigger buffer size and will return an error "Maximum size exceeded" (Subtext will return "Invalid multipart payload format"). I wrote a little program that will help you understand the described scenario. Notice: 1. You need to put a binary file inside the project for the program to work. 2. In my example the file was 28MB and the limit I entered was 30MB. Package.json: { "name": "subtext_example", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "hapi": "^13.4.1", "restler": "^3.4.0", "subtext": "^4.0.4" } } Index.json 'use strict'; const Hapi = require('hapi'); const Subtext = require('subtext'); const Restler = require('restler'); const Fs = require('fs'); const Path = require('path'); const server = new Hapi.Server(); server.connection({ port: 3000 }); var demoFilePath = Path.join(__dirname,"binary_file.exe"); var demoFileMimeType = "application/x-msdownload"; var postUrl = "http://localhost:3000/test_subtext"; var payloadMaxBytes = (30 * 1024 * 1024); server.route({ method: 'POST', path: '/test_subtext', config : { payload : { output: 'stream', parse : false, timeout : false, maxBytes : (1024 * 1024 * 1024) } }, handler: function (request, reply) { var subtextParseOptions = { output: 'file', parse : true, timeout : false, maxBytes : payloadMaxBytes }; const onParsed = (err, parsed) => { request.mime = parsed.mime; request.payload = parsed.payload || null; if (!err) { return reply(); } const failAction = request.route.settings.payload.failAction; // failAction: 'error', 'log', 'ignore' if (failAction !== 'ignore') { request._log(['payload', 'error'], err); } if (failAction === 'error') { return reply(err); } return reply(); }; request._isPayloadPending = true; Subtext.parse(request.raw.req, request._tap(), subtextParseOptions, (err, parsed) => { if (!err || !request._isPayloadPending) { request._isPayloadPending = false; return onParsed(err, parsed); } // Flush out any pending request payload not consumed due to errors const stream = request.raw.req; const read = () => { stream.read(); }; const end = () => { stream.removeListener('readable', read); stream.removeListener('error', end); stream.removeListener('end', end); request._isPayloadPending = false; return onParsed(err, parsed); }; stream.on('readable', read); stream.once('error', end); stream.once('end', end); }); } }); server.start((err) => { Fs.stat(demoFilePath, function(err, stats) { Restler.post(postUrl, { multipart: true, data: { "firstKey": "firstValue", "filename": Restler.file(demoFilePath, null, stats.size, null, demoFileMimeType) } }).on("complete", function(data) { console.log('Completed: ' + JSON.stringify(data)); }); }); });
- Loading branch information