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