Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Junho Kyung
committed
Oct 31, 2011
1 parent
6c8bd9e
commit fd3aded
Showing
2 changed files
with
350 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,312 @@ | ||
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 (var 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]) { | ||
return i; | ||
} | ||
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() { | ||
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); | ||
}; |
Oops, something went wrong.