parse-nested-form-data 1.0.0 → 1.1.0
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.
- package/README.md +315 -174
- package/dist/index.d.ts +143 -30
- package/dist/parse-nested-form-data.cjs.js +578 -369
- package/dist/parse-nested-form-data.esm.js +572 -366
- package/dist/parse-nested-form-data.umd.js +607 -397
- package/dist/parse-nested-form-data.umd.js.map +1 -1
- package/dist/parse-nested-form-data.umd.min.js +7 -7
- package/dist/parse-nested-form-data.umd.min.js.map +1 -1
- package/package.json +108 -53
- package/CHANGELOG.md +0 -13
- package/dist/__tests__/index.d.ts +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @author Christian Schurr <chris@schurr.dev>
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* Thrown when a path is used multiple times or has
|
|
8
|
+
* Thrown when a path is used multiple times or has mismatching path parts.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```ts
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* formData.append('a', 'c')
|
|
24
24
|
* parseFormData(formData)
|
|
25
25
|
* // throws DuplicateKeyError('a')
|
|
26
|
+
* ```
|
|
26
27
|
*
|
|
27
28
|
* @example
|
|
28
29
|
* ```ts
|
|
@@ -35,8 +36,8 @@
|
|
|
35
36
|
*
|
|
36
37
|
*/
|
|
37
38
|
export declare class DuplicateKeyError extends Error {
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
key: string;
|
|
40
|
+
constructor(key: string);
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Thrown when an array is used at the same path with an order parameter and
|
|
@@ -61,12 +62,68 @@ export declare class DuplicateKeyError extends Error {
|
|
|
61
62
|
* ```
|
|
62
63
|
*/
|
|
63
64
|
export declare class MixedArrayError extends Error {
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
key: string;
|
|
66
|
+
constructor(key: string);
|
|
66
67
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Thrown when an object path part is `__proto__`, `constructor` or
|
|
70
|
+
* `prototype`, which could modify `Object.prototype`. The names are rejected
|
|
71
|
+
* at any position in the path. Entries with other names on `Object.prototype`
|
|
72
|
+
* (`toString`, ...) are removed instead (see `parseFormData`).
|
|
73
|
+
* `key` is the path up to the forbidden part, cut to 100 characters plus `...`
|
|
74
|
+
* when it's longer.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const formData = new FormData()
|
|
79
|
+
* formData.append('__proto__.polluted', 'yes')
|
|
80
|
+
* parseFormData(formData)
|
|
81
|
+
* // throws ForbiddenKeyError('__proto__')
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare class ForbiddenKeyError extends Error {
|
|
85
|
+
key: string;
|
|
86
|
+
constructor(key: string);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thrown when a path has more path parts than the `depth` option allows.
|
|
90
|
+
* `key` is the path up to the first path part over the limit, cut to
|
|
91
|
+
* 100 characters plus `...` when it's longer.
|
|
92
|
+
* Within one entry the depth is checked first, so a path that's too deep throws
|
|
93
|
+
* `DepthLimitError` even if it would also throw another error, like a
|
|
94
|
+
* `ForbiddenKeyError` for `__proto__`.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const formData = new FormData()
|
|
99
|
+
* formData.append('a.b[0].c', 'x')
|
|
100
|
+
* parseFormData(formData, {depth: 2})
|
|
101
|
+
* // throws DepthLimitError('a.b[0]')
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare class DepthLimitError extends Error {
|
|
105
|
+
key: string;
|
|
106
|
+
constructor(key: string);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Thrown when an array index in a path is larger than `4294967294`, the
|
|
110
|
+
* largest index a JavaScript array can hold.
|
|
111
|
+
* `key` is the path up to that array part, cut to 100 characters plus `...`
|
|
112
|
+
* when it's longer.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const formData = new FormData()
|
|
117
|
+
* formData.append('a[4294967295]', 'b')
|
|
118
|
+
* parseFormData(formData)
|
|
119
|
+
* // throws InvalidPathError('a[4294967295]')
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare class InvalidPathError extends Error {
|
|
123
|
+
key: string;
|
|
124
|
+
constructor(key: string);
|
|
125
|
+
}
|
|
126
|
+
type JsonObject = { [Key in string]?: JsonValue; };
|
|
70
127
|
type JsonArray = Array<JsonValue>;
|
|
71
128
|
type JsonValue = string | number | JsonObject | JsonArray | boolean | null | File;
|
|
72
129
|
type JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>;
|
|
@@ -119,8 +176,8 @@ type JsonLeafValue = Exclude<JsonValue, JsonArray | JsonObject>;
|
|
|
119
176
|
* @returns the path and the transformed value
|
|
120
177
|
*/
|
|
121
178
|
declare function defaultTransform(entry: [path: string, value: string | File]): {
|
|
122
|
-
|
|
123
|
-
|
|
179
|
+
path: string;
|
|
180
|
+
value: JsonLeafValue;
|
|
124
181
|
};
|
|
125
182
|
type DefaultTransform = typeof defaultTransform;
|
|
126
183
|
/**
|
|
@@ -128,6 +185,20 @@ type DefaultTransform = typeof defaultTransform;
|
|
|
128
185
|
* @param transformEntry - a function to transform the FormData entry into a path and a value
|
|
129
186
|
* (default: `defaultTransform`)
|
|
130
187
|
* @param removeEmptyString - skip empty values '' (default: `false`)
|
|
188
|
+
* @param depth - the maximum number of path parts in a path, counting every
|
|
189
|
+
* part (`a.b[0]` has 3); a positive integer or `Infinity` (default: `Infinity`).
|
|
190
|
+
* For untrusted input, `32` is recommended; 2.0 will lower the default.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const formData = new FormData()
|
|
195
|
+
* formData.append('a.b', 'x')
|
|
196
|
+
* parseFormData(formData, {depth: 2})
|
|
197
|
+
* // => {a: {b: 'x'}}
|
|
198
|
+
* formData.append('a.c.d', 'y')
|
|
199
|
+
* parseFormData(formData, {depth: 2})
|
|
200
|
+
* // throws DepthLimitError('a.c.d')
|
|
201
|
+
* ```
|
|
131
202
|
*
|
|
132
203
|
* @example
|
|
133
204
|
* ```ts
|
|
@@ -144,13 +215,11 @@ type DefaultTransform = typeof defaultTransform;
|
|
|
144
215
|
* formData.append('a', 'a')
|
|
145
216
|
* formData.append('b', 'b')
|
|
146
217
|
* parseFormData(formData, {
|
|
147
|
-
* transformEntry: (
|
|
218
|
+
* transformEntry: (entry, defaultTransform) => {
|
|
219
|
+
* const {path, value} = defaultTransform(entry)
|
|
148
220
|
* return {
|
|
149
221
|
* path,
|
|
150
|
-
* value:
|
|
151
|
-
* typeof value === 'string'
|
|
152
|
-
* ? value.toUpperCase()
|
|
153
|
-
* : defaultTransform(value),
|
|
222
|
+
* value: typeof value === 'string' ? value.toUpperCase() : value,
|
|
154
223
|
* }
|
|
155
224
|
* },
|
|
156
225
|
* })
|
|
@@ -158,11 +227,12 @@ type DefaultTransform = typeof defaultTransform;
|
|
|
158
227
|
* ```
|
|
159
228
|
*/
|
|
160
229
|
export type ParseFormDataOptions = {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
230
|
+
removeEmptyString?: boolean;
|
|
231
|
+
depth?: number;
|
|
232
|
+
transformEntry?: (entry: [path: string, value: string | File], defaultTransform: DefaultTransform) => {
|
|
233
|
+
path: string;
|
|
234
|
+
value: JsonLeafValue;
|
|
235
|
+
};
|
|
166
236
|
};
|
|
167
237
|
/**
|
|
168
238
|
*
|
|
@@ -177,9 +247,9 @@ export type ParseFormDataOptions = {
|
|
|
177
247
|
* - `&` => parse to `Boolean`
|
|
178
248
|
*
|
|
179
249
|
* - `.` between path parts => nest into `objects`
|
|
250
|
+
* - `[key]` after path part => nest into `objects`, same as `.key`; the text
|
|
251
|
+
* in brackets is the key as it is (`a[b.c]` => key `b.c`)
|
|
180
252
|
* - `[\d*]` after path part => push to array in order `\d` or push to end if `[]`
|
|
181
|
-
*
|
|
182
|
-
|
|
183
253
|
*
|
|
184
254
|
* @example
|
|
185
255
|
* ```ts
|
|
@@ -188,7 +258,7 @@ export type ParseFormDataOptions = {
|
|
|
188
258
|
* formData.append('&b', 'true')
|
|
189
259
|
* formData.append('-c', 'null')
|
|
190
260
|
* formData.append('d', 'foo')
|
|
191
|
-
* parseFormData(formData
|
|
261
|
+
* parseFormData(formData)
|
|
192
262
|
* // => {a: 1, b: true, c: null, d: 'foo'}
|
|
193
263
|
* ```
|
|
194
264
|
*
|
|
@@ -203,6 +273,15 @@ export type ParseFormDataOptions = {
|
|
|
203
273
|
* @example
|
|
204
274
|
* ```ts
|
|
205
275
|
* const formData = new FormData()
|
|
276
|
+
* formData.append('a[b]', 'foo')
|
|
277
|
+
* formData.append('a.c', 'bar')
|
|
278
|
+
* parseFormData(formData)
|
|
279
|
+
* // => {a: {b: 'foo', c: 'bar'}}
|
|
280
|
+
* ```
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* const formData = new FormData()
|
|
206
285
|
* formData.append('a[0]', 'foo')
|
|
207
286
|
* formData.append('a[1]', 'bar')
|
|
208
287
|
* parseFormData(formData)
|
|
@@ -222,7 +301,12 @@ export type ParseFormDataOptions = {
|
|
|
222
301
|
* ```ts
|
|
223
302
|
* const formData = new FormData()
|
|
224
303
|
* formData.append('a[0]', 'foo')
|
|
225
|
-
* parseFormData(formData, {
|
|
304
|
+
* parseFormData(formData, {
|
|
305
|
+
* transformEntry: ([path, value]) => ({
|
|
306
|
+
* path,
|
|
307
|
+
* value: typeof value === 'string' ? value + 'bar' : value,
|
|
308
|
+
* }),
|
|
309
|
+
* })
|
|
226
310
|
* // => {a: ['foobar']}
|
|
227
311
|
* ```
|
|
228
312
|
*
|
|
@@ -236,20 +320,49 @@ export type ParseFormDataOptions = {
|
|
|
236
320
|
* ```
|
|
237
321
|
*
|
|
238
322
|
* @param {Iterable<[string, string | File]>} formData - an iterator of an [`path`, `value`] tuple
|
|
239
|
-
* - `path` :=
|
|
240
|
-
*
|
|
323
|
+
* - `path` := an optional prefix (`+`, `-`, `&`), then path parts: keys after
|
|
324
|
+
* `.` or in brackets (`[key]`), and array parts `[]` or `[\d+]` (e.g.
|
|
325
|
+
* `+a[][1].b[c]`)
|
|
326
|
+
* - `value` := `string` or `File` (native or polyfilled `File` values are kept
|
|
327
|
+
* as they are)
|
|
241
328
|
* @param {ParseFormDataOptions} options - options for parsing the form data
|
|
242
329
|
* - `transformEntry` - a function to transform the path and the value before
|
|
243
330
|
* inserting the value at the path in the resulting object (default: `defaultTransform`)
|
|
244
331
|
* - `removeEmptyString` - if `true` removes all entries where the value is an empty string
|
|
332
|
+
* - `depth` - the maximum number of path parts in a path, counting every part
|
|
333
|
+
* (default: `Infinity`)
|
|
245
334
|
* @returns {JsonObject} the parsed JSON object
|
|
335
|
+
* @throws `TypeError` if `depth` isn't a positive integer or `Infinity`
|
|
336
|
+
* @throws `DepthLimitError` if a path has more path parts than `depth`
|
|
337
|
+
* - e.g. `a.b[0]` with `depth: 2` => `DepthLimitError('a.b[0]')`
|
|
246
338
|
* @throws `DuplicateKeyError` if
|
|
247
|
-
* -
|
|
248
|
-
*
|
|
339
|
+
* - two entries set a value at the same path
|
|
340
|
+
* - e.g. `a` and `a`, `a[0]` and `a[0]`, `a.b` and `a[b]`
|
|
341
|
+
* - an entry uses a path part as an object, array or value and another entry
|
|
342
|
+
* uses it differently
|
|
343
|
+
* - e.g. `a` and `a.b`, `a.b` and `a[]`, `a[]` and `a.b`
|
|
344
|
+
* - the path starts with an array part
|
|
345
|
+
* - e.g. `[0]` or `[]`
|
|
249
346
|
* @throws `MixedArrayError` if at a specific path part an unordered array is
|
|
250
347
|
* defined and at a later path part an ordered array is defined or vice versa
|
|
251
348
|
* - e.g. `a[0]` and `a[]`
|
|
252
349
|
* - e.g. `a[]` and `a[0]`
|
|
350
|
+
* @throws `ForbiddenKeyError` if an object path part is `__proto__`,
|
|
351
|
+
* `constructor` or `prototype`
|
|
352
|
+
* - e.g. `__proto__.polluted`
|
|
353
|
+
* @throws `InvalidPathError` if an array index is larger than `4294967294`
|
|
354
|
+
* - e.g. `a[4294967295]`
|
|
355
|
+
*
|
|
356
|
+
* Every error's `key` is the path after `transformEntry`, not the field name
|
|
357
|
+
* (e.g. `+a.b.c` with `depth: 2` => `DepthLimitError('a.b.c')`).
|
|
358
|
+
*
|
|
359
|
+
* An entry with any other object path part named like a property on
|
|
360
|
+
* `Object.prototype` (`toString`, `hasOwnProperty`, ...) is removed from the
|
|
361
|
+
* result. It's parsed like any other entry first, so it causes the same errors
|
|
362
|
+
* as an unused name: an error doesn't tell the client which properties exist
|
|
363
|
+
* on the server's `Object.prototype`.
|
|
364
|
+
* - e.g. `user.toString` => `{}`
|
|
365
|
+
* - e.g. `user[toString]` => `{}`
|
|
253
366
|
*/
|
|
254
|
-
export declare function parseFormData(formData: Iterable<[string, string | File]>, { removeEmptyString, transformEntry, }?: ParseFormDataOptions): JsonObject;
|
|
255
|
-
export {};
|
|
367
|
+
export declare function parseFormData(formData: Iterable<[string, string | File]>, { removeEmptyString, transformEntry, depth }?: ParseFormDataOptions): JsonObject;
|
|
368
|
+
export {};
|