From 7de854ce3f92bd68963c7bccebcb1017a9f58b5d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 7 Aug 2017 15:56:15 -0700 Subject: [PATCH] http: require json bodies to be an object. --- lib/http/base.js | 2 ++ lib/http/request.js | 66 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/lib/http/base.js b/lib/http/base.js index b59cb627..107c7857 100644 --- a/lib/http/base.js +++ b/lib/http/base.js @@ -329,6 +329,8 @@ HTTPBase.prototype.parseBody = async function parseBody(req, options) { switch (type) { case 'json': body = JSON.parse(data); + if (!body || typeof body !== 'object' || Array.isArray(body)) + throw new Error('JSON body must be an object.'); break; case 'form': body = parsePairs(data, options.keyLimit); diff --git a/lib/http/request.js b/lib/http/request.js index 937e442e..76b35509 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -202,16 +202,16 @@ RequestOptions.prototype.isExpected = function isExpected(type) { return this.expect === type; }; -RequestOptions.prototype.isOverflow = function isOverflow(length) { - if (!length) +RequestOptions.prototype.isOverflow = function isOverflow(hdr) { + if (!hdr) return false; if (!this.buffer) return false; - length = parseInt(length, 10); + const length = parseInt(hdr, 10); - if (length !== length) + if (!isFinite(length)) return true; return length > this.limit; @@ -293,9 +293,10 @@ function Request(options) { this.total = 0; this.decoder = null; this.body = null; + this.buffer = null; } -Request.prototype.__proto__ = Stream.prototype; +Object.setPrototypeOf(Request.prototype, Stream.prototype); Request.prototype.startTimeout = function startTimeout() { if (!this.options.timeout) @@ -395,27 +396,58 @@ Request.prototype.finish = function finish(err) { this.cleanup(); - if (this.options.buffer && this.body) { + if (this.options.buffer) { + assert(this.buffer != null); switch (this.type) { - case 'bin': - this.body = Buffer.concat(this.body); + case 'bin': { + this.body = Buffer.concat(this.buffer); + this.buffer = null; break; - case 'json': + } + case 'json': { + const buffer = this.buffer.trim(); + + this.buffer = null; + + if (buffer.length === 0) + break; + + let body; try { - this.body = JSON.parse(this.body); + body = JSON.parse(buffer); } catch (e) { this.emit('error', e); return; } + + if (!body || typeof body !== 'object') { + this.emit('error', new Error('JSON body is a non-object.')); + return; + } + + this.body = body; + break; - case 'form': + } + case 'form': { + const buffer = this.buffer; + + this.buffer = null; + try { - this.body = qs.parse(this.body); + this.body = qs.parse(buffer); } catch (e) { this.emit('error', e); return; } + break; + } + default: { + this.body = this.buffer; + this.buffer = null; + break; + } } } @@ -469,9 +501,9 @@ Request.prototype._onResponse = function _onResponse(response) { if (this.options.buffer) { if (this.type !== 'bin') { this.decoder = new StringDecoder('utf8'); - this.body = ''; + this.buffer = ''; } else { - this.body = []; + this.buffer = []; } } }; @@ -488,11 +520,13 @@ Request.prototype._onData = function _onData(data) { return; } } + if (this.decoder) { - this.body += this.decoder.write(data); + this.buffer += this.decoder.write(data); return; } - this.body.push(data); + + this.buffer.push(data); } };