123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- 'use strict';
- const { Writable } = require('stream');
- const { getDecoder } = require('../utils.js');
- class URLEncoded extends Writable {
- constructor(cfg) {
- const streamOpts = {
- autoDestroy: true,
- emitClose: true,
- highWaterMark: (typeof cfg.highWaterMark === 'number'
- ? cfg.highWaterMark
- : undefined),
- };
- super(streamOpts);
- let charset = (cfg.defCharset || 'utf8');
- if (cfg.conType.params && typeof cfg.conType.params.charset === 'string')
- charset = cfg.conType.params.charset;
- this.charset = charset;
- const limits = cfg.limits;
- this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
- ? limits.fieldSize
- : 1 * 1024 * 1024);
- this.fieldsLimit = (limits && typeof limits.fields === 'number'
- ? limits.fields
- : Infinity);
- this.fieldNameSizeLimit = (
- limits && typeof limits.fieldNameSize === 'number'
- ? limits.fieldNameSize
- : 100
- );
- this._inKey = true;
- this._keyTrunc = false;
- this._valTrunc = false;
- this._bytesKey = 0;
- this._bytesVal = 0;
- this._fields = 0;
- this._key = '';
- this._val = '';
- this._byte = -2;
- this._lastPos = 0;
- this._encode = 0;
- this._decoder = getDecoder(charset);
- }
- static detect(conType) {
- return (conType.type === 'application'
- && conType.subtype === 'x-www-form-urlencoded');
- }
- _write(chunk, enc, cb) {
- if (this._fields >= this.fieldsLimit)
- return cb();
- let i = 0;
- const len = chunk.length;
- this._lastPos = 0;
- // Check if we last ended mid-percent-encoded byte
- if (this._byte !== -2) {
- i = readPctEnc(this, chunk, i, len);
- if (i === -1)
- return cb(new Error('Malformed urlencoded form'));
- if (i >= len)
- return cb();
- if (this._inKey)
- ++this._bytesKey;
- else
- ++this._bytesVal;
- }
- main:
- while (i < len) {
- if (this._inKey) {
- // Parsing key
- i = skipKeyBytes(this, chunk, i, len);
- while (i < len) {
- switch (chunk[i]) {
- case 61: // '='
- if (this._lastPos < i)
- this._key += chunk.latin1Slice(this._lastPos, i);
- this._lastPos = ++i;
- this._key = this._decoder(this._key, this._encode);
- this._encode = 0;
- this._inKey = false;
- continue main;
- case 38: // '&'
- if (this._lastPos < i)
- this._key += chunk.latin1Slice(this._lastPos, i);
- this._lastPos = ++i;
- this._key = this._decoder(this._key, this._encode);
- this._encode = 0;
- if (this._bytesKey > 0) {
- this.emit(
- 'field',
- this._key,
- '',
- { nameTruncated: this._keyTrunc,
- valueTruncated: false,
- encoding: this.charset,
- mimeType: 'text/plain' }
- );
- }
- this._key = '';
- this._val = '';
- this._keyTrunc = false;
- this._valTrunc = false;
- this._bytesKey = 0;
- this._bytesVal = 0;
- if (++this._fields >= this.fieldsLimit) {
- this.emit('fieldsLimit');
- return cb();
- }
- continue;
- case 43: // '+'
- if (this._lastPos < i)
- this._key += chunk.latin1Slice(this._lastPos, i);
- this._key += ' ';
- this._lastPos = i + 1;
- break;
- case 37: // '%'
- if (this._encode === 0)
- this._encode = 1;
- if (this._lastPos < i)
- this._key += chunk.latin1Slice(this._lastPos, i);
- this._lastPos = i + 1;
- this._byte = -1;
- i = readPctEnc(this, chunk, i + 1, len);
- if (i === -1)
- return cb(new Error('Malformed urlencoded form'));
- if (i >= len)
- return cb();
- ++this._bytesKey;
- i = skipKeyBytes(this, chunk, i, len);
- continue;
- }
- ++i;
- ++this._bytesKey;
- i = skipKeyBytes(this, chunk, i, len);
- }
- if (this._lastPos < i)
- this._key += chunk.latin1Slice(this._lastPos, i);
- } else {
- // Parsing value
- i = skipValBytes(this, chunk, i, len);
- while (i < len) {
- switch (chunk[i]) {
- case 38: // '&'
- if (this._lastPos < i)
- this._val += chunk.latin1Slice(this._lastPos, i);
- this._lastPos = ++i;
- this._inKey = true;
- this._val = this._decoder(this._val, this._encode);
- this._encode = 0;
- if (this._bytesKey > 0 || this._bytesVal > 0) {
- this.emit(
- 'field',
- this._key,
- this._val,
- { nameTruncated: this._keyTrunc,
- valueTruncated: this._valTrunc,
- encoding: this.charset,
- mimeType: 'text/plain' }
- );
- }
- this._key = '';
- this._val = '';
- this._keyTrunc = false;
- this._valTrunc = false;
- this._bytesKey = 0;
- this._bytesVal = 0;
- if (++this._fields >= this.fieldsLimit) {
- this.emit('fieldsLimit');
- return cb();
- }
- continue main;
- case 43: // '+'
- if (this._lastPos < i)
- this._val += chunk.latin1Slice(this._lastPos, i);
- this._val += ' ';
- this._lastPos = i + 1;
- break;
- case 37: // '%'
- if (this._encode === 0)
- this._encode = 1;
- if (this._lastPos < i)
- this._val += chunk.latin1Slice(this._lastPos, i);
- this._lastPos = i + 1;
- this._byte = -1;
- i = readPctEnc(this, chunk, i + 1, len);
- if (i === -1)
- return cb(new Error('Malformed urlencoded form'));
- if (i >= len)
- return cb();
- ++this._bytesVal;
- i = skipValBytes(this, chunk, i, len);
- continue;
- }
- ++i;
- ++this._bytesVal;
- i = skipValBytes(this, chunk, i, len);
- }
- if (this._lastPos < i)
- this._val += chunk.latin1Slice(this._lastPos, i);
- }
- }
- cb();
- }
- _final(cb) {
- if (this._byte !== -2)
- return cb(new Error('Malformed urlencoded form'));
- if (!this._inKey || this._bytesKey > 0 || this._bytesVal > 0) {
- if (this._inKey)
- this._key = this._decoder(this._key, this._encode);
- else
- this._val = this._decoder(this._val, this._encode);
- this.emit(
- 'field',
- this._key,
- this._val,
- { nameTruncated: this._keyTrunc,
- valueTruncated: this._valTrunc,
- encoding: this.charset,
- mimeType: 'text/plain' }
- );
- }
- cb();
- }
- }
- function readPctEnc(self, chunk, pos, len) {
- if (pos >= len)
- return len;
- if (self._byte === -1) {
- // We saw a '%' but no hex characters yet
- const hexUpper = HEX_VALUES[chunk[pos++]];
- if (hexUpper === -1)
- return -1;
- if (hexUpper >= 8)
- self._encode = 2; // Indicate high bits detected
- if (pos < len) {
- // Both hex characters are in this chunk
- const hexLower = HEX_VALUES[chunk[pos++]];
- if (hexLower === -1)
- return -1;
- if (self._inKey)
- self._key += String.fromCharCode((hexUpper << 4) + hexLower);
- else
- self._val += String.fromCharCode((hexUpper << 4) + hexLower);
- self._byte = -2;
- self._lastPos = pos;
- } else {
- // Only one hex character was available in this chunk
- self._byte = hexUpper;
- }
- } else {
- // We saw only one hex character so far
- const hexLower = HEX_VALUES[chunk[pos++]];
- if (hexLower === -1)
- return -1;
- if (self._inKey)
- self._key += String.fromCharCode((self._byte << 4) + hexLower);
- else
- self._val += String.fromCharCode((self._byte << 4) + hexLower);
- self._byte = -2;
- self._lastPos = pos;
- }
- return pos;
- }
- function skipKeyBytes(self, chunk, pos, len) {
- // Skip bytes if we've truncated
- if (self._bytesKey > self.fieldNameSizeLimit) {
- if (!self._keyTrunc) {
- if (self._lastPos < pos)
- self._key += chunk.latin1Slice(self._lastPos, pos - 1);
- }
- self._keyTrunc = true;
- for (; pos < len; ++pos) {
- const code = chunk[pos];
- if (code === 61/* '=' */ || code === 38/* '&' */)
- break;
- ++self._bytesKey;
- }
- self._lastPos = pos;
- }
- return pos;
- }
- function skipValBytes(self, chunk, pos, len) {
- // Skip bytes if we've truncated
- if (self._bytesVal > self.fieldSizeLimit) {
- if (!self._valTrunc) {
- if (self._lastPos < pos)
- self._val += chunk.latin1Slice(self._lastPos, pos - 1);
- }
- self._valTrunc = true;
- for (; pos < len; ++pos) {
- if (chunk[pos] === 38/* '&' */)
- break;
- ++self._bytesVal;
- }
- self._lastPos = pos;
- }
- return pos;
- }
- /* eslint-disable no-multi-spaces */
- const HEX_VALUES = [
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- ];
- /* eslint-enable no-multi-spaces */
- module.exports = URLEncoded;
|