|
|
var Buffer = require('buffer').Buffer, s = 0, S = { PARSER_UNINITIALIZED: s++, START: s++, START_BOUNDARY: s++, HEADER_FIELD_START: s++, HEADER_FIELD: s++, HEADER_VALUE_START: s++, HEADER_VALUE: s++, HEADER_VALUE_ALMOST_DONE: s++, HEADERS_ALMOST_DONE: s++, PART_DATA_START: s++, PART_DATA: s++, PART_END: s++, END: s++ },
f = 1, F = { PART_BOUNDARY: f, LAST_BOUNDARY: f *= 2 },
LF = 10, CR = 13, SPACE = 32, HYPHEN = 45, COLON = 58, A = 97, Z = 122,
lower = function(c) { return c | 0x20; };
for (s in S) { exports[s] = S[s]; }
function MultipartParser() { this.boundary = null; this.boundaryChars = null; this.lookbehind = null; this.state = S.PARSER_UNINITIALIZED;
this.index = null; this.flags = 0; }; exports.MultipartParser = MultipartParser;
MultipartParser.stateToString = function(stateNumber) { for (var state in S) { var number = S[state]; if (number === stateNumber) return state; } };
MultipartParser.prototype.initWithBoundary = function(str) { this.boundary = new Buffer(str.length+4); this.boundary.write('\r\n--', 'ascii', 0); this.boundary.write(str, 'ascii', 4); this.lookbehind = new Buffer(this.boundary.length+8); this.state = S.START;
this.boundaryChars = {}; for (var i = 0; i < this.boundary.length; i++) { this.boundaryChars[this.boundary[i]] = true; } };
MultipartParser.prototype.write = function(buffer) { var self = this, i = 0, len = buffer.length, prevIndex = this.index, index = this.index, state = this.state, flags = this.flags, lookbehind = this.lookbehind, boundary = this.boundary, boundaryChars = this.boundaryChars, boundaryLength = this.boundary.length, boundaryEnd = boundaryLength - 1, bufferLength = buffer.length, c, cl,
mark = function(name) { self[name+'Mark'] = i; }, clear = function(name) { delete self[name+'Mark']; }, callback = function(name, buffer, start, end) { if (start !== undefined && start === end) { return; }
var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); if (callbackSymbol in self) { self[callbackSymbol](buffer, start, end); } }, dataCallback = function(name, clear) { var markSymbol = name+'Mark'; if (!(markSymbol in self)) { return; }
if (!clear) { callback(name, buffer, self[markSymbol], buffer.length); self[markSymbol] = 0; } else { callback(name, buffer, self[markSymbol], i); delete self[markSymbol]; } };
for (i = 0; i < len; i++) { c = buffer[i]; switch (state) { case S.PARSER_UNINITIALIZED: return i; case S.START: index = 0; state = S.START_BOUNDARY; case S.START_BOUNDARY: if (index == boundary.length - 2) { if (c != CR) { return i; } index++; break; } else if (index - 1 == boundary.length - 2) { if (c != LF) { return i; } index = 0; callback('partBegin'); state = S.HEADER_FIELD_START; break; }
if (c != boundary[index+2]) { index = -2; } if (c == boundary[index+2]) { index++; } break; case S.HEADER_FIELD_START: state = S.HEADER_FIELD; mark('headerField'); index = 0; case S.HEADER_FIELD: if (c == CR) { clear('headerField'); state = S.HEADERS_ALMOST_DONE; break; }
index++; if (c == HYPHEN) { break; }
if (c == COLON) { if (index == 1) { // empty header field
return i; } dataCallback('headerField', true); state = S.HEADER_VALUE_START; break; }
cl = lower(c); if (cl < A || cl > Z) { return i; } break; case S.HEADER_VALUE_START: if (c == SPACE) { break; }
mark('headerValue'); state = S.HEADER_VALUE; case S.HEADER_VALUE: if (c == CR) { dataCallback('headerValue', true); callback('headerEnd'); state = S.HEADER_VALUE_ALMOST_DONE; } break; case S.HEADER_VALUE_ALMOST_DONE: if (c != LF) { return i; } state = S.HEADER_FIELD_START; break; case S.HEADERS_ALMOST_DONE: if (c != LF) { return i; }
callback('headersEnd'); state = S.PART_DATA_START; break; case S.PART_DATA_START: state = S.PART_DATA; mark('partData'); case S.PART_DATA: prevIndex = index;
if (index == 0) { // boyer-moore derrived algorithm to safely skip non-boundary data
i += boundaryEnd; while (i < bufferLength && !(buffer[i] in boundaryChars)) { i += boundaryLength; } i -= boundaryEnd; c = buffer[i]; }
if (index < boundary.length) { if (boundary[index] == c) { if (index == 0) { dataCallback('partData', true); } index++; } else { index = 0; } } else if (index == boundary.length) { index++; if (c == CR) { // CR = part boundary
flags |= F.PART_BOUNDARY; } else if (c == HYPHEN) { // HYPHEN = end boundary
flags |= F.LAST_BOUNDARY; } else { index = 0; } } else if (index - 1 == boundary.length) { if (flags & F.PART_BOUNDARY) { index = 0; if (c == LF) { // unset the PART_BOUNDARY flag
flags &= ~F.PART_BOUNDARY; callback('partEnd'); callback('partBegin'); state = S.HEADER_FIELD_START; break; } } else if (flags & F.LAST_BOUNDARY) { if (c == HYPHEN) { callback('partEnd'); callback('end'); state = S.END; } else { index = 0; } } else { index = 0; } }
if (index > 0) { // when matching a possible boundary, keep a lookbehind reference
// in case it turns out to be a false lead
lookbehind[index-1] = c; } else if (prevIndex > 0) { // if our boundary turned out to be rubbish, the captured lookbehind
// belongs to partData
callback('partData', lookbehind, 0, prevIndex); prevIndex = 0; mark('partData');
// reconsider the current character even so it interrupted the sequence
// it could be the beginning of a new sequence
i--; }
break; case S.END: break; default: return i; } }
dataCallback('headerField'); dataCallback('headerValue'); dataCallback('partData');
this.index = index; this.state = state; this.flags = flags;
return len; };
MultipartParser.prototype.end = function() { var callback = function(self, name) { var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); if (callbackSymbol in self) { self[callbackSymbol](); } }; if ((this.state == S.HEADER_FIELD_START && this.index == 0) || (this.state == S.PART_DATA && this.index == this.boundary.length)) { callback(this, 'partEnd'); callback(this, 'end'); } else if (this.state != S.END) { return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain()); } };
MultipartParser.prototype.explain = function() { return 'state = ' + MultipartParser.stateToString(this.state); };
|