relu-core
Version:
161 lines (123 loc) • 4.06 kB
JavaScript
// Copyright 2012 Mark Cavage, Inc. All rights reserved.
;
var crypto = require('crypto');
var zlib = require('zlib');
var assert = require('assert-plus');
var errors = require('../errors');
///--- Globals
var BadDigestError = errors.BadDigestError;
var RequestEntityTooLargeError = errors.RequestEntityTooLargeError;
var PayloadTooLargeError = errors.PayloadTooLargeError;
var MD5_MSG = 'Content-MD5 \'%s\' didn\'t match \'%s\'';
///--- Helpers
function createBodyWriter(req) {
var buffers = [];
var contentType = req.contentType();
var isText = false;
if (!contentType ||
contentType === 'application/json' ||
contentType === 'application/x-www-form-urlencoded' ||
contentType === 'multipart/form-data' ||
contentType.substr(0, 5) === 'text/') {
isText = true;
}
req.body = new Buffer(0);
return {
write: function (chunk) {
buffers.push(chunk);
},
end: function () {
req.body = Buffer.concat(buffers);
if (isText) {
req.body = req.body.toString('utf8');
}
}
};
}
///--- API
/**
* reads the body of the request.
* @public
* @function bodyReader
* @throws {BadDigestError | PayloadTooLargeError}
* @param {Object} options an options object
* @returns {Function}
*/
function bodyReader(options) {
options = options || {};
assert.object(options, 'options');
var maxBodySize = options.maxBodySize || 0;
function readBody(req, res, next) {
if ((req.getContentLength() === 0 && !req.isChunked()) ||
req.contentType() === 'multipart/form-data' ||
req.contentType() === 'application/octet-stream') {
next();
return;
}
var bodyWriter = createBodyWriter(req);
var bytesReceived = 0;
var digest;
var gz;
var hash;
var md5;
if ((md5 = req.headers['content-md5'])) {
hash = crypto.createHash('md5');
}
function done() {
var errorMessage;
bodyWriter.end();
if (maxBodySize && bytesReceived > maxBodySize) {
var msg = 'Request body size exceeds ' +
maxBodySize;
// Between Node 0.12 and 4 http status code messages changed
// RequestEntityTooLarge was changed to PayloadTooLarge
// this check is to maintain backwards compatibility
if (PayloadTooLargeError !== undefined) {
errorMessage = new PayloadTooLargeError(msg);
} else {
errorMessage = new RequestEntityTooLargeError(msg);
}
next(errorMessage);
return;
}
if (!req.body.length) {
next();
return;
}
if (hash && md5 !== (digest = hash.digest('base64'))) {
errorMessage = new BadDigestError(MD5_MSG, md5, digest);
next(errorMessage);
return;
}
next();
}
if (req.headers['content-encoding'] === 'gzip') {
gz = zlib.createGunzip();
gz.on('data', bodyWriter.write);
gz.once('end', done);
req.once('end', gz.end.bind(gz));
} else {
req.once('end', done);
}
req.on('data', function onRequestData(chunk) {
if (maxBodySize) {
bytesReceived += chunk.length;
if (bytesReceived > maxBodySize) {
return;
}
}
if (hash) {
hash.update(chunk, 'binary');
}
if (gz) {
gz.write(chunk);
} else {
bodyWriter.write(chunk);
}
});
req.once('error', next);
req.resume();
}
return (readBody);
}
module.exports = bodyReader;