Initial commit

This commit is contained in:
talksik
2021-12-29 01:57:42 -08:00
commit ce39a60b42
4634 changed files with 997667 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
'use strict';
module.exports = {
extends: '@mscdex/eslint-config',
};
+24
View File
@@ -0,0 +1,24 @@
name: CI
on:
pull_request:
push:
branches: [ master ]
jobs:
tests-linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [10.x, 12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install module
run: npm install
- name: Run tests
run: npm test
+23
View File
@@ -0,0 +1,23 @@
name: lint
on:
pull_request:
push:
branches: [ master ]
env:
NODE_VERSION: 16.x
jobs:
lint-js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install ESLint + ESLint configs/plugins
run: npm install --only=dev
- name: Lint files
run: npm run lint
Generated Vendored
+19
View File
@@ -0,0 +1,19 @@
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
+126
View File
@@ -0,0 +1,126 @@
Description
===========
A very fast streaming multipart parser for node.js.
Benchmarks can be found [here](https://github.com/mscdex/dicer/wiki/Benchmarks).
Requirements
============
* [node.js](http://nodejs.org/) -- v10.0.0 or newer
Install
============
npm install dicer
Examples
========
* Parse an HTTP form upload
```js
const { inspect } = require('util');
const http = require('http');
const Dicer = require('dicer');
// Quick and dirty way to parse multipart boundary
const RE_BOUNDARY =
/^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i;
const HTML = Buffer.from(`
<html><head></head><body>
<form method="POST" enctype="multipart/form-data">
<input type="text" name="textfield"><br />
<input type="file" name="filefield"><br />
<input type="submit">
</form>
</body></html>
`);
const PORT = 8080;
http.createServer((req, res) => {
let m;
if (req.method === 'POST'
&& req.headers['content-type']
&& (m = RE_BOUNDARY.exec(req.headers['content-type']))) {
const d = new Dicer({ boundary: m[1] || m[2] });
d.on('part', (p) => {
console.log('New part!');
p.on('header', (header) => {
for (const h in header) {
console.log(
`Part header: k: ${inspect(h)}, v: ${inspect(header[h])}`
);
}
});
p.on('data', (data) => {
console.log(`Part data: ${inspect(data.toString())}`);
});
p.on('end', () => {
console.log('End of part\n');
});
});
d.on('finish', () => {
console.log('End of parts');
res.writeHead(200);
res.end('Form submission successful!');
});
req.pipe(d);
} else if (req.method === 'GET' && req.url === '/') {
res.writeHead(200);
res.end(HTML);
} else {
res.writeHead(404);
res.end();
}
}).listen(PORT, () => {
console.log(`Listening for requests on port ${PORT}`);
});
```
API
===
_Dicer_ is a _Writable_ stream
Dicer (special) events
----------------------
* **finish**() - Emitted when all parts have been parsed and the Dicer instance has been ended.
* **part**(< _PartStream_ >stream) - Emitted when a new part has been found.
* **preamble**(< _PartStream_ >stream) - Emitted for preamble if you should happen to need it (can usually be ignored).
* **trailer**(< _Buffer_ >data) - Emitted when trailing data was found after the terminating boundary (as with the preamble, this can usually be ignored too).
Dicer methods
-------------
* **(constructor)**(< _object_ >config) - Creates and returns a new Dicer instance with the following valid `config` settings:
* **boundary** - _string_ - This is the boundary used to detect the beginning of a new part.
* **headerFirst** - _boolean_ - If true, preamble header parsing will be performed first.
* **maxHeaderPairs** - _integer_ - The maximum number of header key=>value pairs to parse **Default:** 2000 (same as node's http).
* **setBoundary**(< _string_ >boundary) - _(void)_ - Sets the boundary to use for parsing and performs some initialization needed for parsing. You should only need to use this if you set `headerFirst` to true in the constructor and are parsing the boundary from the preamble header.
_PartStream_ is a _Readable_ stream
PartStream (special) events
---------------------------
* **header**(< _object_ >header) - An object containing the header for this particular part. Each property value is an _array_ of one or more string values.
+142
View File
@@ -0,0 +1,142 @@
'use strict';
function createMultipartBuffer(boundary, sizes) {
const bufs = [];
for (let i = 0; i < sizes.length; ++i) {
const mb = sizes[i] * 1024 * 1024;
bufs.push(Buffer.from([
`--${boundary}`,
`content-disposition: form-data; name="field${i + 1}"`,
'',
'0'.repeat(mb),
`--${boundary}--`,
'',
].join('\r\n')));
}
return Buffer.concat(bufs);
}
const boundary = '-----------------------------168072824752491622650073';
const buffer = createMultipartBuffer(boundary, [
10,
10,
10,
20,
50,
]);
const calls = {
partBegin: 0,
headerField: 0,
headerValue: 0,
headerEnd: 0,
headersEnd: 0,
partData: 0,
partEnd: 0,
end: 0,
};
const moduleName = process.argv[2];
switch (moduleName) {
case 'dicer': {
const Dicer = require('..');
const parser = new Dicer({ boundary });
parser.on('part', (p) => {
++calls.partBegin;
p.on('header', (header) => {
++calls.headersEnd;
});
p.on('data', (data) => {
++calls.partData;
});
p.on('end', () => {
++calls.partEnd;
});
}).on('end', () => {
++calls.end;
});
console.time(moduleName);
parser.write(buffer);
console.timeEnd(moduleName);
break;
}
case 'formidable': {
const { MultipartParser } = require('formidable');
const parser = new MultipartParser();
parser.initWithBoundary(boundary);
parser.on('data', ({ name }) => {
++calls[name];
});
console.time(moduleName);
parser.write(buffer);
console.timeEnd(moduleName);
break;
}
case 'multiparty': {
const { Readable } = require('stream');
const { Form } = require('multiparty');
const form = new Form({
maxFieldsSize: Infinity,
maxFields: Infinity,
maxFilesSize: Infinity,
autoFields: false,
autoFiles: false,
});
const req = new Readable({ read: () => {} });
req.headers = {
'content-type': `multipart/form-data; boundary=${boundary}`,
};
req.push(buffer);
req.push(null);
function hijack(name, fn) {
const oldFn = form[name];
form[name] = function() {
fn();
return oldFn.apply(this, arguments);
};
}
hijack('onParseHeaderField', () => {
++calls.headerField;
});
hijack('onParseHeaderValue', () => {
++calls.headerValue;
});
hijack('onParsePartBegin', () => {
++calls.partBegin;
});
hijack('onParsePartData', () => {
++calls.partData;
});
hijack('onParsePartEnd', () => {
++calls.partEnd;
});
form.on('close', () => {
++calls.end;
console.timeEnd(moduleName);
}).on('part', (p) => p.resume());
console.time(moduleName);
form.parse(req);
break;
}
default:
if (moduleName === undefined)
console.error('Missing parser module name');
else
console.error(`Invalid parser module name: ${moduleName}`);
process.exit(1);
}
+250
View File
@@ -0,0 +1,250 @@
'use strict';
const { Writable } = require('stream');
const StreamSearch = require('streamsearch');
const PartStream = require('./PartStream');
const HeaderParser = require('./HeaderParser');
const DASH = 45;
const B_ONEDASH = Buffer.from('-');
const B_CRLF = Buffer.from('\r\n');
const EMPTY_FN = () => {};
class Dicer extends Writable {
constructor(cfg) {
super(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;
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', (header) => {
this._inHeader = false;
this._part.emit('header', header);
});
this._hparser.on('error', (err) => {
if (this._part && !this._ignoreData) {
this._part.emit('error', err);
this._part.push(null);
}
});
}
emit(ev) {
if (ev !== 'finish' || this._realFinish) {
Writable.prototype.emit.apply(this, arguments);
return;
}
if (this._finished)
return;
process.nextTick(() => {
this.emit('error', new Error('Unexpected end of multipart data'));
if (this._part && !this._ignoreData) {
const type = (this._isPreamble ? 'Preamble' : 'Part');
this._part.emit(
'error',
new Error(`${type} terminated early due to `
+ 'unexpected end of multipart data')
);
this._part.push(null);
process.nextTick(() => {
this._realFinish = true;
this.emit('finish');
this._realFinish = false;
});
return;
}
this._realFinish = true;
this.emit('finish');
this._realFinish = false;
});
}
_write(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
ignore(this);
}
const 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();
}
reset() {
this._part = undefined;
this._bparser = undefined;
this._hparser = undefined;
}
setBoundary(boundary) {
this._bparser = new StreamSearch(`\r\n--${boundary}`, onInfo.bind(this));
}
}
function onInfo(isMatch, data, start, end) {
let buf;
let i = 0;
let r;
let ev;
let 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 (this._parts === 0) {
this._realFinish = true;
this.emit('finish');
this._realFinish = false;
}
}
if (this._dashes)
return;
}
if (this._justMatched)
this._justMatched = false;
if (!this._part) {
this._part = new PartStream(this._partOpts);
this._part._read = (n) => {
unpause(this);
};
ev = this._isPreamble ? 'preamble' : 'part';
if (this._events[ev])
this.emit(ev, this._part);
else
ignore(this);
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)
onInfo.call(this, false, data, start + r, end);
}
}
if (isMatch) {
this._hparser.reset();
if (this._isPreamble) {
this._isPreamble = false;
} else {
++this._parts;
this._part.on('end', () => {
if (--this._parts === 0) {
if (this._finished) {
this._realFinish = true;
this.emit('finish');
this._realFinish = false;
} else {
unpause(this);
}
}
});
}
this._part.push(null);
this._part = undefined;
this._ignoreData = false;
this._justMatched = true;
this._dashes = 0;
}
}
function ignore(self) {
if (self._part && !self._ignoreData) {
self._ignoreData = true;
self._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
self._part.resume();
}
}
function unpause(self) {
if (!self._pause)
return;
self._pause = false;
if (self._cb) {
const cb = self._cb;
self._cb = undefined;
cb();
}
}
module.exports = Dicer;
+125
View File
@@ -0,0 +1,125 @@
'use strict';
const EventEmitter = require('events');
const StreamSearch = require('streamsearch');
const B_DCRLF = Buffer.from('\r\n\r\n');
const RE_CRLF = /\r\n/g;
// eslint-disable-next-line no-control-regex
const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/;
const MAX_HEADER_PAIRS = 2000; // From node's http.js
const MAX_HEADER_SIZE = 80 * 1024; // From node's http_parser
class HeaderParser extends EventEmitter {
constructor(cfg) {
super();
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, (isMatch, data, start, end) => {
if (data && !this.maxed) {
if (this.nread + (end - start) > MAX_HEADER_SIZE) {
end = (MAX_HEADER_SIZE - this.nread);
this.nread = MAX_HEADER_SIZE;
} else {
this.nread += (end - start);
}
if (this.nread === MAX_HEADER_SIZE)
this.maxed = true;
this.buffer += data.toString('latin1', start, end);
}
if (isMatch)
this._finish();
});
}
push(data) {
const r = this.ss.push(data);
if (this.finished)
return r;
}
reset() {
this.finished = false;
this.buffer = '';
this.header = {};
this.ss.reset();
}
_finish() {
let hadError = false;
if (this.buffer)
hadError = !parseHeader(this);
this.ss.matches = this.ss.maxMatches;
const header = this.header;
this.header = {};
this.buffer = '';
this.finished = true;
this.nread = this.npairs = 0;
this.maxed = false;
if (!hadError)
this.emit('header', header);
}
}
function parseHeader(self) {
if (self.npairs === self.maxHeaderPairs)
return true;
const lines = self.buffer.split(RE_CRLF);
const len = lines.length;
let m;
let h;
let modded = false;
for (let 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 ...
if (!h) {
self.emit('error', new Error('Unexpected folded header value'));
return false;
}
self.header[h][self.header[h].length - 1] += lines[i];
} else {
m = RE_HDR.exec(lines[i]);
if (m) {
h = m[1].toLowerCase();
if (m[2]) {
if (self.header[h] === undefined)
self.header[h] = [m[2]];
else
self.header[h].push(m[2]);
} else {
self.header[h] = [''];
}
if (++self.npairs === self.maxHeaderPairs)
break;
} else {
self.buffer = lines[i];
modded = true;
break;
}
}
}
if (!modded)
self.buffer = '';
return true;
}
module.exports = HeaderParser;
+9
View File
@@ -0,0 +1,9 @@
'use strict';
const { Readable } = require('stream');
class PartStream extends Readable {
_read(n) {}
}
module.exports = PartStream;
+72
View File
@@ -0,0 +1,72 @@
{
"_from": "dicer@^0.3.0",
"_id": "[email protected]",
"_inBundle": false,
"_integrity": "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA==",
"_location": "/dicer",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "dicer@^0.3.0",
"name": "dicer",
"escapedName": "dicer",
"rawSpec": "^0.3.0",
"saveSpec": null,
"fetchSpec": "^0.3.0"
},
"_requiredBy": [
"/firebase-admin"
],
"_resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.1.tgz",
"_shasum": "abf28921e3475bc5e801e74e0159fd94f927ba97",
"_spec": "dicer@^0.3.0",
"_where": "/Users/talksik/Development/nirvana-server/node_modules/firebase-admin",
"author": {
"name": "Brian White",
"email": "[email protected]"
},
"bugs": {
"url": "https://github.com/mscdex/dicer/issues"
},
"bundleDependencies": false,
"dependencies": {
"streamsearch": "^1.1.0"
},
"deprecated": false,
"description": "A very fast streaming multipart parser for node.js",
"devDependencies": {
"@mscdex/eslint-config": "^1.1.0",
"eslint": "^7.32.0"
},
"engines": {
"node": ">=10.0.0"
},
"homepage": "https://github.com/mscdex/dicer#readme",
"keywords": [
"parser",
"parse",
"parsing",
"multipart",
"form-data",
"streaming"
],
"licenses": [
{
"type": "MIT",
"url": "http://github.com/mscdex/dicer/raw/master/LICENSE"
}
],
"main": "./lib/Dicer.js",
"name": "dicer",
"repository": {
"type": "git",
"url": "git+ssh://[email protected]/mscdex/dicer.git"
},
"scripts": {
"lint": "eslint --cache --report-unused-disable-directives --ext=.js .eslintrc.js lib test",
"lint:fix": "npm run lint -- --fix",
"test": "node test/test.js"
},
"version": "0.3.1"
}
+31
View File
@@ -0,0 +1,31 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
+1
View File
@@ -0,0 +1 @@
put
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"_method\""]}
View File
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[blog]\""]}
View File
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[public_email]\""]}
View File
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[interests]\""]}
+3
View File
@@ -0,0 +1,3 @@
hello
"quote"
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[bio]\""]}
+1
View File
@@ -0,0 +1 @@
Save
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"commit\""]}
+2
View File
@@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""],
"content-type": ["application/octet-stream"]}
+32
View File
@@ -0,0 +1,32 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR--
+33
View File
@@ -0,0 +1,33 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR--
+1
View File
@@ -0,0 +1 @@
Preamble terminated early due to unexpected end of multipart data
+32
View File
@@ -0,0 +1,32 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR--
+1
View File
@@ -0,0 +1 @@
put
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"_method\""]}
View File
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[blog]\""]}
View File
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[public_email]\""]}
View File
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[interests]\""]}
+3
View File
@@ -0,0 +1,3 @@
hello
"quote"
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[bio]\""]}
View File
+2
View File
@@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""],
"content-type": ["application/octet-stream"]}
+1
View File
@@ -0,0 +1 @@
Save
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"commit\""]}
+24
View File
@@ -0,0 +1,24 @@
User-Agent: foo bar baz
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="foo"
bar
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed, boundary=BbC04y
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--
--AaB03x--
+1
View File
@@ -0,0 +1 @@
bar
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"foo\""]}
+12
View File
@@ -0,0 +1,12 @@
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--
+2
View File
@@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"files\""],
"content-type": ["multipart/mixed, boundary=BbC04y"]}
+2
View File
@@ -0,0 +1,2 @@
{"user-agent": ["foo bar baz"],
"content-type": ["multipart/form-data; boundary=AaB03x"]}
+21
View File
@@ -0,0 +1,21 @@
--AaB03x
Content-Disposition: form-data; name="foo"
bar
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed, boundary=BbC04y
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--
--AaB03x--
+1
View File
@@ -0,0 +1 @@
bar
+1
View File
@@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"foo\""]}
+12
View File
@@ -0,0 +1,12 @@
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--
+2
View File
@@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"files\""],
"content-type": ["multipart/mixed, boundary=BbC04y"]}
+97
View File
@@ -0,0 +1,97 @@
'use strict';
const assert = require('assert');
const Dicer = require('..');
const CRLF = '\r\n';
const boundary = 'boundary';
const writeSep = `--${boundary}`;
const writePart = [
writeSep,
'Content-Type: text/plain',
'Content-Length: 0'
].join(CRLF) + `${CRLF}${CRLF}some data${CRLF}`;
const writeEnd = `--${CRLF}`;
let firedEnd = false;
let firedFinish = false;
const dicer = new Dicer({ boundary });
dicer.on('part', partListener);
dicer.on('finish', finishListener);
dicer.write(writePart + writeSep);
function partListener(partReadStream) {
partReadStream.on('data', () => {});
partReadStream.on('end', partEndListener);
}
function partEndListener() {
firedEnd = true;
setImmediate(afterEnd);
}
function afterEnd() {
dicer.end(writeEnd);
setImmediate(afterWrite);
}
function finishListener() {
assert(firedEnd, 'Failed to end before finishing');
firedFinish = true;
test2();
}
function afterWrite() {
assert(firedFinish, 'Failed to finish');
}
let isPausePush = true;
let firedPauseCallback = false;
let firedPauseFinish = false;
let dicer2 = null;
function test2() {
dicer2 = new Dicer({ boundary });
dicer2.on('part', pausePartListener);
dicer2.on('finish', pauseFinish);
dicer2.write(writePart + writeSep, 'utf8', pausePartCallback);
setImmediate(pauseAfterWrite);
}
function pausePartListener(partReadStream) {
partReadStream.on('data', () => {});
partReadStream.on('end', () => {});
const realPush = partReadStream.push;
partReadStream.push = (...args) => {
realPush.apply(partReadStream, args);
if (!isPausePush)
return true;
isPausePush = false;
return false;
};
}
function pauseAfterWrite() {
dicer2.end(writeEnd);
setImmediate(pauseAfterEnd);
}
function pauseAfterEnd() {
assert(firedPauseCallback, 'Failed to call callback after pause');
assert(firedPauseFinish, 'Failed to finish after pause');
}
function pauseFinish() {
firedPauseFinish = true;
}
function pausePartCallback() {
firedPauseCallback = true;
}
+113
View File
@@ -0,0 +1,113 @@
'use strict';
const assert = require('assert');
const path = require('path');
const HeaderParser = require('../lib/HeaderParser');
const DCRLF = '\r\n\r\n';
const MAXED_BUFFER = Buffer.allocUnsafe(128 * 1024);
MAXED_BUFFER.fill(0x41); // 'A'
const group = path.basename(__filename, '.js') + '/';
function makeMsg(what, msg) {
return `[${group}${what}]: ${msg}`;
}
[
{ source: DCRLF,
expected: {},
what: 'No header'
},
{ source: [
'Content-Type:\t text/plain',
'Content-Length:0',
].join('\r\n') + DCRLF,
expected: {
'content-type': [' text/plain'],
'content-length': ['0'],
},
what: 'Value spacing',
},
{ source: [
'Content-Type:\r\n text/plain',
'Foo:\r\n bar\r\n baz',
].join('\r\n') + DCRLF,
expected: {
'content-type': [' text/plain'],
'foo': [' bar baz'],
},
what: 'Folded values',
},
{ source: [
'Content-Type:',
'Foo: ',
].join('\r\n') + DCRLF,
expected: {
'content-type': [''],
'foo': [''],
},
what: 'Empty values',
},
{ source: MAXED_BUFFER.toString('ascii') + DCRLF,
expected: {},
what: 'Max header size (single chunk)',
},
{ source: [
'ABCDEFGHIJ',
MAXED_BUFFER.toString('ascii'),
DCRLF,
],
expected: {},
what: 'Max header size (multiple chunks #1)',
},
{ source: [
MAXED_BUFFER.toString('ascii'),
MAXED_BUFFER.toString('ascii'),
DCRLF,
],
expected: {},
what: 'Max header size (multiple chunk #2)',
},
].forEach((v) => {
const parser = new HeaderParser();
let fired = false;
parser.on('header', (header) => {
assert(!fired, makeMsg(v.what, 'Header event fired more than once'));
fired = true;
assert.deepEqual(header,
v.expected,
makeMsg(v.what, 'Parsed result mismatch'));
});
if (!Array.isArray(v.source))
v.source = [v.source];
for (const chunk of v.source)
parser.push(chunk);
assert(fired, makeMsg(v.what, 'Did not receive header from parser'));
});
{
const source = [
' Content-Disposition: form-data; name="bildbeschreibung"',
DCRLF,
DCRLF,
DCRLF,
];
const parser = new HeaderParser();
let hadError = false;
parser.on('header', (header) => {
assert(false, 'Should not have seen header');
});
parser.on('error', (err) => {
assert(!hadError, 'Unexpected multiple errors');
hadError = true;
assert(/unexpected folded/i.test(err.message),
`Wrong error message: ${err.message}`);
});
for (const chunk of source)
parser.push(chunk);
assert(hadError, 'Expected error');
}
+151
View File
@@ -0,0 +1,151 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { inspect } = require('util');
const Dicer = require('..');
const FIXTURES_ROOT = `${__dirname}/fixtures/`;
let t = 0;
const group = path.basename(__filename, '.js') + '/';
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', () => {
assert(t === tests.length,
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests'));
});
const tests = [
{ source: 'many',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 7,
what: 'Extra trailer data pushed after finished'
},
];
function next() {
if (t === tests.length)
return;
const v = tests[t];
const fixtureBase = FIXTURES_ROOT + v.source;
let n = 0;
const buffer = Buffer.allocUnsafe(v.chsize);
const state = { parts: [] };
const fd = fs.openSync(fixtureBase + '/original', 'r');
const dicer = new Dicer(v.opts);
let error;
let partErrors = 0;
let finishes = 0;
dicer.on('part', (p) => {
const part = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', (h) => {
part.header = h;
}).on('data', (data) => {
// Make a copy because we are using readSync which re-uses a buffer ...
const copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!part.body)
part.body = [ data ];
else
part.body.push(data);
part.bodylen += data.length;
}).on('error', (err) => {
part.error = err;
++partErrors;
}).on('end', () => {
if (part.body)
part.body = Buffer.concat(part.body, part.bodylen);
state.parts.push(part);
});
}).on('error', (err) => {
error = err;
}).on('finish', () => {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
if (v.dicerError)
assert(error !== undefined, makeMsg(v.what, 'Expected error'));
else
assert(error === undefined, makeMsg(v.what, 'Unexpected error'));
if (v.events && v.events.indexOf('part') > -1) {
assert.equal(state.parts.length,
v.nparts,
makeMsg(v.what,
'Part count mismatch:\nActual: '
+ state.parts.length
+ '\nExpected: '
+ v.nparts));
if (!v.npartErrors)
v.npartErrors = 0;
assert.equal(partErrors,
v.npartErrors,
makeMsg(v.what,
'Part errors mismatch:\nActual: '
+ partErrors
+ '\nExpected: '
+ v.npartErrors));
for (let i = 0, header, body; i < v.nparts; ++i) {
if (fs.existsSync(fixtureBase + '/part' + (i + 1))) {
body = fs.readFileSync(fixtureBase + '/part' + (i + 1));
if (body.length === 0)
body = undefined;
} else {
body = undefined;
}
assert.deepEqual(state.parts[i].body,
body,
makeMsg(v.what,
'Part #' + (i + 1) + ' body mismatch'));
if (fs.existsSync(fixtureBase + '/part' + (i + 1) + '.header')) {
header = fs.readFileSync(fixtureBase
+ '/part' + (i + 1) + '.header', 'latin1');
header = JSON.parse(header);
} else {
header = undefined;
}
assert.deepEqual(state.parts[i].header,
header,
makeMsg(v.what,
'Part #' + (i + 1)
+ ' parsed header mismatch:\nActual: '
+ inspect(state.parts[i].header)
+ '\nExpected: '
+ inspect(header)));
}
}
++t;
next();
});
while (true) {
n = fs.readSync(fd, buffer, 0, buffer.length, null);
if (n === 0) {
setTimeout(() => {
dicer.write('\r\n\r\n\r\n');
dicer.end();
}, 50);
break;
}
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n));
}
fs.closeSync(fd);
}
next();
+235
View File
@@ -0,0 +1,235 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { inspect } = require('util');
const Dicer = require('..');
const FIXTURES_ROOT = `${__dirname}/fixtures/`;
let t = 0;
const group = path.basename(__filename, '.js') + '/';
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', () => {
assert(t === tests.length,
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests'));
});
const tests = [
{ source: 'many',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 0,
what: 'No preamble or part listeners'
},
];
function next() {
if (t === tests.length)
return;
const v = tests[t];
const fixtureBase = FIXTURES_ROOT + v.source;
let n = 0;
const buffer = Buffer.allocUnsafe(v.chsize);
const state = { done: false, parts: [], preamble: undefined };
const fd = fs.openSync(fixtureBase + '/original', 'r');
const dicer = new Dicer(v.opts);
let error;
let partErrors = 0;
let finishes = 0;
if (v.events && v.events.indexOf('preamble') > -1) {
dicer.on('preamble', (p) => {
const preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', (h) => {
preamble.header = h;
}).on('data', (data) => {
// Make a copy because we are using readSync which re-uses a buffer ...
const copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!preamble.body)
preamble.body = [ data ];
else
preamble.body.push(data);
preamble.bodylen += data.length;
}).on('error', (err) => {
preamble.error = err;
}).on('end', () => {
if (preamble.body)
preamble.body = Buffer.concat(preamble.body, preamble.bodylen);
if (preamble.body || preamble.header)
state.preamble = preamble;
});
});
}
if (v.events && v.events.indexOf('part') > -1) {
dicer.on('part', (p) => {
const part = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', (h) => {
part.header = h;
}).on('data', (data) => {
// Make a copy because we are using readSync which re-uses a buffer ...
const copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!part.body)
part.body = [ data ];
else
part.body.push(data);
part.bodylen += data.length;
}).on('error', (err) => {
part.error = err;
++partErrors;
}).on('end', () => {
if (part.body)
part.body = Buffer.concat(part.body, part.bodylen);
state.parts.push(part);
});
});
}
dicer.on('error', (err) => {
error = err;
}).on('finish', () => {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
if (v.dicerError)
assert(error !== undefined, makeMsg(v.what, 'Expected error'));
else
assert(error === undefined, makeMsg(v.what, 'Unexpected error'));
if (v.events && v.events.indexOf('preamble') > -1) {
let preamble;
if (fs.existsSync(fixtureBase + '/preamble')) {
const prebody = fs.readFileSync(fixtureBase + '/preamble');
if (prebody.length) {
preamble = {
body: prebody,
bodylen: prebody.length,
error: undefined,
header: undefined
};
}
}
if (fs.existsSync(fixtureBase + '/preamble.header')) {
const prehead = JSON.parse(fs.readFileSync(
fixtureBase + '/preamble.header', 'latin1'
));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: prehead
};
} else {
preamble.header = prehead;
}
}
if (fs.existsSync(fixtureBase + '/preamble.error')) {
const err = new Error(fs.readFileSync(
fixtureBase + '/preamble.error', 'latin1'
));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: err,
header: undefined
};
} else {
preamble.error = err;
}
}
assert.deepEqual(state.preamble,
preamble,
makeMsg(v.what,
'Preamble mismatch:\nActual:'
+ inspect(state.preamble)
+ '\nExpected: '
+ inspect(preamble)));
}
if (v.events && v.events.indexOf('part') > -1) {
assert.equal(state.parts.length,
v.nparts,
makeMsg(v.what,
'Part count mismatch:\nActual: '
+ state.parts.length
+ '\nExpected: '
+ v.nparts));
if (!v.npartErrors)
v.npartErrors = 0;
assert.equal(partErrors,
v.npartErrors,
makeMsg(v.what,
'Part errors mismatch:\nActual: '
+ partErrors
+ '\nExpected: '
+ v.npartErrors));
for (let i = 0, header, body; i < v.nparts; ++i) {
if (fs.existsSync(fixtureBase + '/part' + (i + 1))) {
body = fs.readFileSync(fixtureBase + '/part' + (i + 1));
if (body.length === 0)
body = undefined;
} else {
body = undefined;
}
assert.deepEqual(state.parts[i].body,
body,
makeMsg(v.what,
'Part #' + (i + 1) + ' body mismatch'));
if (fs.existsSync(fixtureBase + '/part' + (i + 1) + '.header')) {
header = fs.readFileSync(fixtureBase
+ '/part' + (i + 1) + '.header', 'latin1');
header = JSON.parse(header);
} else {
header = undefined;
}
assert.deepEqual(state.parts[i].header,
header,
makeMsg(v.what,
'Part #' + (i + 1)
+ ' parsed header mismatch:\nActual: '
+ inspect(state.parts[i].header)
+ '\nExpected: '
+ inspect(header)));
}
}
++t;
next();
});
while (true) {
n = fs.readSync(fd, buffer, 0, buffer.length, null);
if (n === 0) {
dicer.end();
break;
}
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n));
}
fs.closeSync(fd);
}
next();
+249
View File
@@ -0,0 +1,249 @@
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { inspect } = require('util');
const Dicer = require('..');
const FIXTURES_ROOT = `${__dirname}/fixtures/`;
let t = 0;
const group = path.basename(__filename, '.js') + '/';
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', function() {
assert(t === tests.length,
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests'));
});
const tests = [
{ source: 'nested',
opts: { boundary: 'AaB03x' },
chsize: 32,
nparts: 2,
what: 'One nested multipart'
},
{ source: 'many',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 7,
what: 'Many parts'
},
{ source: 'many-wrongboundary',
opts: { boundary: 'LOLOLOL' },
chsize: 8,
nparts: 0,
dicerError: true,
what: 'Many parts, wrong boundary'
},
{ source: 'many-noend',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 7,
npartErrors: 1,
dicerError: true,
what: 'Many parts, end boundary missing, 1 file open'
},
{ source: 'nested-full',
opts: { boundary: 'AaB03x', headerFirst: true },
chsize: 32,
nparts: 2,
what: 'One nested multipart with preceding header'
},
{ source: 'nested-full',
opts: { headerFirst: true },
chsize: 32,
nparts: 2,
setBoundary: 'AaB03x',
what: 'One nested multipart with preceding header, using setBoundary'
},
];
function next() {
if (t === tests.length)
return;
const v = tests[t];
const fixtureBase = FIXTURES_ROOT + v.source;
const state = { parts: [], preamble: undefined };
const dicer = new Dicer(v.opts);
let error;
let partErrors = 0;
let finishes = 0;
dicer.on('preamble', (p) => {
const preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', (h) => {
preamble.header = h;
if (v.setBoundary)
dicer.setBoundary(v.setBoundary);
}).on('data', (data) => {
// Make a copy because we are using readSync which re-uses a buffer ...
const copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!preamble.body)
preamble.body = [ data ];
else
preamble.body.push(data);
preamble.bodylen += data.length;
}).on('error', (err) => {
preamble.error = err;
}).on('end', () => {
if (preamble.body)
preamble.body = Buffer.concat(preamble.body, preamble.bodylen);
if (preamble.body || preamble.header)
state.preamble = preamble;
});
});
dicer.on('part', (p) => {
const part = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', (h) => {
part.header = h;
}).on('data', (data) => {
if (!part.body)
part.body = [ data ];
else
part.body.push(data);
part.bodylen += data.length;
}).on('error', (err) => {
part.error = err;
++partErrors;
}).on('end', () => {
if (part.body)
part.body = Buffer.concat(part.body, part.bodylen);
state.parts.push(part);
});
}).on('error', (err) => {
error = err;
}).on('finish', () => {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
if (v.dicerError) {
assert(error !== undefined, makeMsg(v.what, 'Expected error'));
} else {
assert(error === undefined,
makeMsg(v.what, 'Unexpected error: ' + error));
}
let preamble;
if (fs.existsSync(fixtureBase + '/preamble')) {
const prebody = fs.readFileSync(fixtureBase + '/preamble');
if (prebody.length) {
preamble = {
body: prebody,
bodylen: prebody.length,
error: undefined,
header: undefined
};
}
}
if (fs.existsSync(fixtureBase + '/preamble.header')) {
const prehead = JSON.parse(fs.readFileSync(
fixtureBase + '/preamble.header', 'latin1'
));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: prehead
};
} else {
preamble.header = prehead;
}
}
if (fs.existsSync(fixtureBase + '/preamble.error')) {
const err = new Error(fs.readFileSync(
fixtureBase + '/preamble.error', 'latin1'
));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: err,
header: undefined
};
} else {
preamble.error = err;
}
}
assert.deepEqual(state.preamble,
preamble,
makeMsg(v.what,
'Preamble mismatch:\nActual:'
+ inspect(state.preamble)
+ '\nExpected: '
+ inspect(preamble)));
assert.equal(state.parts.length,
v.nparts,
makeMsg(v.what,
'Part count mismatch:\nActual: '
+ state.parts.length
+ '\nExpected: '
+ v.nparts));
if (!v.npartErrors)
v.npartErrors = 0;
assert.equal(partErrors,
v.npartErrors,
makeMsg(v.what,
'Part errors mismatch:\nActual: '
+ partErrors
+ '\nExpected: '
+ v.npartErrors));
for (let i = 0, header, body; i < v.nparts; ++i) {
if (fs.existsSync(fixtureBase + '/part' + (i + 1))) {
body = fs.readFileSync(fixtureBase + '/part' + (i + 1));
if (body.length === 0)
body = undefined;
} else {
body = undefined;
}
assert.deepEqual(state.parts[i].body,
body,
makeMsg(v.what,
'Part #' + (i + 1) + ' body mismatch'));
if (fs.existsSync(fixtureBase + '/part' + (i + 1) + '.header')) {
header = fs.readFileSync(fixtureBase
+ '/part' + (i + 1) + '.header', 'latin1');
header = JSON.parse(header);
} else {
header = undefined;
}
assert.deepEqual(state.parts[i].header,
header,
makeMsg(v.what,
'Part #' + (i + 1)
+ ' parsed header mismatch:\nActual: '
+ inspect(state.parts[i].header)
+ '\nExpected: '
+ inspect(header)));
}
++t;
next();
});
fs.createReadStream(fixtureBase + '/original').pipe(dicer);
}
next();
+6
View File
@@ -0,0 +1,6 @@
'use strict';
require('fs').readdirSync(__dirname).forEach((f) => {
if (f.substr(0, 5) === 'test-')
require(`./${f}`);
});