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