var Decoder = require('../utils').Decoder, decodeText = require('../utils').decodeText; var RE_CHARSET = /^charset$/i; UrlEncoded.detect = /^application\/x-www-form-urlencoded/i; function UrlEncoded(boy, cfg) { if (!(this instanceof UrlEncoded)) return new UrlEncoded(boy, cfg); var limits = cfg.limits, headers = cfg.headers, parsedConType = cfg.parsedConType; this.boy = boy; this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' ? limits.fieldSize : 1 * 1024 * 1024); this.fieldNameSizeLimit = (limits && typeof limits.fieldNameSize === 'number' ? limits.fieldNameSize : 100); this.fieldsLimit = (limits && typeof limits.fields === 'number' ? limits.fields : Infinity); var charset; for (var i = 0, len = parsedConType.length; i < len; ++i) { if (Array.isArray(parsedConType[i]) && RE_CHARSET.test(parsedConType[i][0])) { charset = parsedConType[i][1].toLowerCase(); break; } } if (charset === undefined) charset = cfg.defCharset || 'utf8'; this.decoder = new Decoder(); this.charset = charset; this._fields = 0; this._state = 'key'; this._checkingBytes = true; this._bytesKey = 0; this._bytesVal = 0; this._key = ''; this._val = ''; this._keyTrunc = false; this._valTrunc = false; this._hitlimit = false; } UrlEncoded.prototype.write = function(data, cb) { if (this._fields === this.fieldsLimit) { if (!this.boy.hitFieldsLimit) { this.boy.hitFieldsLimit = true; this.boy.emit('fieldsLimit'); } return cb(); } var idxeq, idxamp, i, p = 0, len = data.length; while (p < len) { if (this._state === 'key') { idxeq = idxamp = undefined; for (i = p; i < len; ++i) { if (!this._checkingBytes) ++p; if (data[i] === 0x3D/*=*/) { idxeq = i; break; } else if (data[i] === 0x26/*&*/) { idxamp = i; break; } if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) { this._hitLimit = true; break; } else if (this._checkingBytes) ++this._bytesKey; } if (idxeq !== undefined) { // key with assignment if (idxeq > p) this._key += this.decoder.write(data.toString('binary', p, idxeq)); this._state = 'val'; this._hitLimit = false; this._checkingBytes = true; this._val = ''; this._bytesVal = 0; this._valTrunc = false; this.decoder.reset(); p = idxeq + 1; } else if (idxamp !== undefined) { // key with no assignment ++this._fields; var key, keyTrunc = this._keyTrunc; if (idxamp > p) key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))); else key = this._key; this._hitLimit = false; this._checkingBytes = true; this._key = ''; this._bytesKey = 0; this._keyTrunc = false; this.decoder.reset(); if (key.length) { this.boy.emit('field', decodeText(key, 'binary', this.charset), '', keyTrunc, false); } p = idxamp + 1; if (this._fields === this.fieldsLimit) return cb(); } else if (this._hitLimit) { // we may not have hit the actual limit if there are encoded bytes... if (i > p) this._key += this.decoder.write(data.toString('binary', p, i)); p = i; if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) { // yep, we actually did hit the limit this._checkingBytes = false; this._keyTrunc = true; } } else { if (p < len) this._key += this.decoder.write(data.toString('binary', p)); p = len; } } else { idxamp = undefined; for (i = p; i < len; ++i) { if (!this._checkingBytes) ++p; if (data[i] === 0x26/*&*/) { idxamp = i; break; } if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) { this._hitLimit = true; break; } else if (this._checkingBytes) ++this._bytesVal; } if (idxamp !== undefined) { ++this._fields; if (idxamp > p) this._val += this.decoder.write(data.toString('binary', p, idxamp)); this.boy.emit('field', decodeText(this._key, 'binary', this.charset), decodeText(this._val, 'binary', this.charset), this._keyTrunc, this._valTrunc); this._state = 'key'; this._hitLimit = false; this._checkingBytes = true; this._key = ''; this._bytesKey = 0; this._keyTrunc = false; this.decoder.reset(); p = idxamp + 1; if (this._fields === this.fieldsLimit) return cb(); } else if (this._hitLimit) { // we may not have hit the actual limit if there are encoded bytes... if (i > p) this._val += this.decoder.write(data.toString('binary', p, i)); p = i; if ((this._val === '' && this.fieldSizeLimit === 0) || (this._bytesVal = this._val.length) === this.fieldSizeLimit) { // yep, we actually did hit the limit this._checkingBytes = false; this._valTrunc = true; } } else { if (p < len) this._val += this.decoder.write(data.toString('binary', p)); p = len; } } } cb(); }; UrlEncoded.prototype.end = function() { if (this.boy._done) return; if (this._state === 'key' && this._key.length > 0) { this.boy.emit('field', decodeText(this._key, 'binary', this.charset), '', this._keyTrunc, false); } else if (this._state === 'val') { this.boy.emit('field', decodeText(this._key, 'binary', this.charset), decodeText(this._val, 'binary', this.charset), this._keyTrunc, this._valTrunc); } this.boy._done = true; this.boy.emit('finish'); }; module.exports = UrlEncoded;