@poppinss/multiparty 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +3 -0
  2. package/index.js +65 -22
  3. package/package.json +2 -3
  4. package/HISTORY.md +0 -255
package/README.md CHANGED
@@ -84,6 +84,9 @@ Creates a new form. Options:
84
84
  * `maxFilesSize` - Only relevant when `autoFiles` is `true`. Limits the
85
85
  total bytes accepted for all files combined. If this value is exceeded,
86
86
  an `error` event is emitted. The default is `Infinity`.
87
+ * `maxHeaderSize` - Limits the size of header names and values for each part.
88
+ If this value is exceeded, an `error` event is emitted. The default size is
89
+ 16KB.
87
90
  * `autoFields` - Enables `field` events and disables `part` events for fields.
88
91
  This is automatically set to `true` if you add a `field` listener.
89
92
  * `autoFiles` - Enables `file` events and disables `part` events for files.
package/index.js CHANGED
@@ -39,11 +39,11 @@ var A = 97;
39
39
  var Z = 122;
40
40
 
41
41
  var CONTENT_TYPE_RE = /^multipart\/(?:form-data|related)(?:;|$)/i;
42
- var CONTENT_TYPE_PARAM_RE = /;\s*([^=]+)=(?:"([^"]+)"|([^;]+))/gi;
42
+ var CONTENT_TYPE_BOUNDARY_RE = /;[ \t]*boundary=(?:"([^"]*)"|([^;]+))/gi;
43
43
  var FILE_EXT_RE = /(\.[_\-a-zA-Z0-9]{0,16})[\S\s]*/;
44
- var FILENAME_PARAM_RE = /\bfilename=(?:"(.*?)"|([!#$%&'*+.0-9A-Z^_`a-z|~-]+))($|; )/i
44
+ var FILENAME_PARAM_RE = /;[ \t]*filename=(?:"((?:[^"]|"(?![ \t]*(?:;|$)))*)"|([^;]+))/i
45
45
  var LAST_BOUNDARY_SUFFIX_LEN = 4; // --\r\n
46
- var NAME_PARAM_RE = /\bname=(?:"([^"]+)"|([!#$%&'*+.0-9A-Z^_`a-z|~-]+))/i
46
+ var NAME_PARAM_RE = /;[ \t]*name=(?:"([^"]*)"|([^;]+))/i
47
47
 
48
48
  exports.Form = Form;
49
49
 
@@ -75,6 +75,7 @@ function Form(options) {
75
75
  self.maxFields = opts.maxFields || 1000
76
76
  self.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024
77
77
  self.maxFilesSize = opts.maxFilesSize || Infinity
78
+ self.maxHeaderSize = opts.maxHeaderSize || 16 * 1024
78
79
  self.uploadDir = opts.uploadDir || os.tmpdir()
79
80
  self.encoding = opts.encoding || 'utf8'
80
81
 
@@ -140,12 +141,24 @@ Form.prototype.parse = function(req, cb) {
140
141
  });
141
142
  });
142
143
  self.on('field', function(name, value) {
143
- var fieldsArray = fields[name] || (fields[name] = []);
144
- fieldsArray.push(value);
144
+ var fieldsArray = Object.prototype.hasOwnProperty.call(fields, name)
145
+ ? fields[name]
146
+ : undefined;
147
+ if (Array.isArray(fieldsArray)) {
148
+ fieldsArray.push(value);
149
+ } else {
150
+ fields[name] = [value]
151
+ }
145
152
  });
146
153
  self.on('file', function(name, file) {
147
- var filesArray = files[name] || (files[name] = []);
148
- filesArray.push(file);
154
+ var filesArray = Object.prototype.hasOwnProperty.call(files, name)
155
+ ? files[name]
156
+ : undefined;
157
+ if (Array.isArray(filesArray)) {
158
+ filesArray.push(file);
159
+ } else {
160
+ files[name] = [file]
161
+ }
149
162
  });
