Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / multiparty / index.js
1 exports.Form = Form;
2
3 var stream = require('readable-stream')
4   , util = require('util')
5   , fs = require('fs')
6   , crypto = require('crypto')
7   , path = require('path')
8   , os = require('os')
9   , StringDecoder = require('string_decoder').StringDecoder
10   , StreamCounter = require('stream-counter')
11
12 var START = 0
13   , START_BOUNDARY = 1
14   , HEADER_FIELD_START = 2
15   , HEADER_FIELD = 3
16   , HEADER_VALUE_START = 4
17   , HEADER_VALUE = 5
18   , HEADER_VALUE_ALMOST_DONE = 6
19   , HEADERS_ALMOST_DONE = 7
20   , PART_DATA_START = 8
21   , PART_DATA = 9
22   , PART_END = 10
23   , CLOSE_BOUNDARY = 11
24   , END = 12
25
26   , LF = 10
27   , CR = 13
28   , SPACE = 32
29   , HYPHEN = 45
30   , COLON = 58
31   , A = 97
32   , Z = 122
33
34 var CONTENT_TYPE_RE = /^multipart\/(?:form-data|related)(?:;|$)/i;
35 var CONTENT_TYPE_PARAM_RE = /;\s*([^=]+)=(?:"([^"]+)"|([^;]+))/gi;
36 var FILE_EXT_RE = /(\.[_\-a-zA-Z0-9]{0,16}).*/;
37 var LAST_BOUNDARY_SUFFIX_LEN = 4; // --\r\n
38
39 util.inherits(Form, stream.Writable);
40 function Form(options) {
41   var self = this;
42   stream.Writable.call(self);
43
44   options = options || {};
45
46   self.error = null;
47   self.finished = false;
48
49   self.autoFields = !!options.autoFields;
50   self.autoFiles = !!options.autoFiles;
51
52   self.maxFields = options.maxFields || 1000;
53   self.maxFieldsSize = options.maxFieldsSize || 2 * 1024 * 1024;
54   self.maxFilesSize = options.maxFilesSize || Infinity;
55   self.uploadDir = options.uploadDir || os.tmpDir();
56   self.encoding = options.encoding || 'utf8';
57   self.hash = options.hash || false;
58
59   self.bytesReceived = 0;
60   self.bytesExpected = null;
61
62   self.openedFiles = [];
63   self.totalFieldSize = 0;
64   self.totalFieldCount = 0;
65   self.totalFileSize = 0;
66   self.flushing = 0;
67
68   self.backpressure = false;
69   self.writeCbs = [];
70
71   if (options.boundary) setUpParser(self, options.boundary);
72
73   self.on('newListener', function(eventName) {
74     if (eventName === 'file') {
75       self.autoFiles = true;
76     } else if (eventName === 'field') {
77       self.autoFields = true;
78     }
79   });
80 }
81
82 Form.prototype.parse = function(req, cb) {
83   var called = false;
84   var self = this;
85   var waitend = true;
86
87   if (cb) {
88     // if the user supplies a callback, this implies autoFields and autoFiles
89     self.autoFields = true;
90     self.autoFiles = true;
91
92     // wait for request to end before calling cb
93     var end = function (done) {
94       if (called) return;
95
96       called = true;
97
98       // wait for req events to fire
99       process.nextTick(function() {
100         if (waitend && req.readable) {
101           // dump rest of request
102           req.resume();
103           req.once('end', done);
104           return;
105         }
106
107         done();
108       });
109     };
110
111     var fields = {};
112     var files = {};
113     self.on('error', function(err) {
114       end(function() {
115         cb(err);
116       });
117     });
118     self.on('field', function(name, value) {
119       var fieldsArray = fields[name] || (fields[name] = []);
120       fieldsArray.push(value);
121     });
122     self.on('file', function(name, file) {
123       var filesArray = files[name] || (files[name] = []);
124       filesArray.push(file);
125     });
126     self.on('close', function() {
127       end(function() {
128         cb(null, fields, files);
129       });
130     });
131   }
132
133   self.handleError = handleError;
134   self.bytesExpected = getBytesExpected(req.headers);
135
136   req.on('end', onReqEnd);
137   req.on('error', function(err) {
138     waitend = false;
139     handleError(err);
140   });
141   req.on('aborted', onReqAborted);
142
143   var state = req._readableState;
144   if (req._decoder || (state && (state.encoding || state.decoder))) {
145     // this is a binary protocol
146     // if an encoding is set, input is likely corrupted
147     validationError(new Error('request encoding must not be set'));
148     return;
149   }
150
151   var contentType = req.headers['content-type'];
152   if (!contentType) {
153     validationError(new Error('missing content-type header'));
154     return;
155   }
156
157   var m = CONTENT_TYPE_RE.exec(contentType);
158   if (!m) {
159     validationError(new Error('unrecognized content-type: ' + contentType));
160     return;
161   }
162
163   var boundary;
164   CONTENT_TYPE_PARAM_RE.lastIndex = m.index + m[0].length - 1;
165   while ((m = CONTENT_TYPE_PARAM_RE.exec(contentType))) {
166     if (m[1].toLowerCase() !== 'boundary') continue;
167     boundary = m[2] || m[3];
168     break;
169   }
170
171   if (!boundary) {
172     validationError(new Error('content-type missing boundary: ' + require('util').inspect(m)));
173     return;
174   }
175
176   setUpParser(self, boundary);
177   req.pipe(self);
178
179   function onReqAborted() {
180     waitend = false;
181     self.emit('aborted');
182     handleError(new Error("Request aborted"));
183   }
184
185   function onReqEnd() {
186     waitend = false;
187   }
188
189   function handleError(err) {
190     var first = !self.error;
191     if (first) {
192       self.error = err;
193       req.removeListener('aborted', onReqAborted);
194       req.removeListener('end', onReqEnd);
195     }
196
197     self.openedFiles.forEach(function(file) {
198       destroyFile(self, file);
199     });
200     self.openedFiles = [];
201
202     if (first) {
203       self.emit('error', err);
204     }
205   }
206
207   function validationError(err) {
208     // handle error on next tick for event listeners to attach
209     process.nextTick(handleError.bind(null, err))
210   }
211 };
212
213 Form.prototype._write = function(buffer, encoding, cb) {
214   if (this.error) return;
215
216   var self = this
217     , i = 0
218     , len = buffer.length
219     , prevIndex = self.index
220     , index = self.index
221     , state = self.state
222     , lookbehind = self.lookbehind
223     , boundary = self.boundary
224     , boundaryChars = self.boundaryChars
225     , boundaryLength = self.boundary.length
226     , boundaryEnd = boundaryLength - 1
227     , bufferLength = buffer.length
228     , c
229     , cl
230
231   for (i = 0; i < len; i++) {
232     c = buffer[i];
233     switch (state) {
234       case START:
235         index = 0;
236         state = START_BOUNDARY;
237         /* falls through */
238       case START_BOUNDARY:
239         if (index === boundaryLength - 2 && c === HYPHEN) {
240           index = 1;
241           state = CLOSE_BOUNDARY;
242           break;
243         } else if (index === boundaryLength - 2) {
244           if (c !== CR) return self.handleError(new Error("Expected CR Received " + c));
245           index++;
246           break;
247         } else if (index === boundaryLength - 1) {
248           if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
249           index = 0;
250           self.onParsePartBegin();
251           state = HEADER_FIELD_START;
252           break;
253         }
254
255         if (c !== boundary[index+2]) index = -2;
256         if (c === boundary[index+2]) index++;
257         break;
258       case HEADER_FIELD_START:
259         state = HEADER_FIELD;
260         self.headerFieldMark = i;
261         index = 0;
262         /* falls through */
263       case HEADER_FIELD:
264         if (c === CR) {
265           self.headerFieldMark = null;
266           state = HEADERS_ALMOST_DONE;
267           break;
268         }
269
270         index++;
271         if (c === HYPHEN) break;
272
273         if (c === COLON) {
274           if (index === 1) {
275             // empty header field
276             self.handleError(new Error("Empty header field"));
277             return;
278           }
279           self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
280           self.headerFieldMark = null;
281           state = HEADER_VALUE_START;
282           break;
283         }
284
285         cl = lower(c);
286         if (cl < A || cl > Z) {
287           self.handleError(new Error("Expected alphabetic character, received " + c));
288           return;
289         }
290         break;
291       case HEADER_VALUE_START:
292         if (c === SPACE) break;
293
294         self.headerValueMark = i;
295         state = HEADER_VALUE;
296         /* falls through */
297       case HEADER_VALUE:
298         if (c === CR) {
299           self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
300           self.headerValueMark = null;
301           self.onParseHeaderEnd();
302           state = HEADER_VALUE_ALMOST_DONE;
303         }
304         break;
305       case HEADER_VALUE_ALMOST_DONE:
306         if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
307         state = HEADER_FIELD_START;
308         break;
309       case HEADERS_ALMOST_DONE:
310         if (c !== LF) return self.handleError(new Error("Expected LF Received " + c));
311         var err = self.onParseHeadersEnd(i + 1);
312         if (err) return self.handleError(err);
313         state = PART_DATA_START;
314         break;
315       case PART_DATA_START:
316         state = PART_DATA;
317         self.partDataMark = i;
318         /* falls through */
319       case PART_DATA:
320         prevIndex = index;
321
322         if (index === 0) {
323           // boyer-moore derrived algorithm to safely skip non-boundary data
324           i += boundaryEnd;
325           while (i < bufferLength && !(buffer[i] in boundaryChars)) {
326             i += boundaryLength;
327           }
328           i -= boundaryEnd;
329           c = buffer[i];
330         }
331
332         if (index < boundaryLength) {
333           if (boundary[index] === c) {
334             if (index === 0) {
335               self.onParsePartData(buffer.slice(self.partDataMark, i));
336               self.partDataMark = null;
337             }
338             index++;
339           } else {
340             index = 0;
341           }
342         } else if (index === boundaryLength) {
343           index++;
344           if (c === CR) {
345             // CR = part boundary
346             self.partBoundaryFlag = true;
347           } else if (c === HYPHEN) {
348             index = 1;
349             state = CLOSE_BOUNDARY;
350             break;
351           } else {
352             index = 0;
353           }
354         } else if (index - 1 === boundaryLength)  {
355           if (self.partBoundaryFlag) {
356             index = 0;
357             if (c === LF) {
358               self.partBoundaryFlag = false;
359               self.onParsePartEnd();
360               self.onParsePartBegin();
361               state = HEADER_FIELD_START;
362               break;
363             }
364           } else {
365             index = 0;
366           }
367         }
368
369         if (index > 0) {
370           // when matching a possible boundary, keep a lookbehind reference
371           // in case it turns out to be a false lead
372           lookbehind[index-1] = c;
373         } else if (prevIndex > 0) {
374           // if our boundary turned out to be rubbish, the captured lookbehind
375           // belongs to partData
376           self.onParsePartData(lookbehind.slice(0, prevIndex));
377           prevIndex = 0;
378           self.partDataMark = i;
379
380           // reconsider the current character even so it interrupted the sequence
381           // it could be the beginning of a new sequence
382           i--;
383         }
384
385         break;
386       case CLOSE_BOUNDARY:
387         if (c !== HYPHEN) return self.handleError(new Error("Expected HYPHEN Received " + c));
388         if (index === 1) {
389           self.onParsePartEnd();
390           state = END;
391         } else if (index > 1) {
392           return self.handleError(new Error("Parser has invalid state."));
393         }
394         index++;
395         break;
396       case END:
397         break;
398       default:
399         self.handleError(new Error("Parser has invalid state."));
400         return;
401     }
402   }
403
404   if (self.headerFieldMark != null) {
405     self.onParseHeaderField(buffer.slice(self.headerFieldMark));
406     self.headerFieldMark = 0;
407   }
408   if (self.headerValueMark != null) {
409     self.onParseHeaderValue(buffer.slice(self.headerValueMark));
410     self.headerValueMark = 0;
411   }
412   if (self.partDataMark != null) {
413     self.onParsePartData(buffer.slice(self.partDataMark));
414     self.partDataMark = 0;
415   }
416
417   self.index = index;
418   self.state = state;
419
420   self.bytesReceived += buffer.length;
421   self.emit('progress', self.bytesReceived, self.bytesExpected);
422
423   if (self.backpressure) {
424     self.writeCbs.push(cb);
425   } else {
426     cb();
427   }
428 };
429
430 Form.prototype.onParsePartBegin = function() {
431   clearPartVars(this);
432 }
433
434 Form.prototype.onParseHeaderField = function(b) {
435   this.headerField += this.headerFieldDecoder.write(b);
436 }
437
438 Form.prototype.onParseHeaderValue = function(b) {
439   this.headerValue += this.headerValueDecoder.write(b);
440 }
441
442 Form.prototype.onParseHeaderEnd = function() {
443   this.headerField = this.headerField.toLowerCase();
444   this.partHeaders[this.headerField] = this.headerValue;
445
446   var m;
447   if (this.headerField === 'content-disposition') {
448     if (m = this.headerValue.match(/\bname="([^"]+)"/i)) {
449       this.partName = m[1];
450     }
451     this.partFilename = parseFilename(this.headerValue);
452   } else if (this.headerField === 'content-transfer-encoding') {
453     this.partTransferEncoding = this.headerValue.toLowerCase();
454   }
455
456   this.headerFieldDecoder = new StringDecoder(this.encoding);
457   this.headerField = '';
458   this.headerValueDecoder = new StringDecoder(this.encoding);
459   this.headerValue = '';
460 }
461
462 Form.prototype.onParsePartData = function(b) {
463   if (this.partTransferEncoding === 'base64') {
464     this.backpressure = ! this.destStream.write(b.toString('ascii'), 'base64');
465   } else {
466     this.backpressure = ! this.destStream.write(b);
467   }
468 }
469
470 Form.prototype.onParsePartEnd = function() {
471   if (this.destStream) {
472     flushWriteCbs(this);
473     var s = this.destStream;
474     process.nextTick(function() {
475       s.end();
476     });
477   }
478   clearPartVars(this);
479 }
480
481 Form.prototype.onParseHeadersEnd = function(offset) {
482   var self = this;
483   switch(self.partTransferEncoding){
484     case 'binary':
485     case '7bit':
486     case '8bit':
487     self.partTransferEncoding = 'binary';
488     break;
489
490     case 'base64': break;
491     default:
492     return new Error("unknown transfer-encoding: " + self.partTransferEncoding);
493   }
494
495   self.totalFieldCount += 1;
496   if (self.totalFieldCount > self.maxFields) {
497     return new Error("maxFields " + self.maxFields + " exceeded.");
498   }
499
500   self.destStream = new stream.PassThrough();
501   self.destStream.on('drain', function() {
502     flushWriteCbs(self);
503   });
504   self.destStream.headers = self.partHeaders;
505   self.destStream.name = self.partName;
506   self.destStream.filename = self.partFilename;
507   self.destStream.byteOffset = self.bytesReceived + offset;
508   var partContentLength = self.destStream.headers['content-length'];
509   self.destStream.byteCount = partContentLength ? parseInt(partContentLength, 10) :
510     self.bytesExpected ? (self.bytesExpected - self.destStream.byteOffset -
511       self.boundary.length - LAST_BOUNDARY_SUFFIX_LEN) :
512     undefined;
513
514   self.emit('part', self.destStream);
515   if (self.destStream.filename == null && self.autoFields) {
516     handleField(self, self.destStream);
517   } else if (self.destStream.filename != null && self.autoFiles) {
518     handleFile(self, self.destStream);
519   } else {
520     beginFlush(self);
521     self.destStream.on('end', function(){
522       endFlush(self);
523     });
524   }
525 }
526
527 function flushWriteCbs(self) {
528   self.writeCbs.forEach(function(cb) {
529     process.nextTick(cb);
530   });
531   self.writeCbs = [];
532   self.backpressure = false;
533 }
534
535 function getBytesExpected(headers) {
536   var contentLength = headers['content-length'];
537   if (contentLength) {
538     return parseInt(contentLength, 10);
539   } else if (headers['transfer-encoding'] == null) {
540     return 0;
541   } else {
542     return null;
543   }
544 }
545
546 function beginFlush(self) {
547   self.flushing += 1;
548 }
549
550 function endFlush(self) {
551   self.flushing -= 1;
552   maybeClose(self);
553 }
554
555 function maybeClose(self) {
556   if (!self.flushing && self.finished && !self.error) {
557     self.emit('close');
558   }
559 }
560
561 function destroyFile(self, file) {
562   if (!file.ws) return;
563   file.ws.removeAllListeners('close');
564   file.ws.on('close', function() {
565     fs.unlink(file.path, function(err) {
566       if (err && !self.error) self.handleError(err);
567     });
568   });
569   file.ws.destroy();
570 }
571
572 function handleFile(self, fileStream) {
573   if (self.error) return;
574   var file = {
575     fieldName: fileStream.name,
576     originalFilename: fileStream.filename,
577     path: uploadPath(self.uploadDir, fileStream.filename),
578     headers: fileStream.headers,
579   };
580   beginFlush(self); // flush to write stream
581   file.ws = fs.createWriteStream(file.path);
582   self.openedFiles.push(file);
583   fileStream.pipe(file.ws);
584   var counter = new StreamCounter();
585   var seenBytes = 0;
586   fileStream.pipe(counter);
587   var hashWorkaroundStream
588     , hash = null;
589   if (self.hash) {
590     // workaround stream because https://github.com/joyent/node/issues/5216
591     hashWorkaroundStream = stream.Writable();
592     hash = crypto.createHash(self.hash);
593     hashWorkaroundStream._write = function(buffer, encoding, callback) {
594       hash.update(buffer);
595       callback();
596     };
597     fileStream.pipe(hashWorkaroundStream);
598   }
599   counter.on('progress', function() {
600     var deltaBytes = counter.bytes - seenBytes;
601     seenBytes += deltaBytes;
602     self.totalFileSize += deltaBytes;
603     if (self.totalFileSize > self.maxFilesSize) {
604       if (hashWorkaroundStream) fileStream.unpipe(hashWorkaroundStream);
605       fileStream.unpipe(counter);
606       fileStream.unpipe(file.ws);
607       self.handleError(new Error("maxFilesSize " + self.maxFilesSize + " exceeded"));
608     }
609   });
610   file.ws.on('error', function(err) {
611     if (!self.error) self.handleError(err);
612   });
613   file.ws.on('close', function() {
614     if (hash) file.hash = hash.digest('hex');
615     file.size = counter.bytes;
616     self.emit('file', fileStream.name, file);
617     endFlush(self);
618   });
619   beginFlush(self); // flush from file stream
620   fileStream.on('end', function(){
621     endFlush(self);
622   });
623 }
624
625 function handleField(self, fieldStream) {
626   var value = '';
627   var decoder = new StringDecoder(self.encoding);
628
629   beginFlush(self);
630   fieldStream.on('readable', function() {
631     var buffer = fieldStream.read();
632     if (!buffer) return;
633
634     self.totalFieldSize += buffer.length;
635     if (self.totalFieldSize > self.maxFieldsSize) {
636       self.handleError(new Error("maxFieldsSize " + self.maxFieldsSize + " exceeded"));
637       return;
638     }
639     value += decoder.write(buffer);
640   });
641
642   fieldStream.on('end', function() {
643     self.emit('field', fieldStream.name, value);
644     endFlush(self);
645   });
646 }
647
648 function clearPartVars(self) {
649   self.partHeaders = {};
650   self.partName = null;
651   self.partFilename = null;
652   self.partTransferEncoding = 'binary';
653   self.destStream = null;
654
655   self.headerFieldDecoder = new StringDecoder(self.encoding);
656   self.headerField = "";
657   self.headerValueDecoder = new StringDecoder(self.encoding);
658   self.headerValue = "";
659 }
660
661 function setUpParser(self, boundary) {
662   self.boundary = new Buffer(boundary.length + 4);
663   self.boundary.write('\r\n--', 0, boundary.length + 4, 'ascii');
664   self.boundary.write(boundary, 4, boundary.length, 'ascii');
665   self.lookbehind = new Buffer(self.boundary.length + 8);
666   self.state = START;
667   self.boundaryChars = {};
668   for (var i = 0; i < self.boundary.length; i++) {
669     self.boundaryChars[self.boundary[i]] = true;
670   }
671
672   self.index = null;
673   self.partBoundaryFlag = false;
674
675   self.on('finish', function() {
676     if ((self.state === HEADER_FIELD_START && self.index === 0) ||
677         (self.state === PART_DATA && self.index === self.boundary.length))
678     {
679       self.onParsePartEnd();
680     } else if (self.state !== END) {
681       self.handleError(new Error('stream ended unexpectedly'));
682     }
683     self.finished = true;
684     maybeClose(self);
685   });
686 }
687
688 function uploadPath(baseDir, filename) {
689   var ext = path.extname(filename).replace(FILE_EXT_RE, '$1');
690   var name = process.pid + '-' +
691     (Math.random() * 0x100000000 + 1).toString(36) + ext;
692   return path.join(baseDir, name);
693 }
694
695 function parseFilename(headerValue) {
696   var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
697   if (!m) {
698     m = headerValue.match(/\bfilename\*=utf-8\'\'(.*?)($|; )/i);
699     if (m) {
700       m[1] = decodeURI(m[1]);
701     }
702     else {
703       return;
704     }
705   }
706
707   var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
708   filename = filename.replace(/%22/g, '"');
709   filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
710     return String.fromCharCode(code);
711   });
712   return filename;
713 }
714
715 function lower(c) {
716   return c | 0x20;
717 }
718