diff options
Diffstat (limited to 'debian/tests/test_modules/dicer/lib')
-rw-r--r-- | debian/tests/test_modules/dicer/lib/Dicer.js | 239 | ||||
-rw-r--r-- | debian/tests/test_modules/dicer/lib/HeaderParser.js | 110 | ||||
-rw-r--r-- | debian/tests/test_modules/dicer/lib/PartStream.js | 11 |
3 files changed, 360 insertions, 0 deletions
diff --git a/debian/tests/test_modules/dicer/lib/Dicer.js b/debian/tests/test_modules/dicer/lib/Dicer.js new file mode 100644 index 0000000..9d580cb --- /dev/null +++ b/debian/tests/test_modules/dicer/lib/Dicer.js @@ -0,0 +1,239 @@ +var WritableStream = require('stream').Writable, + inherits = require('util').inherits; + +var StreamSearch = require('streamsearch'); + +var PartStream = require('./PartStream'), + HeaderParser = require('./HeaderParser'); + +var DASH = 45, + B_ONEDASH = Buffer.from('-'), + B_CRLF = Buffer.from('\r\n'), + EMPTY_FN = function() {}; + +function Dicer(cfg) { + if (!(this instanceof Dicer)) + return new Dicer(cfg); + WritableStream.call(this, cfg); + + if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) + throw new TypeError('Boundary required'); + + if (typeof cfg.boundary === 'string') + this.setBoundary(cfg.boundary); + else + this._bparser = undefined; + + this._headerFirst = cfg.headerFirst; + + var self = this; + + this._dashes = 0; + this._parts = 0; + this._finished = false; + this._realFinish = false; + this._isPreamble = true; + this._justMatched = false; + this._firstWrite = true; + this._inHeader = true; + this._part = undefined; + this._cb = undefined; + this._ignoreData = false; + this._partOpts = (typeof cfg.partHwm === 'number' + ? { highWaterMark: cfg.partHwm } + : {}); + this._pause = false; + + this._hparser = new HeaderParser(cfg); + this._hparser.on('header', function(header) { + self._inHeader = false; + self._part.emit('header', header); + }); + +} +inherits(Dicer, WritableStream); + +Dicer.prototype.emit = function(ev) { + if (ev === 'finish' && !this._realFinish) { + if (!this._finished) { + var self = this; + process.nextTick(function() { + self.emit('error', new Error('Unexpected end of multipart data')); + if (self._part && !self._ignoreData) { + var type = (self._isPreamble ? 'Preamble' : 'Part'); + self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data')); + self._part.push(null); + process.nextTick(function() { + self._realFinish = true; + self.emit('finish'); + self._realFinish = false; + }); + return; + } + self._realFinish = true; + self.emit('finish'); + self._realFinish = false; + }); + } + } else + WritableStream.prototype.emit.apply(this, arguments); +}; + +Dicer.prototype._write = function(data, encoding, cb) { + // ignore unexpected data (e.g. extra trailer data after finished) + if (!this._hparser && !this._bparser) + return cb(); + + if (this._headerFirst && this._isPreamble) { + if (!this._part) { + this._part = new PartStream(this._partOpts); + if (this._events.preamble) + this.emit('preamble', this._part); + else + this._ignore(); + } + var r = this._hparser.push(data); + if (!this._inHeader && r !== undefined && r < data.length) + data = data.slice(r); + else + return cb(); + } + + // allows for "easier" testing + if (this._firstWrite) { + this._bparser.push(B_CRLF); + this._firstWrite = false; + } + + this._bparser.push(data); + + if (this._pause) + this._cb = cb; + else + cb(); +}; + +Dicer.prototype.reset = function() { + this._part = undefined; + this._bparser = undefined; + this._hparser = undefined; +}; + +Dicer.prototype.setBoundary = function(boundary) { + var self = this; + this._bparser = new StreamSearch('\r\n--' + boundary); + this._bparser.on('info', function(isMatch, data, start, end) { + self._oninfo(isMatch, data, start, end); + }); +}; + +Dicer.prototype._ignore = function() { + if (this._part && !this._ignoreData) { + this._ignoreData = true; + this._part.on('error', EMPTY_FN); + // we must perform some kind of read on the stream even though we are + // ignoring the data, otherwise node's Readable stream will not emit 'end' + // after pushing null to the stream + this._part.resume(); + } +}; + +Dicer.prototype._oninfo = function(isMatch, data, start, end) { + var buf, self = this, i = 0, r, ev, shouldWriteMore = true; + + if (!this._part && this._justMatched && data) { + while (this._dashes < 2 && (start + i) < end) { + if (data[start + i] === DASH) { + ++i; + ++this._dashes; + } else { + if (this._dashes) + buf = B_ONEDASH; + this._dashes = 0; + break; + } + } + if (this._dashes === 2) { + if ((start + i) < end && this._events.trailer) + this.emit('trailer', data.slice(start + i, end)); + this.reset(); + this._finished = true; + // no more parts will be added + if (self._parts === 0) { + self._realFinish = true; + self.emit('finish'); + self._realFinish = false; + } + } + if (this._dashes) + return; + } + if (this._justMatched) + this._justMatched = false; + if (!this._part) { + this._part = new PartStream(this._partOpts); + this._part._read = function(n) { + self._unpause(); + }; + ev = this._isPreamble ? 'preamble' : 'part'; + if (this._events[ev]) + this.emit(ev, this._part); + else + this._ignore(); + if (!this._isPreamble) + this._inHeader = true; + } + if (data && start < end && !this._ignoreData) { + if (this._isPreamble || !this._inHeader) { + if (buf) + shouldWriteMore = this._part.push(buf); + shouldWriteMore = this._part.push(data.slice(start, end)); + if (!shouldWriteMore) + this._pause = true; + } else if (!this._isPreamble && this._inHeader) { + if (buf) + this._hparser.push(buf); + r = this._hparser.push(data.slice(start, end)); + if (!this._inHeader && r !== undefined && r < end) + this._oninfo(false, data, start + r, end); + } + } + if (isMatch) { + this._hparser.reset(); + if (this._isPreamble) + this._isPreamble = false; + else { + ++this._parts; + this._part.on('end', function() { + if (--self._parts === 0) { + if (self._finished) { + self._realFinish = true; + self.emit('finish'); + self._realFinish = false; + } else { + self._unpause(); + } + } + }); + } + this._part.push(null); + this._part = undefined; + this._ignoreData = false; + this._justMatched = true; + this._dashes = 0; + } +}; + +Dicer.prototype._unpause = function() { + if (!this._pause) + return; + + this._pause = false; + if (this._cb) { + var cb = this._cb; + this._cb = undefined; + cb(); + } +}; + +module.exports = Dicer; diff --git a/debian/tests/test_modules/dicer/lib/HeaderParser.js b/debian/tests/test_modules/dicer/lib/HeaderParser.js new file mode 100644 index 0000000..8ccb6e5 --- /dev/null +++ b/debian/tests/test_modules/dicer/lib/HeaderParser.js @@ -0,0 +1,110 @@ +var EventEmitter = require('events').EventEmitter, + inherits = require('util').inherits; + +var StreamSearch = require('streamsearch'); + +var B_DCRLF = Buffer.from('\r\n\r\n'), + RE_CRLF = /\r\n/g, + RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/, + MAX_HEADER_PAIRS = 2000, // from node's http.js + MAX_HEADER_SIZE = 80 * 1024; // from node's http_parser + +function HeaderParser(cfg) { + EventEmitter.call(this); + + var self = this; + this.nread = 0; + this.maxed = false; + this.npairs = 0; + this.maxHeaderPairs = (cfg && typeof cfg.maxHeaderPairs === 'number' + ? cfg.maxHeaderPairs + : MAX_HEADER_PAIRS); + this.buffer = ''; + this.header = {}; + this.finished = false; + this.ss = new StreamSearch(B_DCRLF); + this.ss.on('info', function(isMatch, data, start, end) { + if (data && !self.maxed) { + if (self.nread + (end - start) > MAX_HEADER_SIZE) { + end = (MAX_HEADER_SIZE - self.nread); + self.nread = MAX_HEADER_SIZE; + } else + self.nread += (end - start); + + if (self.nread === MAX_HEADER_SIZE) + self.maxed = true; + + self.buffer += data.toString('binary', start, end); + } + if (isMatch) + self._finish(); + }); +} +inherits(HeaderParser, EventEmitter); + +HeaderParser.prototype.push = function(data) { + var r = this.ss.push(data); + if (this.finished) + return r; +}; + +HeaderParser.prototype.reset = function() { + this.finished = false; + this.buffer = ''; + this.header = {}; + this.ss.reset(); +}; + +HeaderParser.prototype._finish = function() { + if (this.buffer) + this._parseHeader(); + this.ss.matches = this.ss.maxMatches; + var header = this.header; + this.header = {}; + this.buffer = ''; + this.finished = true; + this.nread = this.npairs = 0; + this.maxed = false; + this.emit('header', header); +}; + +HeaderParser.prototype._parseHeader = function() { + if (this.npairs === this.maxHeaderPairs) + return; + + var lines = this.buffer.split(RE_CRLF), len = lines.length, m, h, + modded = false; + + for (var i = 0; i < len; ++i) { + if (lines[i].length === 0) + continue; + if (lines[i][0] === '\t' || lines[i][0] === ' ') { + // folded header content + // RFC2822 says to just remove the CRLF and not the whitespace following + // it, so we follow the RFC and include the leading whitespace ... + this.header[h][this.header[h].length - 1] += lines[i]; + } else { + m = RE_HDR.exec(lines[i]); + if (m) { + h = m[1].toLowerCase(); + if (m[2]) { + if (this.header[h] === undefined) + this.header[h] = [m[2]]; + else + this.header[h].push(m[2]); + } else + this.header[h] = ['']; + if (++this.npairs === this.maxHeaderPairs) + break; + } else { + this.buffer = lines[i]; + modded = true; + break; + } + } + } + if (!modded) + this.buffer = ''; +}; + +module.exports = HeaderParser; diff --git a/debian/tests/test_modules/dicer/lib/PartStream.js b/debian/tests/test_modules/dicer/lib/PartStream.js new file mode 100644 index 0000000..b646ac0 --- /dev/null +++ b/debian/tests/test_modules/dicer/lib/PartStream.js @@ -0,0 +1,11 @@ +var inherits = require('util').inherits, + ReadableStream = require('stream').Readable; + +function PartStream(opts) { + ReadableStream.call(this, opts); +} +inherits(PartStream, ReadableStream); + +PartStream.prototype._read = function(n) {}; + +module.exports = PartStream; |