150
163
  self.on('close', function() {
151
164
  end(function() {
@@ -185,10 +198,9 @@ Form.prototype.parse = function(req, cb) {
185
198
  }
186
199
 
187
200
  var boundary;
188
- CONTENT_TYPE_PARAM_RE.lastIndex = m.index + m[0].length - 1;
189
- while ((m = CONTENT_TYPE_PARAM_RE.exec(contentType))) {
190
- if (m[1].toLowerCase() !== 'boundary') continue;
191
- boundary = m[2] || m[3];
201
+ CONTENT_TYPE_BOUNDARY_RE.lastIndex = m.index + m[0].length - 1;
202
+ while ((m = CONTENT_TYPE_BOUNDARY_RE.exec(contentType))) {
203
+ boundary = m[1] || m[2];
192
204
  break;
193
205
  }
194
206
 
@@ -305,6 +317,7 @@ Form.prototype._write = function(buffer, encoding, cb) {
305
317
  return;
306
318
  }
307
319
  self.onParseHeaderField(buffer.slice(self.headerFieldMark, i));
320
+ if (self.error) return;
308
321
  self.headerFieldMark = null;
309
322
  state = HEADER_VALUE_START;
310
323
  break;
@@ -325,6 +338,7 @@ Form.prototype._write = function(buffer, encoding, cb) {
325
338
  case HEADER_VALUE:
326
339
  if (c === CR) {
327
340
  self.onParseHeaderValue(buffer.slice(self.headerValueMark, i));
341
+ if (self.error) return;
328
342
  self.headerValueMark = null;
329
343
  self.onParseHeaderEnd();
330
344
  state = HEADER_VALUE_ALMOST_DONE;
@@ -460,10 +474,20 @@ Form.prototype.onParsePartBegin = function() {
460
474
  }
461
475
 
462
476
  Form.prototype.onParseHeaderField = function(b) {
477
+ this.partHeadersSize += b.length;
478
+ if (this.partHeadersSize > this.maxHeaderSize) {
479
+ this.handleError(createError(413, 'maxHeaderSize ' + this.maxHeaderSize + ' exceeded'));
480
+ return;
481
+ }
463
482
  this.headerField += this.headerFieldDecoder.write(b);
464
483
  }
465
484
 
466
485
  Form.prototype.onParseHeaderValue = function(b) {
486
+ this.partHeadersSize += b.length;
487
+ if (this.partHeadersSize > this.maxHeaderSize) {
488
+ this.handleError(createError(413, 'maxHeaderSize ' + this.maxHeaderSize + ' exceeded'));
489
+ return;
490
+ }
467
491
  this.headerValue += this.headerValueDecoder.write(b);
468
492
  }
469
493
 
@@ -489,7 +513,13 @@ Form.prototype.onParseHeaderEnd = function() {
489
513
 
490
514
  Form.prototype.onParsePartData = function(b) {
491
515
  if (this.partTransferEncoding === 'base64') {
492
- this.backpressure = ! this.destStream.write(b.toString('ascii'), 'base64');
516
+ // Count base64 characters, not whitespace, and retain incomplete groups.
517
+ var data = this.partRemainder + b.toString('binary').replace(/[\t\n\v\f\r ]/g, '');
518
+ var length = data.length - (data.length % 4);
519
+ this.partRemainder = data.slice(length);
520
+ if (length > 0) {
521
+ this.backpressure = ! this.destStream.write(data.slice(0, length), this.partTransferEncoding);
522
+ }
493
523
  } else {
494
524
  this.backpressure = ! this.destStream.write(b);
495
525
  }
@@ -499,8 +529,10 @@ Form.prototype.onParsePartEnd = function() {
499
529
  if (this.destStream) {
500
530
  flushWriteCbs(this);
501
531
  var s = this.destStream;
532
+ var remainder = this.partRemainder;
533
+ var encoding = this.partTransferEncoding;
502
534
  process.nextTick(function() {
503
- s.end();
535
+ s.end(remainder, encoding);
504
536
  });
505
537
  }
506
538
  clearPartVars(this);
@@ -755,15 +787,16 @@ function handleField(self, fieldStream) {
755
787
  self.handleError(err);
756
788
  });
757
789
  fieldStream.on('readable', function() {
758
- var buffer = fieldStream.read();
759
- if (!buffer) return;
790
+ var buffer;
791
+ while ((buffer = fieldStream.read()) !== null) {
792
+ self.totalFieldSize += buffer.length;
793
+ if (self.totalFieldSize > self.maxFieldsSize) {
794
+ self.handleError(createError(413, 'maxFieldsSize ' + self.maxFieldsSize + ' exceeded'));
795
+ return;
796
+ }
760
797
 
761
- self.totalFieldSize += buffer.length;
762
- if (self.totalFieldSize > self.maxFieldsSize) {
763
- self.handleError(createError(413, 'maxFieldsSize ' + self.maxFieldsSize + ' exceeded'));
764
- return;
798
+ value += decoder.write(buffer);
765
799
  }
766
- value += decoder.write(buffer);
767
800
  });
768
801
 
769
802
  fieldStream.on('end', function() {
@@ -796,9 +829,11 @@ function hasListeners (emitter, type) {
796
829
 
797
830
  function clearPartVars(self) {
798
831
  self.partHeaders = {};
832
+ self.partHeadersSize = 0;
799
833
  self.partName = null;
800
834
  self.partFilename = null;
801
835
  self.partTransferEncoding = 'binary';
836
+ self.partRemainder = '';
802
837
  self.destStream = null;
803
838
 
804
839
  self.headerFieldDecoder = new StringDecoder(self.encoding);
@@ -839,9 +874,9 @@ function uploadPath(baseDir, filename) {
839
874
  function parseFilename(headerValue) {
840
875
  var m = FILENAME_PARAM_RE.exec(headerValue)
841
876
  if (!m) {
842
- m = headerValue.match(/\bfilename\*=utf-8''(.*?)($|; )/i)
877
+ m = headerValue.match(/;[ \t]*filename\*=utf-8''([^;]*)/i)
843
878
  if (m) {
844
- m[1] = decodeURI(m[1]);
879
+ m[1] = tryDecode(m[1]);
845
880
  } else {
846
881
  return;
847
882
  }
@@ -858,3 +893,11 @@ function parseFilename(headerValue) {
858
893
  function lower(c) {
859
894
  return c | 0x20;
860
895
  }
896
+
897
+ function tryDecode(value) {
898
+ try {
899
+ return decodeURIComponent(value);
900
+ } catch (e) {
901
+ return undefined;
902
+ }
903
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poppinss/multiparty",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "multipart/form-data parser which supports streaming",
5
5
  "main": "index.js",
6
6
  "contributors": [
@@ -22,7 +22,7 @@
22
22
  "eslint": "^8.9.0",
23
23
  "eslint-plugin-markdown": "2.2.1",
24
24
  "mkdirp": "^1.0.4",
25
- "mocha": "^9.2.1",
25
+ "mocha": "^10.8.2",
26
26
  "nyc": "15.1.0",
27
27
  "pend": "1.2.0",
28
28
  "require-all": "3.0.0",
@@ -30,7 +30,6 @@
30
30
  "superagent": "^7.1.1"
31
31
  },
32
32
  "files": [
33
- "HISTORY.md",
34
33
  "LICENSE",
35
34
  "README.md",
36
35
  "index.js"
package/HISTORY.md DELETED
@@ -1,255 +0,0 @@
1
- unreleased
2
- ==========
3
-
4
- * Fix form parsing when no `part` event listener added
5
-
6
- 4.2.3 / 2022-01-20
7
- ==================
8
-
9
- * Fix handling of unquoted values in `Content-Disposition`
10
- * deps: http-errors@~1.8.1
11
- - deps: toidentifier@1.0.1
12
-
13
- 4.2.2 / 2020-07-27
14
- ==================
15
-
16
- * Fix empty files on Node.js 14.x
17
- * Fix form emitting aborted error after close
18
- * Replace `fd-slicer` module with internal transform stream
19
- * deps: http-errors@~1.8.0
20
- - Fix error creating objects in some environments
21
- - deps: inherits@2.0.4
22
- - deps: setprototypeof@1.2.0
23
- * deps: safe-buffer@5.2.1
24
-
25
- 4.2.1 / 2018-08-12
26
- ==================
27
-
28
- * Use `uid-safe` module to for temp file names
29
- * deps: fd-slicer@1.1.0
30
- * deps: http-errors@~1.7.0
31
-
32
- 4.2.0 / 2018-07-30
33
- ==================
34
-
35
- * Use `http-errors` for raised errors
36
- * Use `random-bytes` module for polyfill
37
- * perf: remove parameter reassignment
38
-
39
- 4.1.4 / 2018-05-11
40
- ==================
41
-
42
- * Fix file extension filtering stopping on certain whitespace characters
43
- * Use `safe-buffer` for improved API safety
44
- * perf: enable strict mode
45
-
46
- 4.1.3 / 2017-01-22
47
- ==================
48
-
49
- * Use `os.tmpdir()` instead of `os.tmpDir()`
50
- * deps: fd-slicer@1.0.1
51
-
52
- 4.1.2 / 2015-05-09
53
- ==================
54
-
55
- * Do not emit error on part prior to emitting part
56
- * Fix filename with quotes truncating from certain clients
57
-
58
- 4.1.1 / 2015-01-18
59
- ==================
60
-
61
- * Do not clobber existing temporary files
62
-
63
- 4.1.0 / 2014-12-04
64
- ==================
65
-
66
- * Add `statusCode` field to HTTP-related errors
67
- * deps: fd-slicer@1.0.0
68
-
69
- 4.0.0 / 2014-10-14
70
- ==================
71
-
72
- * `part` events for fields no longer fire if `autoFields` is on
73
- * `part` events for files no longer fire if `autoFiles` is on
74
- * `field`, `file`, and `part` events are guaranteed to emit in the
75
- correct order - the order that the user places the parts in the
76
- request. Each `part` `end` event is guaranteed to emit before the
77
- next `part` event is emitted.
78
- * Drop Node.js 0.8.x support
79
- * Improve random temp file names
80
- - Now using 18 bytes of randomness instead of 8.
81
- * More robust `maxFilesSize` implementation
82
- - Before it was possible for race conditions to cause more than
83
- `maxFilesSize` bytes to get written to disk. That is now fixed.
84
- * Now `part` objects emit `error` events
85
- - This makes streaming work better since the part stream will emit
86
- an error when it is no longer streaming.
87
- * Remove support for generating the hash digest of a part
88
- - If you want this, do it in your own code.
89
- * Remove undocumented `ws` property from `file` objects
90
- * Require the close boundary
91
- - This makes multiparty more RFC-compliant and makes some invalid
92
- requests which used to work, now emit an error instead.
93
-
94
- 3.3.2 / 2014-08-07
95
- ==================
96
-
97
- * Do not invoke callback after close
98
- * Share callback ending logic between error and close
99
-
100
- 3.3.1 / 2014-07-22
101
- ==================
102
-
103
- * Remove problematic test fixtures
104
-
105
- 3.3.0 / 2014-07-03
106
- ==================
107
-
108
- * Always emit close after all parts ended
109
-
110
- 3.2.10 / 2014-07-03
111
- ===================
112
-
113
- * Fix callback hang in node.js 0.8 on errors
114
- * Remove execute bit from files
115
-
116
- 3.2.9 / 2014-06-16
117
- ==================
118
-
119
- * Fix attaching error listeners directly after form.parse
120
- * Fix to not synchronously invoke callback to form.parse on error
121
-
122
- 3.2.8 / 2014-06-01
123
- ==================
124
-
125
- * Fix developer accidentally corrupting data
126
- * Fix handling epilogue in a separate chunk
127
- * Fix initial check errors to use supplied callback
128
-
129
- 3.2.7 / 2014-05-26
130
- ==================
131
-
132
- * Fix errors hanging responses in callback-style
133
-
134
- 3.2.6 / 2014-05-13
135
- ==================
136
-
137
- * Fix `maxFields` to error on field after max
138
-
139
- 3.2.5 / 2014-05-11
140
- ==================
141
-
142
- * Support boundary containing equal sign
143
-
144
- 3.2.4 / 2014-03-26
145
- ==================
146
-
147
- * Keep `part.byteCount` undefined in chunked encoding
148
- * Fix temp files not always cleaned up
149
-
150
- 3.2.3 / 2014-02-20
151
- ==================
152
-
153
- * Improve parsing boundary attribute from `Content-Type`
154
-
155
- 3.2.2 / 2014-01-29
156
- ==================
157
-
158
- * Fix error on empty payloads
159
-
160
- 3.2.1 / 2014-01-27
161
- ==================
162
-
163
- * Fix `maxFilesSize` overcalculation bug
164
-
165
- 3.2.0 / 2014-01-17
166
- ==================
167
-
168
- * Add `maxFilesSize` for `autoFiles`
169
-
170
- 3.1.2 / 2014-01-13
171
- ==================
172
-
173
- * Fix incorrectly using `autoFields` value for `autoFiles`
174
-
175
- 3.1.1 / 2013-12-13
176
- ==================
177
-
178
- * Fix not emitting `close` after all part `end` events
179
-
180
- 3.1.0 / 2013-11-10
181
- ==================
182
-
183
- * Support UTF-8 filename in `Content-Disposition`
184
-
185
- 3.0.0 / 2013-10-25
186
- ==================
187
-
188
- * `form.parse` callback API changed in a compatibility-breaking manner
189
-
190
- 2.2.0 / 2013-10-15
191
- ==================
192
-
193
- * Add callback API to support multiple files with same field name
194
- * Fix assertion crash when max field count is exceeded
195
- * Fix assertion crash when client aborts an invalid request
196
- * Fix assertion crash when `EMFILE` occurrs
197
- * Switch from assertions to only `error` events
198
- * Unpipe the request when an error occurs to save resources
199
- * Update readable-stream to ~1.1.9
200
-
201
- 2.1.9 / 2013-10-06
202
- ==================
203
-
204
- * relax `Content-Type` detection regex
205
-
206
- 2.1.8 / 2013-08-26
207
- ==================
208
-
209
- * Replace deprecated `Buffer.write()`
210
-
211
- 2.1.7 / 2013-05-23
212
- ==================
213
-
214
- * Add repository field to package.json
215
-
216
- 2.1.6 / 2013-04-30
217
- ==================
218
-
219
- * Expose `hash` as an option to `Form`
220
-
221
- 2.1.5 / 2013-04-10
222
- ==================
223
-
224
- * Fix possible `close` event before all temp files are done
225
-
226
- 2.1.4 / 2013-04-09
227
- ==================
228
-
229
- * Fix crash for invalid requests
230
-
231
- 2.1.3 / 2013-04-09
232
- ==================
233
-
234
- * Add `file.size`
235
-
236
- 2.1.2 / 2013-04-08
237
- ==================
238
-
239
- * Add proper backpressure support
240
-
241
- 2.1.1 / 2013-04-05
242
- ==================
243
-
244
- * Add `part.byteCount` and `part.byteOffset`
245
- * Fix uploads larger than 2KB
246
-
247
- 2.1.0 / 2013-04-04
248
- ==================
249
-
250
- * Complete rewrite. See README for changes and new API.
251
-
252
- 2.0.0 / 2013-04-02
253
- ==================
254
-
255
- * Fork and rewrite from `formidable`