1 var Buffer = require('buffer').Buffer,
4 { PARSER_UNINITIALIZED: s++,
7 HEADER_FIELD_START: s++,
9 HEADER_VALUE_START: s++,
11 HEADER_VALUE_ALMOST_DONE: s++,
12 HEADERS_ALMOST_DONE: s++,
22 LAST_BOUNDARY: f *= 2,
41 function MultipartParser() {
43 this.boundaryChars = null;
44 this.lookbehind = null;
45 this.state = S.PARSER_UNINITIALIZED;
50 exports.MultipartParser = MultipartParser;
52 MultipartParser.stateToString = function(stateNumber) {
53 for (var state in S) {
54 var number = S[state];
55 if (number === stateNumber) return state;
59 MultipartParser.prototype.initWithBoundary = function(str) {
60 this.boundary = new Buffer(str.length+4);
61 this.boundary.write('\r\n--', 'ascii', 0);
62 this.boundary.write(str, 'ascii', 4);
63 this.lookbehind = new Buffer(this.boundary.length+8);
66 this.boundaryChars = {};
67 for (var i = 0; i < this.boundary.length; i++) {
68 this.boundaryChars[this.boundary[i]] = true;
72 MultipartParser.prototype.write = function(buffer) {
76 prevIndex = this.index,
80 lookbehind = this.lookbehind,
81 boundary = this.boundary,
82 boundaryChars = this.boundaryChars,
83 boundaryLength = this.boundary.length,
84 boundaryEnd = boundaryLength - 1,
85 bufferLength = buffer.length,
89 mark = function(name) {
90 self[name+'Mark'] = i;
92 clear = function(name) {
93 delete self[name+'Mark'];
95 callback = function(name, buffer, start, end) {
96 if (start !== undefined && start === end) {
100 var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1);
101 if (callbackSymbol in self) {
102 self[callbackSymbol](buffer, start, end);
105 dataCallback = function(name, clear) {
106 var markSymbol = name+'Mark';
107 if (!(markSymbol in self)) {
112 callback(name, buffer, self[markSymbol], buffer.length);
113 self[markSymbol] = 0;
115 callback(name, buffer, self[markSymbol], i);
116 delete self[markSymbol];
120 for (i = 0; i < len; i++) {
123 case S.PARSER_UNINITIALIZED:
127 state = S.START_BOUNDARY;
128 case S.START_BOUNDARY:
129 if (index == boundary.length - 2) {
135 } else if (index - 1 == boundary.length - 2) {
140 callback('partBegin');
141 state = S.HEADER_FIELD_START;
145 if (c != boundary[index+2]) {
150 case S.HEADER_FIELD_START:
151 state = S.HEADER_FIELD;
156 clear('headerField');
157 state = S.HEADERS_ALMOST_DONE;
168 // empty header field
171 dataCallback('headerField', true);
172 state = S.HEADER_VALUE_START;
177 if (cl < A || cl > Z) {
181 case S.HEADER_VALUE_START:
187 state = S.HEADER_VALUE;
190 dataCallback('headerValue', true);
191 callback('headerEnd');
192 state = S.HEADER_VALUE_ALMOST_DONE;
195 case S.HEADER_VALUE_ALMOST_DONE:
199 state = S.HEADER_FIELD_START;
201 case S.HEADERS_ALMOST_DONE:
206 callback('headersEnd');
207 state = S.PART_DATA_START;
209 case S.PART_DATA_START:
216 // boyer-moore derrived algorithm to safely skip non-boundary data
218 while (i < bufferLength && !(buffer[i] in boundaryChars)) {
225 if (index < boundary.length) {
226 if (boundary[index] == c) {
228 dataCallback('partData', true);
234 } else if (index == boundary.length) {
237 // CR = part boundary
238 flags |= F.PART_BOUNDARY;
239 } else if (c == HYPHEN) {
240 // HYPHEN = end boundary
241 flags |= F.LAST_BOUNDARY;
245 } else if (index - 1 == boundary.length) {
246 if (flags & F.PART_BOUNDARY) {
249 // unset the PART_BOUNDARY flag
250 flags &= ~F.PART_BOUNDARY;
252 callback('partBegin');
253 state = S.HEADER_FIELD_START;
256 } else if (flags & F.LAST_BOUNDARY) {
270 // when matching a possible boundary, keep a lookbehind reference
271 // in case it turns out to be a false lead
272 lookbehind[index-1] = c;
273 } else if (prevIndex > 0) {
274 // if our boundary turned out to be rubbish, the captured lookbehind
275 // belongs to partData
276 callback('partData', lookbehind, 0, prevIndex);
280 // reconsider the current character even so it interrupted the sequence
281 // it could be the beginning of a new sequence
293 dataCallback('headerField');
294 dataCallback('headerValue');
295 dataCallback('partData');
304 MultipartParser.prototype.end = function() {
305 if (this.state != S.END) {
306 return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain());
310 MultipartParser.prototype.explain = function() {
311 return 'state = ' + MultipartParser.stateToString(this.state);