123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- /**
- * Parted (https://github.com/chjj/parted)
- * A streaming multipart state parser.
- * Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed)
- */
- var fs = require('fs')
- , path = require('path')
- , EventEmitter = require('events').EventEmitter
- , StringDecoder = require('string_decoder').StringDecoder
- , set = require('qs').set
- , each = Array.prototype.forEach;
- /**
- * Character Constants
- */
- var DASH = '-'.charCodeAt(0)
- , CR = '\r'.charCodeAt(0)
- , LF = '\n'.charCodeAt(0)
- , COLON = ':'.charCodeAt(0)
- , SPACE = ' '.charCodeAt(0);
- /**
- * Parser
- */
- var Parser = function(type, options) {
- if (!(this instanceof Parser)) {
- return new Parser(type, options);
- }
- EventEmitter.call(this);
- this.writable = true;
- this.readable = true;
- this.options = options || {};
- var key = grab(type, 'boundary');
- if (!key) {
- return this._error('No boundary key found.');
- }
- this.key = new Buffer('\r\n--' + key);
- this._key = {};
- each.call(this.key, function(ch) {
- this._key[ch] = true;
- }, this);
- this.state = 'start';
- this.pending = 0;
- this.written = 0;
- this.writtenDisk = 0;
- this.buff = new Buffer(200);
- this.preamble = true;
- this.epilogue = false;
- this._reset();
- };
- Parser.prototype.__proto__ = EventEmitter.prototype;
- /**
- * Parsing
- */
- Parser.prototype.write = function(data) {
- if (!this.writable
- || this.epilogue) return;
- try {
- this._parse(data);
- } catch (e) {
- this._error(e);
- }
- return true;
- };
- Parser.prototype.end = function(data) {
- if (!this.writable) return;
- if (data) this.write(data);
- if (!this.epilogue) {
- return this._error('Message underflow.');
- }
- return true;
- };
- Parser.prototype._parse = function(data) {
- var i = 0
- , len = data.length
- , buff = this.buff
- , key = this.key
- , ch
- , val
- , j;
- for (; i < len; i++) {
- if (this.pos >= 200) {
- return this._error('Potential buffer overflow.');
- }
- ch = data[i];
- switch (this.state) {
- case 'start':
- switch (ch) {
- case DASH:
- this.pos = 3;
- this.state = 'key';
- break;
- default:
- break;
- }
- break;
- case 'key':
- if (this.pos === key.length) {
- this.state = 'key_end';
- i--;
- } else if (ch !== key[this.pos]) {
- if (this.preamble) {
- this.state = 'start';
- i--;
- } else {
- this.state = 'body';
- val = this.pos - i;
- if (val > 0) {
- this._write(key.slice(0, val));
- }
- i--;
- }
- } else {
- this.pos++;
- }
- break;
- case 'key_end':
- switch (ch) {
- case CR:
- this.state = 'key_line_end';
- break;
- case DASH:
- this.state = 'key_dash_end';
- break;
- default:
- return this._error('Expected CR or DASH.');
- }
- break;
- case 'key_line_end':
- switch (ch) {
- case LF:
- if (this.preamble) {
- this.preamble = false;
- } else {
- this._finish();
- }
- this.state = 'header_name';
- this.pos = 0;
- break;
- default:
- return this._error('Expected CR.');
- }
- break;
- case 'key_dash_end':
- switch (ch) {
- case DASH:
- this.epilogue = true;
- this._finish();
- return;
- default:
- return this._error('Expected DASH.');
- }
- break;
- case 'header_name':
- switch (ch) {
- case COLON:
- this.header = buff.toString('ascii', 0, this.pos);
- this.pos = 0;
- this.state = 'header_val';
- break;
- default:
- buff[this.pos++] = ch | 32;
- break;
- }
- break;
- case 'header_val':
- switch (ch) {
- case CR:
- this.state = 'header_val_end';
- break;
- case SPACE:
- if (this.pos === 0) {
- break;
- }
- ; // FALL-THROUGH
- default:
- buff[this.pos++] = ch;
- break;
- }
- break;
- case 'header_val_end':
- switch (ch) {
- case LF:
- val = buff.toString('ascii', 0, this.pos);
- this._header(this.header, val);
- this.pos = 0;
- this.state = 'header_end';
- break;
- default:
- return this._error('Expected LF.');
- }
- break;
- case 'header_end':
- switch (ch) {
- case CR:
- this.state = 'head_end';
- break;
- default:
- this.state = 'header_name';
- i--;
- break;
- }
- break;
- case 'head_end':
- switch (ch) {
- case LF:
- this.state = 'body';
- i++;
- if (i >= len) return;
- data = data.slice(i);
- i = -1;
- len = data.length;
- break;
- default:
- return this._error('Expected LF.');
- }
- break;
- case 'body':
- switch (ch) {
- case CR:
- if (i > 0) {
- this._write(data.slice(0, i));
- }
- this.pos = 1;
- this.state = 'key';
- data = data.slice(i);
- i = 0;
- len = data.length;
- break;
- default:
- // boyer-moore-like algorithm
- // at felixge's suggestion
- while ((j = i + key.length - 1) < len) {
- if (this._key[data[j]]) break;
- i = j;
- }
- break;
- }
- break;
- }
- }
- if (this.state === 'body') {
- this._write(data);
- }
- };
- Parser.prototype._header = function(name, val) {
- /*if (name === 'content-disposition') {
- this.field = grab(val, 'name');
- this.file = grab(val, 'filename');
- if (this.file) {
- this.data = stream(this.file, this.options.path);
- } else {
- this.decode = new StringDecoder('utf8');
- this.data = '';
- }
- }*/
- return this.emit('header', name, val);
- };
- Parser.prototype._write = function(data) {
- /*if (this.data == null) {
- return this._error('No disposition.');
- }
- if (this.file) {
- this.data.write(data);
- this.writtenDisk += data.length;
- } else {
- this.data += this.decode.write(data);
- this.written += data.length;
- }*/
- this.emit('data', data);
- };
- Parser.prototype._reset = function() {
- this.pos = 0;
- this.decode = null;
- this.field = null;
- this.data = null;
- this.file = null;
- this.header = null;
- };
- Parser.prototype._error = function(err) {
- this.destroy();
- this.emit('error', typeof err === 'string'
- ? new Error(err)
- : err);
- };
- Parser.prototype.destroy = function(err) {
- this.writable = false;
- this.readable = false;
- this._reset();
- };
- Parser.prototype._finish = function() {
- var self = this
- , field = this.field
- , data = this.data
- , file = this.file
- , part;
- this.pending++;
- this._reset();
- if (data && data.path) {
- part = data.path;
- data.end(next);
- } else {
- part = data;
- next();
- }
- function next() {
- if (!self.readable) return;
- self.pending--;
- self.emit('part', field, part);
- if (data && data.path) {
- self.emit('file', field, part, file);
- }
- if (self.epilogue && !self.pending) {
- self.emit('end');
- self.destroy();
- }
- }
- };
- /**
- * Uploads
- */
- Parser.root = process.platform === 'win32'
- ? 'C:/Temp'
- : '/tmp';
- /**
- * Middleware
- */
- Parser.middleware = function(options) {
- options = options || {};
- return function(req, res, next) {
- if (options.ensureBody) {
- req.body = {};
- }
- if (req.method === 'GET'
- || req.method === 'HEAD'
- || req._multipart) return next();
- req._multipart = true;
- var type = req.headers['content-type'];
- if (type) type = type.split(';')[0].trim().toLowerCase();
- if (type === 'multipart/form-data') {
- Parser.handle(req, res, next, options);
- } else {
- next();
- }
- };
- };
- /**
- * Handler
- */
- Parser.handle = function(req, res, next, options) {
- var parser = new Parser(req.headers['content-type'], options)
- , diskLimit = options.diskLimit
- , limit = options.limit
- , parts = {}
- , files = {};
- parser.on('error', function(err) {
- req.destroy();
- next(err);
- });
- parser.on('part', function(field, part) {
- set(parts, field, part);
- });
- parser.on('file', function(field, path, name) {
- set(files, field, {
- path: path,
- name: name,
- toString: function() {
- return path;
- }
- });
- });
- parser.on('data', function() {
- if (this.writtenDisk > diskLimit || this.written > limit) {
- this.emit('error', new Error('Overflow.'));
- this.destroy();
- }
- });
- parser.on('end', next);
- req.body = parts;
- req.files = files;
- req.pipe(parser);
- };
- /**
- * Helpers
- */
- var isWindows = process.platform === 'win32';
- var stream = function(name, dir) {
- var ext = path.extname(name) || ''
- , name = path.basename(name, ext) || ''
- , dir = dir || Parser.root
- , tag;
- tag = Math.random().toString(36).substring(2);
- name = name.substring(0, 200) + '.' + tag;
- name = path.join(dir, name) + ext.substring(0, 6);
- name = name.replace(/\0/g, '');
- if (isWindows) {
- name = name.replace(/[:*<>|"?]/g, '');
- }
- return fs.createWriteStream(name);
- };
- var grab = function(str, name) {
- if (!str) return;
- var rx = new RegExp('\\b' + name + '\\s*=\\s*("[^"]+"|\'[^\']+\'|[^;,]+)', 'i')
- , cap = rx.exec(str);
- if (cap) {
- return cap[1].trim().replace(/^['"]|['"]$/g, '');
- }
- };
- /**
- * Expose
- */
- module.exports = Parser;
|