Name: js-handler/node_modules/restify/node_modules/formidable/lib/incoming_form.js 
1:
if (global.GENTLY) require = GENTLY.hijack(require);
2:
 
3:
var fs = require('fs');
4:
var 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:
 
16:
function 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:
};
44:
util.inherits(IncomingForm, EventEmitter);
45:
exports.IncomingForm = IncomingForm;
46:
 
47:
IncomingForm.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:
 
126:
IncomingForm.prototype.writeHeaders = function(headers) {
127:
  this.headers = headers;
128:
  this._parseContentLength();
129:
  this._parseContentType();
130:
};
131:
 
132:
IncomingForm.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:
 
149:
IncomingForm.prototype.pause = function() {
150:
  // this does nothing, unless overwritten in IncomingForm.parse
151:
  return false;
152:
};
153:
 
154:
IncomingForm.prototype.resume = function() {
155:
  // this does nothing, unless overwritten in IncomingForm.parse
156:
  return false;
157:
};
158:
 
159:
IncomingForm.prototype.onPart = function(part) {
160:
  // this method can be overwritten by the user
161:
  this.handlePart(part);
162:
};
163:
 
164:
IncomingForm.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:
 
216:
function dummyParser(self) {
217:
  return {
218:
    end: function () {
219:
      self.ended = true;
220:
      self._maybeEnd();
221:
      return null;
222:
    }
223:
  };
224:
}
225:
 
226:
IncomingForm.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:
 
265:
IncomingForm.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:
 
282:
IncomingForm.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:
 
295:
IncomingForm.prototype._newParser = function() {
296:
  return new MultipartParser();
297:
};
298:
 
299:
IncomingForm.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:
 
405:
IncomingForm.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:
 
417:
IncomingForm.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:
 
435:
IncomingForm.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:
 
490:
IncomingForm.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:
 
512:
IncomingForm.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:
 
528:
IncomingForm.prototype._maybeEnd = function() {
529:
  if (!this.ended || this._flushing || this.error) {
530:
    return;
531:
  }
532:
 
533:
  this.emit('end');
534:
};