프로젝트

일반

사용자정보

통계
| 개정판:

root / HServer / 00.Server / 00.Program / node_modules / multiparty / index.js

이력 | 보기 | 이력해설 | 다운로드 (21.4 KB)

1 39 HKM
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
}