diff --git a/deps/dicer/lib/Dicer.js b/deps/dicer/lib/Dicer.js index fc50a50..483cff8 100644 --- a/deps/dicer/lib/Dicer.js +++ b/deps/dicer/lib/Dicer.js @@ -1,3 +1,5 @@ +'use strict' + const WritableStream = require('stream').Writable const inherits = require('util').inherits diff --git a/deps/dicer/lib/HeaderParser.js b/deps/dicer/lib/HeaderParser.js index a11fd90..2ec8fb2 100644 --- a/deps/dicer/lib/HeaderParser.js +++ b/deps/dicer/lib/HeaderParser.js @@ -1,6 +1,8 @@ +'use strict' + const EventEmitter = require('events').EventEmitter const inherits = require('util').inherits -const getLimit = require('../../../lib/utils').getLimit +const getLimit = require('../../../lib/utils/getLimit') const StreamSearch = require('../../streamsearch/sbmh') diff --git a/deps/dicer/lib/PartStream.js b/deps/dicer/lib/PartStream.js index c969bda..d5c0dfe 100644 --- a/deps/dicer/lib/PartStream.js +++ b/deps/dicer/lib/PartStream.js @@ -1,3 +1,5 @@ +'use strict' + const inherits = require('util').inherits const ReadableStream = require('stream').Readable diff --git a/deps/streamsearch/sbmh.js b/deps/streamsearch/sbmh.js index 7f79e59..21745ac 100644 --- a/deps/streamsearch/sbmh.js +++ b/deps/streamsearch/sbmh.js @@ -1,3 +1,5 @@ +'use strict' + /** * Copyright Brian White. All rights reserved. * diff --git a/lib/main.js b/lib/main.js index b0fa9f6..52206d2 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,10 +1,12 @@ +'use strict' + const WritableStream = require('stream').Writable const { inherits } = require('util') const Dicer = require('../deps/dicer/lib/Dicer') const MultipartParser = require('./types/multipart') const UrlencodedParser = require('./types/urlencoded') -const parseParams = require('./utils').parseParams +const parseParams = require('./utils/parseParams') function Busboy (opts) { if (!(this instanceof Busboy)) { return new Busboy(opts) } diff --git a/lib/types/multipart.js b/lib/types/multipart.js index 1b3cb01..e24905c 100644 --- a/lib/types/multipart.js +++ b/lib/types/multipart.js @@ -1,3 +1,5 @@ +'use strict' + // TODO: // * support 1 nested multipart level // (see second multipart example here: @@ -10,10 +12,10 @@ const inherits = require('util').inherits const Dicer = require('../../deps/dicer/lib/Dicer') -const parseParams = require('../utils').parseParams -const decodeText = require('../utils').decodeText -const basename = require('../utils').basename -const getLimit = require('../utils').getLimit +const parseParams = require('../utils/parseParams') +const decodeText = require('../utils/decodeText') +const basename = require('../utils/basename') +const getLimit = require('../utils/getLimit') const RE_BOUNDARY = /^boundary$/i const RE_FIELD = /^form-data$/i diff --git a/lib/types/urlencoded.js b/lib/types/urlencoded.js index a83684e..6f5f784 100644 --- a/lib/types/urlencoded.js +++ b/lib/types/urlencoded.js @@ -1,6 +1,8 @@ -const Decoder = require('../utils').Decoder -const decodeText = require('../utils').decodeText -const getLimit = require('../utils').getLimit +'use strict' + +const Decoder = require('../utils/Decoder') +const decodeText = require('../utils/decodeText') +const getLimit = require('../utils/getLimit') const RE_CHARSET = /^charset$/i diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 1b2bd5c..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,198 +0,0 @@ -const { TextDecoder } = require('util') -const { TextDecoder: PolyfillTextDecoder, getEncoding } = require('text-decoding') - -const RE_ENCODED = /%([a-fA-F0-9]{2})/g -const RE_PLUS = /\+/g - -const HEX = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -] - -// Node has always utf-8 -const textDecoders = new Map() -textDecoders.set('utf-8', new TextDecoder('utf-8')) -textDecoders.set('utf8', textDecoders.get('utf-8')) - -function encodedReplacer (match, byte) { - return String.fromCharCode(parseInt(byte, 16)) -} - -function parseParams (str) { - const res = [] - let state = 'key' - let charset = '' - let inquote = false - let escaping = false - let p = 0 - let tmp = '' - - for (var i = 0, len = str.length; i < len; ++i) { // eslint-disable-line no-var - const char = str[i] - if (char === '\\' && inquote) { - if (escaping) { escaping = false } else { - escaping = true - continue - } - } else if (char === '"') { - if (!escaping) { - if (inquote) { - inquote = false - state = 'key' - } else { inquote = true } - continue - } else { escaping = false } - } else { - if (escaping && inquote) { tmp += '\\' } - escaping = false - if ((state === 'charset' || state === 'lang') && char === "'") { - if (state === 'charset') { - state = 'lang' - charset = tmp.substring(1) - } else { state = 'value' } - tmp = '' - continue - } else if (state === 'key' && - (char === '*' || char === '=') && - res.length) { - if (char === '*') { state = 'charset' } else { state = 'value' } - res[p] = [tmp, undefined] - tmp = '' - continue - } else if (!inquote && char === ';') { - state = 'key' - if (charset) { - if (tmp.length) { - tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), - 'binary', - charset) - } - charset = '' - } else if (tmp.length) { - tmp = decodeText(tmp, 'binary', 'utf8') - } - if (res[p] === undefined) { res[p] = tmp } else { res[p][1] = tmp } - tmp = '' - ++p - continue - } else if (!inquote && (char === ' ' || char === '\t')) { continue } - } - tmp += char - } - if (charset && tmp.length) { - tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), - 'binary', - charset) - } else if (tmp) { - tmp = decodeText(tmp, 'binary', 'utf8') - } - - if (res[p] === undefined) { - if (tmp) { res[p] = tmp } - } else { res[p][1] = tmp } - - return res -} - -function decodeText (text, textEncoding, destEncoding) { - if (text) { - if (textDecoders.has(destEncoding)) { - try { - return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) - } catch (e) { } - } else { - try { - textDecoders.set(destEncoding, new TextDecoder(destEncoding)) - return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) - } catch (e) { - if (getEncoding(destEncoding)) { - try { - textDecoders.set(destEncoding, new PolyfillTextDecoder(destEncoding)) - return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) - } catch (e) { } - } - } - } - } - return text -} - -function Decoder () { - this.buffer = undefined -} -Decoder.prototype.write = function (str) { - // Replace '+' with ' ' before decoding - str = str.replace(RE_PLUS, ' ') - let res = '' - let i = 0; let p = 0; const len = str.length - for (; i < len; ++i) { - if (this.buffer !== undefined) { - if (!HEX[str.charCodeAt(i)]) { - res += '%' + this.buffer - this.buffer = undefined - --i // retry character - } else { - this.buffer += str[i] - ++p - if (this.buffer.length === 2) { - res += String.fromCharCode(parseInt(this.buffer, 16)) - this.buffer = undefined - } - } - } else if (str[i] === '%') { - if (i > p) { - res += str.substring(p, i) - p = i - } - this.buffer = '' - ++p - } - } - if (p < len && this.buffer === undefined) { res += str.substring(p) } - return res -} -Decoder.prototype.reset = function () { - this.buffer = undefined -} - -function basename (path) { - if (typeof path !== 'string') { return '' } - for (var i = path.length - 1; i >= 0; --i) { // eslint-disable-line no-var - switch (path.charCodeAt(i)) { - case 0x2F: // '/' - case 0x5C: // '\' - path = path.slice(i + 1) - return (path === '..' || path === '.' ? '' : path) - } - } - return (path === '..' || path === '.' ? '' : path) -} - -function getLimit (limits, name, defaultLimit) { - if ( - !limits || - limits[name] === undefined || - limits[name] === null - ) { return defaultLimit } - - if ( - typeof limits[name] !== 'number' || - isNaN(limits[name]) - ) { throw new TypeError('Limit ' + name + ' is not a valid number') } - - return limits[name] -} - -module.exports = { - Decoder, - basename, - getLimit, - parseParams, - decodeText -} diff --git a/lib/utils/Decoder.js b/lib/utils/Decoder.js new file mode 100644 index 0000000..7917678 --- /dev/null +++ b/lib/utils/Decoder.js @@ -0,0 +1,54 @@ +'use strict' + +const RE_PLUS = /\+/g + +const HEX = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +] + +function Decoder () { + this.buffer = undefined +} +Decoder.prototype.write = function (str) { + // Replace '+' with ' ' before decoding + str = str.replace(RE_PLUS, ' ') + let res = '' + let i = 0; let p = 0; const len = str.length + for (; i < len; ++i) { + if (this.buffer !== undefined) { + if (!HEX[str.charCodeAt(i)]) { + res += '%' + this.buffer + this.buffer = undefined + --i // retry character + } else { + this.buffer += str[i] + ++p + if (this.buffer.length === 2) { + res += String.fromCharCode(parseInt(this.buffer, 16)) + this.buffer = undefined + } + } + } else if (str[i] === '%') { + if (i > p) { + res += str.substring(p, i) + p = i + } + this.buffer = '' + ++p + } + } + if (p < len && this.buffer === undefined) { res += str.substring(p) } + return res +} +Decoder.prototype.reset = function () { + this.buffer = undefined +} + +module.exports = Decoder diff --git a/lib/utils/basename.js b/lib/utils/basename.js new file mode 100644 index 0000000..db58819 --- /dev/null +++ b/lib/utils/basename.js @@ -0,0 +1,14 @@ +'use strict' + +module.exports = function basename (path) { + if (typeof path !== 'string') { return '' } + for (var i = path.length - 1; i >= 0; --i) { // eslint-disable-line no-var + switch (path.charCodeAt(i)) { + case 0x2F: // '/' + case 0x5C: // '\' + path = path.slice(i + 1) + return (path === '..' || path === '.' ? '' : path) + } + } + return (path === '..' || path === '.' ? '' : path) +} diff --git a/lib/utils/decodeText.js b/lib/utils/decodeText.js new file mode 100644 index 0000000..ec47716 --- /dev/null +++ b/lib/utils/decodeText.js @@ -0,0 +1,60 @@ +'use strict' + +let TextDecoder +try { + TextDecoder = require('util').TextDecoder +} catch (e) { } + +const { TextDecoder: PolyfillTextDecoder, getEncoding } = require('text-decoding') + +// Node has always utf-8 +const textDecoders = new Map() +if (TextDecoder) { + textDecoders.set('utf-8', new TextDecoder('utf-8')) +} else { + textDecoders.set('utf-8', new PolyfillTextDecoder('utf-8')) +} +textDecoders.set('utf8', textDecoders.get('utf-8')) + +function decodeText (text, textEncoding, destEncoding) { + if (text) { + if (textDecoders.has(destEncoding)) { + try { + return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) + } catch (e) { } + } else { + try { + textDecoders.set(destEncoding, new TextDecoder(destEncoding)) + return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) + } catch (e) { + if (getEncoding(destEncoding)) { + try { + textDecoders.set(destEncoding, new PolyfillTextDecoder(destEncoding)) + return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) + } catch (e) { } + } + } + } + } + return text +} + +function decodeTextPolyfill (text, textEncoding, destEncoding) { + if (text) { + if (textDecoders.has(destEncoding)) { + try { + return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) + } catch (e) { } + } else { + if (getEncoding(destEncoding)) { + try { + textDecoders.set(destEncoding, new PolyfillTextDecoder(destEncoding)) + return textDecoders.get(destEncoding).decode(Buffer.from(text, textEncoding)) + } catch (e) { } + } + } + } + return text +} + +module.exports = TextDecoder ? decodeText : decodeTextPolyfill diff --git a/lib/utils/getLimit.js b/lib/utils/getLimit.js new file mode 100644 index 0000000..cb64fd6 --- /dev/null +++ b/lib/utils/getLimit.js @@ -0,0 +1,16 @@ +'use strict' + +module.exports = function getLimit (limits, name, defaultLimit) { + if ( + !limits || + limits[name] === undefined || + limits[name] === null + ) { return defaultLimit } + + if ( + typeof limits[name] !== 'number' || + isNaN(limits[name]) + ) { throw new TypeError('Limit ' + name + ' is not a valid number') } + + return limits[name] +} diff --git a/lib/utils/parseParams.js b/lib/utils/parseParams.js new file mode 100644 index 0000000..f921418 --- /dev/null +++ b/lib/utils/parseParams.js @@ -0,0 +1,87 @@ +'use strict' + +const decodeText = require('./decodeText') + +const RE_ENCODED = /%([a-fA-F0-9]{2})/g + +function encodedReplacer (match, byte) { + return String.fromCharCode(parseInt(byte, 16)) +} + +function parseParams (str) { + const res = [] + let state = 'key' + let charset = '' + let inquote = false + let escaping = false + let p = 0 + let tmp = '' + + for (var i = 0, len = str.length; i < len; ++i) { // eslint-disable-line no-var + const char = str[i] + if (char === '\\' && inquote) { + if (escaping) { escaping = false } else { + escaping = true + continue + } + } else if (char === '"') { + if (!escaping) { + if (inquote) { + inquote = false + state = 'key' + } else { inquote = true } + continue + } else { escaping = false } + } else { + if (escaping && inquote) { tmp += '\\' } + escaping = false + if ((state === 'charset' || state === 'lang') && char === "'") { + if (state === 'charset') { + state = 'lang' + charset = tmp.substring(1) + } else { state = 'value' } + tmp = '' + continue + } else if (state === 'key' && + (char === '*' || char === '=') && + res.length) { + if (char === '*') { state = 'charset' } else { state = 'value' } + res[p] = [tmp, undefined] + tmp = '' + continue + } else if (!inquote && char === ';') { + state = 'key' + if (charset) { + if (tmp.length) { + tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), + 'binary', + charset) + } + charset = '' + } else if (tmp.length) { + tmp = decodeText(tmp, 'binary', 'utf8') + } + if (res[p] === undefined) { res[p] = tmp } else { res[p][1] = tmp } + tmp = '' + ++p + continue + } else if (!inquote && (char === ' ' || char === '\t')) { continue } + } + tmp += char + } + if (charset && tmp.length) { + tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), + 'binary', + charset) + } else if (tmp) { + tmp = decodeText(tmp, 'binary', 'utf8') + } + + if (res[p] === undefined) { + if (tmp) { res[p] = tmp } + } else { res[p][1] = tmp } + + return res +} + +module.exports = parseParams diff --git a/test/decoder.spec.js b/test/decoder.spec.js index 3a70756..99c551a 100644 --- a/test/decoder.spec.js +++ b/test/decoder.spec.js @@ -1,5 +1,5 @@ const { assert, expect } = require('chai') -const { Decoder } = require('../lib/utils') +const Decoder = require('../lib/utils/Decoder') describe('Decoder', () => { [ diff --git a/test/get-limit.spec.js b/test/get-limit.spec.js index 39e7cb6..7586f73 100644 --- a/test/get-limit.spec.js +++ b/test/get-limit.spec.js @@ -1,4 +1,4 @@ -const { getLimit } = require('../lib/utils') +const getLimit = require('../lib/utils/getLimit') const { assert } = require('chai') describe('Get limit', () => { diff --git a/test/parse-params.spec.js b/test/parse-params.spec.js index a4a9e4f..1032d14 100644 --- a/test/parse-params.spec.js +++ b/test/parse-params.spec.js @@ -1,6 +1,6 @@ const { inspect } = require('util') const { assert } = require('chai') -const { parseParams } = require('../lib/utils') +const parseParams = require('../lib/utils/parseParams') describe('parse-params', () => { [