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