arnaucube
arnaucube/raspberryGPIOhtmlserverPublic Self-hosted repository
raspberryGPIOhtmlserver/node_modules/formidable/lib/incoming_form.js
node_modules/formidable/lib/incoming_form.jsRaw
  1if (global.GENTLY) require = GENTLY.hijack(require);
  2
  3var fs = require('fs');
  4var util = require('util'),
  5    path = require('path'),
  6    File = require('./file'),
  7    MultipartParser = require('./multipart_parser').MultipartParser,
  8    QuerystringParser = require('./querystring_parser').QuerystringParser,
  9    OctetParser       = require('./octet_parser').OctetParser,
 10    JSONParser = require('./json_parser').JSONParser,
 11    StringDecoder = require('string_decoder').StringDecoder,
 12    EventEmitter = require('events').EventEmitter,
 13    Stream = require('stream').Stream,
 14    os = require('os');
 15
 16function IncomingForm(opts) {
 17  if (!(this instanceof IncomingForm)) return new IncomingForm(opts);
 18  EventEmitter.call(this);
 19
 20  opts=opts||{};
 21
 22  this.error = null;
 23  this.ended = false;
 24
 25  this.maxFields = opts.maxFields || 1000;
 26  this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024;
 27  this.keepExtensions = opts.keepExtensions || false;
 28  this.uploadDir = opts.uploadDir || os.tmpDir();
 29  this.encoding = opts.encoding || 'utf-8';
 30  this.headers = null;
 31  this.type = null;
 32  this.hash = false;
 33
 34  this.bytesReceived = null;
 35  this.bytesExpected = null;
 36
 37  this._parser = null;
 38  this._flushing = 0;
 39  this._fieldsSize = 0;
 40  this.openedFiles = [];
 41
 42  return this;
 43};
 44util.inherits(IncomingForm, EventEmitter);
 45exports.IncomingForm = IncomingForm;
 46
 47IncomingForm.prototype.parse = function(req, cb) {
 48  this.pause = function() {
 49    try {
 50      req.pause();
 51    } catch (err) {
 52      // the stream was destroyed
 53      if (!this.ended) {
 54        // before it was completed, crash & burn
 55        this._error(err);
 56      }
 57      return false;
 58    }
 59    return true;
 60  };
 61
 62  this.resume = function() {
 63    try {
 64      req.resume();
 65    } catch (err) {
 66      // the stream was destroyed
 67      if (!this.ended) {
 68        // before it was completed, crash & burn
 69        this._error(err);
 70      }
 71      return false;
 72    }
 73
 74    return true;
 75  };
 76
 77  // Setup callback first, so we don't miss anything from data events emitted
 78  // immediately.
 79  if (cb) {
 80    var fields = {}, files = {};
 81    this
 82      .on('field', function(name, value) {
 83        fields[name] = value;
 84      })
 85      .on('file', function(name, file) {
 86        files[name] = file;
 87      })
 88      .on('error', function(err) {
 89        cb(err, fields, files);
 90      })
 91      .on('end', function() {
 92        cb(null, fields, files);
 93      });
 94  }
 95
 96  // Parse headers and setup the parser, ready to start listening for data.
 97  this.writeHeaders(req.headers);
 98
 99  // Start listening for data.
100  var self = this;
101  req
102    .on('error', function(err) {
103      self._error(err);
104    })
105    .on('aborted', function() {
106      self.emit('aborted');
107      self._error(new Error('Request aborted'));
108    })
109    .on('data', function(buffer) {
110      self.write(buffer);
111    })
112    .on('end', function() {
113      if (self.error) {
114        return;
115      }
116
117      var err = self._parser.end();
118      if (err) {
119        self._error(err);
120      }
121    });
122
123  return this;
124};
125
126IncomingForm.prototype.writeHeaders = function(headers) {
127  this.headers = headers;
128  this._parseContentLength();
129  this._parseContentType();
130};
131
132IncomingForm.prototype.write = function(buffer) {
133  if (!this._parser) {
134    this._error(new Error('unintialized parser'));
135    return;
136  }
137
138  this.bytesReceived += buffer.length;
139  this.emit('progress', this.bytesReceived, this.bytesExpected);
140
141  var bytesParsed = this._parser.write(buffer);
142  if (bytesParsed !== buffer.length) {
143    this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
144  }
145
146  return bytesParsed;
147};
148
149IncomingForm.prototype.pause = function() {
150  // this does nothing, unless overwritten in IncomingForm.parse
151  return false;
152};
153
154IncomingForm.prototype.resume = function() {
155  // this does nothing, unless overwritten in IncomingForm.parse
156  return false;
157};
158
159IncomingForm.prototype.onPart = function(part) {
160  // this method can be overwritten by the user
161  this.handlePart(part);
162};
163
164IncomingForm.prototype.handlePart = function(part) {
165  var self = this;
166
167  if (part.filename === undefined) {
168    var value = ''
169      , decoder = new StringDecoder(this.encoding);
170
171    part.on('data', function(buffer) {
172      self._fieldsSize += buffer.length;
173      if (self._fieldsSize > self.maxFieldsSize) {
174        self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
175        return;
176      }
177      value += decoder.write(buffer);
178    });
179
180    part.on('end', function() {
181      self.emit('field', part.name, value);
182    });
183    return;
184  }
185
186  this._flushing++;
187
188  var file = new File({
189    path: this._uploadPath(part.filename),
190    name: part.filename,
191    type: part.mime,
192    hash: self.hash
193  });
194
195  this.emit('fileBegin', part.name, file);
196
197  file.open();
198  this.openedFiles.push(file);
199
200  part.on('data', function(buffer) {
201    self.pause();
202    file.write(buffer, function() {
203      self.resume();
204    });
205  });
206
207  part.on('end', function() {
208    file.end(function() {
209      self._flushing--;
210      self.emit('file', part.name, file);
211      self._maybeEnd();
212    });
213  });
214};
215
216function dummyParser(self) {
217  return {
218    end: function () {
219      self.ended = true;
220      self._maybeEnd();
221      return null;
222    }
223  };
224}
225
226IncomingForm.prototype._parseContentType = function() {
227  if (this.bytesExpected === 0) {
228    this._parser = dummyParser(this);
229    return;
230  }
231
232  if (!this.headers['content-type']) {
233    this._error(new Error('bad content-type header, no content-type'));
234    return;
235  }
236
237  if (this.headers['content-type'].match(/octet-stream/i)) {
238    this._initOctetStream();
239    return;
240  }
241
242  if (this.headers['content-type'].match(/urlencoded/i)) {
243    this._initUrlencoded();
244    return;
245  }
246
247  if (this.headers['content-type'].match(/multipart/i)) {
248    var m;
249    if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) {
250      this._initMultipart(m[1] || m[2]);
251    } else {
252      this._error(new Error('bad content-type header, no multipart boundary'));
253    }
254    return;
255  }
256
257  if (this.headers['content-type'].match(/json/i)) {
258    this._initJSONencoded();
259    return;
260  }
261
262  this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type']));
263};
264
265IncomingForm.prototype._error = function(err) {
266  if (this.error || this.ended) {
267    return;
268  }
269
270  this.error = err;
271  this.pause();
272  this.emit('error', err);
273
274  if (Array.isArray(this.openedFiles)) {
275    this.openedFiles.forEach(function(file) {
276      file._writeStream.destroy();
277      setTimeout(fs.unlink, 0, file.path);
278    });
279  }
280};
281
282IncomingForm.prototype._parseContentLength = function() {
283  this.bytesReceived = 0;
284  if (this.headers['content-length']) {
285    this.bytesExpected = parseInt(this.headers['content-length'], 10);
286  } else if (this.headers['transfer-encoding'] === undefined) {
287    this.bytesExpected = 0;
288  }
289
290  if (this.bytesExpected !== null) {
291    this.emit('progress', this.bytesReceived, this.bytesExpected);
292  }
293};
294
295IncomingForm.prototype._newParser = function() {
296  return new MultipartParser();
297};
298
299IncomingForm.prototype._initMultipart = function(boundary) {
300  this.type = 'multipart';
301
302  var parser = new MultipartParser(),
303      self = this,
304      headerField,
305      headerValue,
306      part;
307
308  parser.initWithBoundary(boundary);
309
310  parser.onPartBegin = function() {
311    part = new Stream();
312    part.readable = true;
313    part.headers = {};
314    part.name = null;
315    part.filename = null;
316    part.mime = null;
317
318    part.transferEncoding = 'binary';
319    part.transferBuffer = '';
320
321    headerField = '';
322    headerValue = '';
323  };
324
325  parser.onHeaderField = function(b, start, end) {
326    headerField += b.toString(self.encoding, start, end);
327  };
328
329  parser.onHeaderValue = function(b, start, end) {
330    headerValue += b.toString(self.encoding, start, end);
331  };
332
333  parser.onHeaderEnd = function() {
334    headerField = headerField.toLowerCase();
335    part.headers[headerField] = headerValue;
336
337    var m;
338    if (headerField == 'content-disposition') {
339      if (m = headerValue.match(/\bname="([^"]+)"/i)) {
340        part.name = m[1];
341      }
342
343      part.filename = self._fileName(headerValue);
344    } else if (headerField == 'content-type') {
345      part.mime = headerValue;
346    } else if (headerField == 'content-transfer-encoding') {
347      part.transferEncoding = headerValue.toLowerCase();
348    }
349
350    headerField = '';
351    headerValue = '';
352  };
353
354  parser.onHeadersEnd = function() {
355    switch(part.transferEncoding){
356      case 'binary':
357      case '7bit':
358      case '8bit':
359      parser.onPartData = function(b, start, end) {
360        part.emit('data', b.slice(start, end));
361      };
362
363      parser.onPartEnd = function() {
364        part.emit('end');
365      };
366      break;
367
368      case 'base64':
369      parser.onPartData = function(b, start, end) {
370        part.transferBuffer += b.slice(start, end).toString('ascii');
371
372        /*
373        four bytes (chars) in base64 converts to three bytes in binary
374        encoding. So we should always work with a number of bytes that
375        can be divided by 4, it will result in a number of buytes that
376        can be divided vy 3.
377        */
378        var offset = parseInt(part.transferBuffer.length / 4) * 4;
379        part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64'))
380        part.transferBuffer = part.transferBuffer.substring(offset);
381      };
382
383      parser.onPartEnd = function() {
384        part.emit('data', new Buffer(part.transferBuffer, 'base64'))
385        part.emit('end');
386      };
387      break;
388
389      default:
390      return self._error(new Error('unknown transfer-encoding'));
391    }
392
393    self.onPart(part);
394  };
395
396
397  parser.onEnd = function() {
398    self.ended = true;
399    self._maybeEnd();
400  };
401
402  this._parser = parser;
403};
404
405IncomingForm.prototype._fileName = function(headerValue) {
406  var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
407  if (!m) return;
408
409  var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
410  filename = filename.replace(/%22/g, '"');
411  filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
412    return String.fromCharCode(code);
413  });
414  return filename;
415};
416
417IncomingForm.prototype._initUrlencoded = function() {
418  this.type = 'urlencoded';
419
420  var parser = new QuerystringParser(this.maxFields)
421    , self = this;
422
423  parser.onField = function(key, val) {
424    self.emit('field', key, val);
425  };
426
427  parser.onEnd = function() {
428    self.ended = true;
429    self._maybeEnd();
430  };
431
432  this._parser = parser;
433};
434
435IncomingForm.prototype._initOctetStream = function() {
436  this.type = 'octet-stream';
437  var filename = this.headers['x-file-name'];
438  var mime = this.headers['content-type'];
439
440  var file = new File({
441    path: this._uploadPath(filename),
442    name: filename,
443    type: mime
444  });
445
446  file.open();
447
448  this.emit('fileBegin', filename, file);
449
450  this._flushing++;
451
452  var self = this;
453
454  self._parser = new OctetParser();
455
456  //Keep track of writes that haven't finished so we don't emit the file before it's done being written
457  var outstandingWrites = 0;
458
459  self._parser.on('data', function(buffer){
460    self.pause();
461    outstandingWrites++;
462
463    file.write(buffer, function() {
464      outstandingWrites--;
465      self.resume();
466
467      if(self.ended){
468        self._parser.emit('doneWritingFile');
469      }
470    });
471  });
472
473  self._parser.on('end', function(){
474    self._flushing--;
475    self.ended = true;
476
477    var done = function(){
478      self.emit('file', 'file', file);
479      self._maybeEnd();
480    };
481
482    if(outstandingWrites === 0){
483      done();
484    } else {
485      self._parser.once('doneWritingFile', done);
486    }
487  });
488};
489
490IncomingForm.prototype._initJSONencoded = function() {
491  this.type = 'json';
492
493  var parser = new JSONParser()
494    , self = this;
495
496  if (this.bytesExpected) {
497    parser.initWithLength(this.bytesExpected);
498  }
499
500  parser.onField = function(key, val) {
501    self.emit('field', key, val);
502  }
503
504  parser.onEnd = function() {
505    self.ended = true;
506    self._maybeEnd();
507  };
508
509  this._parser = parser;
510};
511
512IncomingForm.prototype._uploadPath = function(filename) {
513  var name = '';
514  for (var i = 0; i < 32; i++) {
515    name += Math.floor(Math.random() * 16).toString(16);
516  }
517
518  if (this.keepExtensions) {
519    var ext = path.extname(filename);
520    ext     = ext.replace(/(\.[a-z0-9]+).*/, '$1');
521
522    name += ext;
523  }
524
525  return path.join(this.uploadDir, name);
526};
527
528IncomingForm.prototype._maybeEnd = function() {
529  if (!this.ended || this._flushing || this.error) {
530    return;
531  }
532
533  this.emit('end');
534};
535