@sap/cds-dk 10.2.0 → 10.2.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.
- package/CHANGELOG.md +13 -0
- package/bin/bind.js +5 -4
- package/bin/completion/scripts/cds.fish +4 -3
- package/bin/completion/scripts/cds.ps1 +4 -4
- package/bin/completion/scripts/cds.sh +4 -4
- package/bin/completion/scripts/cds.zsh +4 -4
- package/bin/debug.js +3 -3
- package/bin/deploy/index.js +2 -2
- package/bin/deploy/to-hana/hana.js +1 -1
- package/bin/deploy/to-hana/hdiDeployUtil.js +25 -14
- package/bin/up.js +3 -3
- package/bin/version.js +1 -1
- package/bin/watch/vite-watch.js +12 -6
- package/lib/bind/shared.js +1 -1
- package/lib/init/index.js +1 -0
- package/lib/init/projectReader.js +3 -2
- package/lib/init/template/approuter/files/values.yaml.hbs +0 -9
- package/lib/init/template/approuter/files/xs-app.json.hbs +0 -22
- package/lib/init/template/approuter/index.js +8 -9
- package/lib/init/template/html5-repo/index.js +12 -0
- package/lib/init/template/react/files/vite.config.{{extension}}.hbs +2 -3
- package/lib/init/template/react/index.js +1 -3
- package/lib/init/template/sample/files/app/index.html.hbs +18 -0
- package/lib/init/template/sample/index.js +1 -0
- package/lib/init/template/vue/files/vite.config.{{extension}}.hbs +2 -3
- package/lib/init/template/vue/index.js +1 -3
- package/lib/mtx/push.js +7 -1
- package/node_modules/@cap-js/asyncapi/README.md +4 -14
- package/node_modules/@cap-js/asyncapi/cds-plugin.js +12 -0
- package/node_modules/@cap-js/asyncapi/index.js +3 -2
- package/node_modules/@cap-js/asyncapi/lib/compile/components/messages/index.js +4 -2
- package/node_modules/@cap-js/asyncapi/lib/compile/components/schemas/definition.js +11 -1
- package/node_modules/@cap-js/asyncapi/lib/compile/components/schemas/utils.js +16 -0
- package/node_modules/@cap-js/asyncapi/lib/compile/utils.js +4 -3
- package/node_modules/@cap-js/asyncapi/lib/import/importAsyncAPI.js +34 -0
- package/node_modules/@cap-js/asyncapi/lib/import/index.js +18 -0
- package/node_modules/@cap-js/asyncapi/lib/import/message.js +9 -0
- package/node_modules/@cap-js/asyncapi/lib/import/parserContext.js +368 -0
- package/node_modules/@cap-js/asyncapi/lib/import/utils.js +85 -0
- package/node_modules/@cap-js/asyncapi/package.json +89 -1
- package/node_modules/@sap/cds/CHANGELOG.md +10 -0
- package/node_modules/@sap/cds/app/index.js +1 -1
- package/node_modules/@sap/cds/lib/env/schemas/cds-package.js +0 -2
- package/node_modules/@sap/cds/lib/env/schemas/cds-rc.js +3 -3
- package/node_modules/@sap/cds/lib/env/schemas/index.js +4 -4
- package/node_modules/@sap/cds/lib/utils/cds-utils.js +21 -8
- package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/connections.js +45 -39
- package/node_modules/@sap/cds/libx/odata/ODataAdapter.js +1 -1
- package/node_modules/@sap/cds/libx/queue/TaskRunner.js +22 -5
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +8 -0
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +57 -41
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +27 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +31 -25
- package/node_modules/body-parser/README.md +17 -13
- package/node_modules/body-parser/lib/read.js +74 -57
- package/node_modules/body-parser/lib/types/json.js +11 -11
- package/node_modules/body-parser/lib/types/urlencoded.js +5 -5
- package/node_modules/body-parser/lib/utils.js +0 -15
- package/node_modules/body-parser/package.json +7 -12
- package/node_modules/encodeurl/README.md +14 -0
- package/node_modules/encodeurl/index.js +5 -10
- package/node_modules/encodeurl/package.json +10 -6
- package/node_modules/on-finished/index.js +14 -2
- package/node_modules/on-finished/package.json +11 -7
- package/node_modules/qs/.editorconfig +1 -1
- package/node_modules/qs/CHANGELOG.md +87 -22
- package/node_modules/qs/README.md +63 -10
- package/node_modules/qs/dist/qs.js +17 -17
- package/node_modules/qs/eslint.config.mjs +61 -50
- package/node_modules/qs/lib/formats.js +7 -2
- package/node_modules/qs/lib/parse.js +29 -10
- package/node_modules/qs/lib/stringify.js +5 -3
- package/node_modules/qs/lib/utils.js +28 -9
- package/node_modules/qs/package.json +6 -6
- package/node_modules/qs/test/parse.js +335 -2
- package/node_modules/qs/test/stringify.js +222 -0
- package/node_modules/qs/test/utils.js +61 -3
- package/node_modules/semver/classes/range.js +2 -2
- package/node_modules/semver/internal/constants.js +30 -0
- package/node_modules/semver/internal/trim-comparator-whitespace.js +438 -0
- package/node_modules/semver/package.json +1 -1
- package/node_modules/ws/README.md +21 -29
- package/node_modules/ws/lib/buffer-util.js +13 -2
- package/node_modules/ws/lib/sender.js +4 -1
- package/node_modules/ws/lib/websocket.js +8 -3
- package/node_modules/ws/package.json +1 -1
- package/package.json +1 -1
- package/node_modules/on-finished/HISTORY.md +0 -98
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "qs",
|
|
3
3
|
"description": "A querystring parser that supports nesting and arrays, with a depth limit",
|
|
4
4
|
"homepage": "https://github.com/ljharb/qs",
|
|
5
|
-
"version": "6.
|
|
5
|
+
"version": "6.17.1",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/ljharb/qs.git"
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"common-shakeify": "~1.0.0",
|
|
44
44
|
"eclint": "^2.8.1",
|
|
45
45
|
"es-value-fixtures": "^1.7.1",
|
|
46
|
-
"eslint": "^10.
|
|
47
|
-
"evalmd": "^0.0
|
|
46
|
+
"eslint": "^10.13.0",
|
|
47
|
+
"evalmd": "^0.1.0",
|
|
48
48
|
"for-each": "^0.3.5",
|
|
49
49
|
"glob": "=10.3.7",
|
|
50
50
|
"has-bigints": "^1.1.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"mkdirp": "^0.5.5",
|
|
60
60
|
"mock-property": "^1.1.2",
|
|
61
61
|
"module-deps": "^6.2.3",
|
|
62
|
-
"npmignore": "^0.3.
|
|
62
|
+
"npmignore": "^0.3.6",
|
|
63
63
|
"nyc": "^10.3.2",
|
|
64
64
|
"object-inspect": "^1.13.4",
|
|
65
65
|
"qs-iconv": "^1.0.4",
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"prepack": "npmignore --auto --commentLines=autogenerated && npm run dist",
|
|
73
73
|
"prepublishOnly": "safe-publish-latest",
|
|
74
74
|
"prepublish": "not-in-publish || npm run prepublishOnly",
|
|
75
|
-
"pretest": "npm run
|
|
75
|
+
"pretest": "npm run lint",
|
|
76
76
|
"test": "npm run tests-only",
|
|
77
77
|
"tests-only": "nyc tape 'test/**/*.js'",
|
|
78
78
|
"posttest": "npx npm@'>=10.2' audit --production",
|
|
79
|
-
"readme": "evalmd README.md",
|
|
80
79
|
"postlint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git' | grep -v dist/)",
|
|
80
|
+
"prelint": "evalmd --eslint README.md",
|
|
81
81
|
"lint": "eslint .",
|
|
82
82
|
"dist": "mkdirp dist && browserify --standalone Qs -g unassertify -g @browserify/envify -g [@browserify/uglifyify --mangle.keep_fnames --compress.keep_fnames --format.indent_level=1 --compress.arrows=false --compress.passes=4 --compress.typeofs=false] -p common-shakeify -p bundle-collapser/plugin lib/index.js > dist/qs.js"
|
|
83
83
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var test = require('tape');
|
|
4
|
+
var forEach = require('for-each');
|
|
4
5
|
var hasPropertyDescriptors = require('has-property-descriptors')();
|
|
5
6
|
var iconv = require('iconv-lite');
|
|
6
7
|
var mockProperty = require('mock-property');
|
|
@@ -98,6 +99,21 @@ test('parse()', function (t) {
|
|
|
98
99
|
st.end();
|
|
99
100
|
});
|
|
100
101
|
|
|
102
|
+
t.test('decode dot keys with case-insensitive percent escapes', function (st) {
|
|
103
|
+
var input = 'name%252eobj.first%252ename=John&name%252Eobj.last%252Ename=Doe';
|
|
104
|
+
st.deepEqual(
|
|
105
|
+
qs.parse(input, { decodeDotInKeys: true }),
|
|
106
|
+
{ 'name.obj': { 'first.name': 'John', 'last.name': 'Doe' } },
|
|
107
|
+
'lowercase and uppercase escapes decode in both parent and child keys'
|
|
108
|
+
);
|
|
109
|
+
st.deepEqual(
|
|
110
|
+
qs.parse(input, { allowDots: true, decodeDotInKeys: false }),
|
|
111
|
+
{ 'name%2eobj': { 'first%2ename': 'John' }, 'name%2Eobj': { 'last%2Ename': 'Doe' } },
|
|
112
|
+
'escaped dots remain unchanged when decoding is disabled'
|
|
113
|
+
);
|
|
114
|
+
st.end();
|
|
115
|
+
});
|
|
116
|
+
|
|
101
117
|
t.test('decode dot keys correctly', function (st) {
|
|
102
118
|
st.deepEqual(
|
|
103
119
|
qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { allowDots: false, decodeDotInKeys: false }),
|
|
@@ -258,8 +274,34 @@ test('parse()', function (t) {
|
|
|
258
274
|
st.end();
|
|
259
275
|
});
|
|
260
276
|
|
|
277
|
+
t.test('ignores bracket-wrapped prototype keys when depth = 0 and allowPrototypes is false', function (st) {
|
|
278
|
+
st.deepEqual(qs.parse('[toString]=foo', { depth: 0 }), {}, 'the stored key is the bracket content');
|
|
279
|
+
st.deepEqual(qs.parse('[hasOwnProperty]=bar', { depth: 0 }), {});
|
|
280
|
+
st.deepEqual(qs.parse('%5BhasOwnProperty%5D=bar', { depth: 0 }), {}, 'percent-encoded brackets');
|
|
281
|
+
st.deepEqual(qs.parse('[toString]=foo&a=b', { depth: 0 }), { a: 'b' });
|
|
282
|
+
st.deepEqual(qs.parse('[toString]=foo', { depth: false }), {}, 'depth: false');
|
|
283
|
+
st.deepEqual(qs.parse('.toString=foo', { depth: 0, allowDots: true }), {}, 'a leading dot becomes a bracket group');
|
|
284
|
+
st.deepEqual(qs.parse('[a]=b&[toString]=foo', { depth: 0 }), { a: 'b' }, 'other bracket-wrapped keys still strip their brackets');
|
|
285
|
+
st.end();
|
|
286
|
+
});
|
|
287
|
+
|
|
261
288
|
t.test('allows prototype keys when depth = 0 and allowPrototypes is true', function (st) {
|
|
262
289
|
st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' });
|
|
290
|
+
st.deepEqual(qs.parse('[toString]=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' });
|
|
291
|
+
st.end();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
t.test('keeps bracket-wrapped prototype keys when depth = 0 and plainObjects is true', function (st) {
|
|
295
|
+
var result = qs.parse('[toString]=foo', { depth: 0, plainObjects: true });
|
|
296
|
+
st.deepEqual(result, { __proto__: null, toString: 'foo' });
|
|
297
|
+
st.equal(Object.getPrototypeOf(result), null);
|
|
298
|
+
st.end();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
t.test('strips one wrapping bracket pair from the key when depth = 0', function (st) {
|
|
302
|
+
st.deepEqual(qs.parse('[a]=b', { depth: 0 }), { a: 'b' });
|
|
303
|
+
st.deepEqual(qs.parse('[[toString]]=foo', { depth: 0 }), { '[toString]': 'foo' }, 'only the outer pair is stripped');
|
|
304
|
+
st.deepEqual(qs.parse('x[toString]=foo', { depth: 0 }), { 'x[toString]': 'foo' }, 'a key with a parent stays literal');
|
|
263
305
|
st.end();
|
|
264
306
|
});
|
|
265
307
|
|
|
@@ -1139,6 +1181,67 @@ test('parse()', function (t) {
|
|
|
1139
1181
|
st.end();
|
|
1140
1182
|
});
|
|
1141
1183
|
|
|
1184
|
+
var withOwnProto = function (value) {
|
|
1185
|
+
var obj = {};
|
|
1186
|
+
Object.defineProperty(obj, '__proto__', {
|
|
1187
|
+
configurable: true,
|
|
1188
|
+
enumerable: true,
|
|
1189
|
+
value: value,
|
|
1190
|
+
writable: true
|
|
1191
|
+
});
|
|
1192
|
+
return obj;
|
|
1193
|
+
};
|
|
1194
|
+
// node < 0.12 reads an own `__proto__` data property as `Object.prototype`, so it can not be copied by value
|
|
1195
|
+
var ownProtoIsReadable = withOwnProto(1).__proto__ === 1; // eslint-disable-line no-proto
|
|
1196
|
+
|
|
1197
|
+
t.test('object-valued input with only an own `__proto__` does not mutate sub-object [[Prototype]]', function (st) {
|
|
1198
|
+
// `JSON.parse` in node < 0.12 sets the [[Prototype]] instead of creating an own `__proto__`
|
|
1199
|
+
var out = qs.parse({ user: withOwnProto({ isAdmin: true }) });
|
|
1200
|
+
|
|
1201
|
+
st.equal(out.user.isAdmin, undefined, 'attacker-controlled inherited property is not exposed');
|
|
1202
|
+
st.equal(Object.getPrototypeOf(out.user), Object.prototype, 'sub-object [[Prototype]] is unchanged');
|
|
1203
|
+
st.equal(Object.prototype.isAdmin, undefined, 'Object.prototype is not polluted');
|
|
1204
|
+
|
|
1205
|
+
var sibling = withOwnProto({ isAdmin: true });
|
|
1206
|
+
sibling.name = 'alice';
|
|
1207
|
+
var withSibling = qs.parse({ user: sibling });
|
|
1208
|
+
|
|
1209
|
+
st.equal(withSibling.user.name, 'alice', 'sibling key is preserved');
|
|
1210
|
+
st.equal(withSibling.user.isAdmin, undefined, 'a sibling key does not hide an inherited property');
|
|
1211
|
+
st.equal(Object.getPrototypeOf(withSibling.user), Object.prototype, 'a sibling key does not change [[Prototype]]');
|
|
1212
|
+
|
|
1213
|
+
st.end();
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
t.test('object-valued input keeps an own `__proto__` as a data property', { skip: !ownProtoIsReadable }, function (st) {
|
|
1217
|
+
var descriptor = function (value) {
|
|
1218
|
+
return {
|
|
1219
|
+
configurable: true,
|
|
1220
|
+
enumerable: true,
|
|
1221
|
+
value: value,
|
|
1222
|
+
writable: true
|
|
1223
|
+
};
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
st.deepEqual(
|
|
1227
|
+
Object.getOwnPropertyDescriptor(qs.parse({ user: withOwnProto({ isAdmin: true }) }).user, '__proto__'),
|
|
1228
|
+
descriptor({ isAdmin: true }),
|
|
1229
|
+
'an object value is stored as a data property'
|
|
1230
|
+
);
|
|
1231
|
+
st.deepEqual(
|
|
1232
|
+
Object.getOwnPropertyDescriptor(qs.parse({ user: withOwnProto('admin') }).user, '__proto__'),
|
|
1233
|
+
descriptor('admin'),
|
|
1234
|
+
'a primitive value is stored as a data property'
|
|
1235
|
+
);
|
|
1236
|
+
st.deepEqual(
|
|
1237
|
+
Object.getOwnPropertyDescriptor(qs.parse({ 'user[name]': 'alice', user: withOwnProto({ isAdmin: true }) }).user, '__proto__'),
|
|
1238
|
+
descriptor({ isAdmin: true }),
|
|
1239
|
+
'it survives a merge with a bracket key'
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
st.end();
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1142
1245
|
t.test('can return null objects', { skip: !hasProto }, function (st) {
|
|
1143
1246
|
var expected = {
|
|
1144
1247
|
__proto__: null,
|
|
@@ -1162,6 +1265,19 @@ test('parse()', function (t) {
|
|
|
1162
1265
|
st.end();
|
|
1163
1266
|
});
|
|
1164
1267
|
|
|
1268
|
+
t.test('returns null objects for empty brackets when parseArrays is false', { skip: !hasProto }, function (st) {
|
|
1269
|
+
st.deepEqual(
|
|
1270
|
+
qs.parse('a[]=b', { parseArrays: false, plainObjects: true }),
|
|
1271
|
+
{ __proto__: null, a: { __proto__: null, 0: 'b' } }
|
|
1272
|
+
);
|
|
1273
|
+
st.deepEqual(
|
|
1274
|
+
qs.parse('a[]=b&a[hasOwnProperty]=c', { parseArrays: false, plainObjects: true }),
|
|
1275
|
+
{ __proto__: null, a: { __proto__: null, 0: 'b', hasOwnProperty: 'c' } },
|
|
1276
|
+
'prototype keys land on a null object'
|
|
1277
|
+
);
|
|
1278
|
+
st.end();
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1165
1281
|
t.test('can parse with custom encoding', function (st) {
|
|
1166
1282
|
st.deepEqual(qs.parse('%8c%a7=%91%e5%8d%e3%95%7b', {
|
|
1167
1283
|
decoder: function (str) {
|
|
@@ -1254,6 +1370,31 @@ test('parse()', function (t) {
|
|
|
1254
1370
|
st.end();
|
|
1255
1371
|
});
|
|
1256
1372
|
|
|
1373
|
+
t.test('interprets numeric entities above U+FFFF as surrogate pairs', function (st) {
|
|
1374
|
+
var options = { charset: 'iso-8859-1', interpretNumericEntities: true };
|
|
1375
|
+
st.deepEqual(qs.parse('foo=%26%23128512%3B', options), { foo: '\uD83D\uDE00' }, 'U+1F600, which a browser submits as `😀` from an iso-8859-1 form');
|
|
1376
|
+
st.deepEqual(qs.parse('foo=%26%2365536%3B', options), { foo: '\uD800\uDC00' }, 'U+10000');
|
|
1377
|
+
st.deepEqual(qs.parse('foo=%26%231114111%3B', options), { foo: '\uDBFF\uDFFF' }, 'U+10FFFF');
|
|
1378
|
+
st.deepEqual(qs.parse('foo=%26%2365535%3B', options), { foo: '\uFFFF' }, 'U+FFFF is still one code unit');
|
|
1379
|
+
st.end();
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
t.test('interprets numeric entities in each value, but not in keys', function (st) {
|
|
1383
|
+
var options = { charset: 'iso-8859-1', interpretNumericEntities: true };
|
|
1384
|
+
st.deepEqual(qs.parse('a=' + urlEncodedNumSmiley + ',x', options), { a: '☺,x' }, 'a value with commas stays one string without `comma`');
|
|
1385
|
+
st.deepEqual(qs.parse('a' + urlEncodedNumSmiley + '=x', options), { 'a☺': 'x' }, 'keys are not interpreted');
|
|
1386
|
+
st.deepEqual(qs.parse('a[]=' + urlEncodedNumSmiley + '&a[]=x', options), { a: ['☺', 'x'] }, 'bracket array values');
|
|
1387
|
+
st.deepEqual(qs.parse('a=' + urlEncodedNumSmiley + '&a=x', options), { a: ['☺', 'x'] }, 'repeated key values');
|
|
1388
|
+
st.deepEqual(qs.parse('a[b]=' + urlEncodedNumSmiley, options), { a: { b: '☺' } }, 'nested key values');
|
|
1389
|
+
st.deepEqual(
|
|
1390
|
+
qs.parse('utf8=%26%2310003%3B&a=' + urlEncodedNumSmiley, { charsetSentinel: true, interpretNumericEntities: true }),
|
|
1391
|
+
{ a: '☺' },
|
|
1392
|
+
'an iso-8859-1 charset sentinel enables interpretation'
|
|
1393
|
+
);
|
|
1394
|
+
st.deepEqual(qs.parse('a=' + urlEncodedNumSmiley + ',x', { charset: 'iso-8859-1', comma: true }), { a: ['☺', 'x'] }, '`comma` splits without `interpretNumericEntities`');
|
|
1395
|
+
st.end();
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1257
1398
|
t.test('handles a custom decoder returning `null`, in the `iso-8859-1` charset, when `interpretNumericEntities`', function (st) {
|
|
1258
1399
|
st.deepEqual(qs.parse('foo=&bar=' + urlEncodedNumSmiley, {
|
|
1259
1400
|
charset: 'iso-8859-1',
|
|
@@ -1289,15 +1430,70 @@ test('parse()', function (t) {
|
|
|
1289
1430
|
st.end();
|
|
1290
1431
|
});
|
|
1291
1432
|
|
|
1292
|
-
t.test('interpretNumericEntities
|
|
1433
|
+
t.test('interpretNumericEntities preserves comma arrays in iso charset', function (st) {
|
|
1293
1434
|
st.deepEqual(
|
|
1294
1435
|
qs.parse('b&a[]=1,' + urlEncodedNumSmiley, { comma: true, charset: 'iso-8859-1', interpretNumericEntities: true }),
|
|
1295
|
-
{ b: '', a: ['1
|
|
1436
|
+
{ b: '', a: [['1', '☺']] }
|
|
1296
1437
|
);
|
|
1297
1438
|
|
|
1298
1439
|
st.end();
|
|
1299
1440
|
});
|
|
1300
1441
|
|
|
1442
|
+
t.test('interprets numeric entities per comma-separated value', function (st) {
|
|
1443
|
+
var options = { comma: true, charset: 'iso-8859-1', interpretNumericEntities: true };
|
|
1444
|
+
st.deepEqual(qs.parse('a=b,c', options), { a: ['b', 'c'] });
|
|
1445
|
+
st.deepEqual(qs.parse('a=' + urlEncodedNumSmiley + ',%26%239786%3B', options), { a: ['☺', '☺'] });
|
|
1446
|
+
st.deepEqual(qs.parse('a=%26%2344%3B,b%2Cc', options), { a: [',', 'b,c'] });
|
|
1447
|
+
st.deepEqual(qs.parse('a=,b,', options), { a: ['', 'b', ''] });
|
|
1448
|
+
st.deepEqual(qs.parse('a=b,c&a=d,e', options), { a: ['b', 'c', 'd', 'e'] });
|
|
1449
|
+
st.deepEqual(qs.parse('a[]=b,c&a[]=d,e', options), { a: [['b', 'c'], ['d', 'e']] });
|
|
1450
|
+
st.deepEqual(qs.parse('a[0]=b,c', options), { a: [['b', 'c']] });
|
|
1451
|
+
st.deepEqual(qs.parse('utf8=%26%2310003%3B&a=b,' + urlEncodedNumSmiley, {
|
|
1452
|
+
comma: true,
|
|
1453
|
+
charsetSentinel: true,
|
|
1454
|
+
interpretNumericEntities: true
|
|
1455
|
+
}), { a: ['b', '☺'] });
|
|
1456
|
+
st.deepEqual(qs.parse('a=b,c,d', {
|
|
1457
|
+
comma: true,
|
|
1458
|
+
charset: 'iso-8859-1',
|
|
1459
|
+
interpretNumericEntities: true,
|
|
1460
|
+
arrayLimit: 2
|
|
1461
|
+
}), { a: { 0: 'b', 1: 'c', 2: 'd' } });
|
|
1462
|
+
st['throws'](function () {
|
|
1463
|
+
qs.parse('a=b,c,d', {
|
|
1464
|
+
comma: true,
|
|
1465
|
+
charset: 'iso-8859-1',
|
|
1466
|
+
interpretNumericEntities: true,
|
|
1467
|
+
arrayLimit: 2,
|
|
1468
|
+
throwOnLimitExceeded: true
|
|
1469
|
+
});
|
|
1470
|
+
}, RangeError);
|
|
1471
|
+
st.end();
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
t.test('preserves falsy decoder results inside comma arrays', function (st) {
|
|
1475
|
+
st.deepEqual(qs.parse('a=null,0,false,' + urlEncodedNumSmiley, {
|
|
1476
|
+
comma: true,
|
|
1477
|
+
charset: 'iso-8859-1',
|
|
1478
|
+
interpretNumericEntities: true,
|
|
1479
|
+
decoder: function (str, defaultDecoder, charset, type) {
|
|
1480
|
+
if (type === 'value') {
|
|
1481
|
+
if (str === 'null') {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
if (str === '0') {
|
|
1485
|
+
return 0;
|
|
1486
|
+
}
|
|
1487
|
+
if (str === 'false') {
|
|
1488
|
+
return false;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return defaultDecoder(str, defaultDecoder, charset, type);
|
|
1492
|
+
}
|
|
1493
|
+
}), { a: [null, 0, false, '☺'] });
|
|
1494
|
+
st.end();
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1301
1497
|
t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) {
|
|
1302
1498
|
st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' });
|
|
1303
1499
|
st.end();
|
|
@@ -1421,6 +1617,41 @@ test('parse()', function (t) {
|
|
|
1421
1617
|
sst.end();
|
|
1422
1618
|
});
|
|
1423
1619
|
|
|
1620
|
+
st.test('zero and negative arrayLimit allow non-array values', function (sst) {
|
|
1621
|
+
forEach([0, -1], function (arrayLimit) {
|
|
1622
|
+
var options = { arrayLimit: arrayLimit, throwOnLimitExceeded: true };
|
|
1623
|
+
var label = 'arrayLimit ' + arrayLimit;
|
|
1624
|
+
sst.deepEqual(qs.parse('a=b&c[d]=e', options), { a: 'b', c: { d: 'e' } }, label + ': query string fields');
|
|
1625
|
+
sst.deepEqual(qs.parse('a=b', { arrayLimit: arrayLimit, throwOnLimitExceeded: true, comma: true }), { a: 'b' }, label + ': a value without commas when comma is enabled');
|
|
1626
|
+
sst.deepEqual(qs.parse({ a: 'b', 'c[d]': 'e' }, options), { a: 'b', c: { d: 'e' } }, label + ': object input fields');
|
|
1627
|
+
sst.deepEqual(qs.parse({ a: [] }, options), { a: [] }, label + ': an empty input array has no elements');
|
|
1628
|
+
sst.deepEqual(
|
|
1629
|
+
qs.parse('a=b&a=c', { arrayLimit: arrayLimit, throwOnLimitExceeded: true, duplicates: 'last' }),
|
|
1630
|
+
{ a: 'c' },
|
|
1631
|
+
label + ': replacing a duplicate does not create an array'
|
|
1632
|
+
);
|
|
1633
|
+
sst.deepEqual(
|
|
1634
|
+
qs.parse('a[]=', { arrayLimit: arrayLimit, throwOnLimitExceeded: true, allowEmptyArrays: true }),
|
|
1635
|
+
{ a: [] },
|
|
1636
|
+
label + ': an empty array has no elements to exceed the limit'
|
|
1637
|
+
);
|
|
1638
|
+
sst.deepEqual(qs.parse({ a: ['b'] }, options), { a: ['b'] }, label + ': an array value in object input is left as-is');
|
|
1639
|
+
sst.deepEqual(qs.parse({ 'a[b]': ['x'] }, options), { a: { b: ['x'] } }, label + ': an array value under a nested key in object input is left as-is');
|
|
1640
|
+
sst.deepEqual(qs.parse('a.b=c', { arrayLimit: arrayLimit, throwOnLimitExceeded: true, allowDots: true }), { a: { b: 'c' } }, label + ': dotted keys');
|
|
1641
|
+
sst.deepEqual(
|
|
1642
|
+
qs.parse('a=b&a=c', { arrayLimit: arrayLimit, throwOnLimitExceeded: true, duplicates: 'first' }),
|
|
1643
|
+
{ a: 'b' },
|
|
1644
|
+
label + ': keeping the first duplicate does not create an array'
|
|
1645
|
+
);
|
|
1646
|
+
sst.deepEqual(
|
|
1647
|
+
qs.parse('a[]=b', { arrayLimit: arrayLimit, throwOnLimitExceeded: true, parseArrays: false }),
|
|
1648
|
+
{ a: { 0: 'b' } },
|
|
1649
|
+
label + ': array syntax with parseArrays false does not create an array'
|
|
1650
|
+
);
|
|
1651
|
+
});
|
|
1652
|
+
sst.end();
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1424
1655
|
st.test('does not throw when at limit', function (sst) {
|
|
1425
1656
|
// 3 elements = limit of 3, should not throw
|
|
1426
1657
|
var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3, throwOnLimitExceeded: true });
|
|
@@ -1429,6 +1660,58 @@ test('parse()', function (t) {
|
|
|
1429
1660
|
sst.end();
|
|
1430
1661
|
});
|
|
1431
1662
|
|
|
1663
|
+
st.test('zero and negative arrayLimit', function (sst) {
|
|
1664
|
+
forEach([0, -1], function (arrayLimit) {
|
|
1665
|
+
var options = { arrayLimit: arrayLimit };
|
|
1666
|
+
var throwOptions = { arrayLimit: arrayLimit, throwOnLimitExceeded: true };
|
|
1667
|
+
var label = 'arrayLimit ' + arrayLimit;
|
|
1668
|
+
|
|
1669
|
+
sst.deepEqual(qs.parse('a=b&c[d]=e', options), { a: 'b', c: { d: 'e' } }, label + ': scalar and object fields are unaffected');
|
|
1670
|
+
sst.deepEqual(qs.parse('a[]=b', options), { a: { 0: 'b' } }, label + ': a bracket array becomes an object');
|
|
1671
|
+
sst.deepEqual(qs.parse('a[]=b&a[]=c', options), { a: { 0: 'b', 1: 'c' } }, label + ': a multi-element bracket array becomes an object');
|
|
1672
|
+
sst.deepEqual(qs.parse('a[0]=b', options), { a: { 0: 'b' } }, label + ': an indexed array becomes an object');
|
|
1673
|
+
sst.deepEqual(qs.parse('a=b&a=c', options), { a: { 0: 'b', 1: 'c' } }, label + ': combined duplicates become an object');
|
|
1674
|
+
sst.deepEqual(qs.parse('a[]=', options), { a: { 0: '' } }, label + ': an empty-string element still counts as an element');
|
|
1675
|
+
sst.deepEqual(qs.parse('a[]=', { arrayLimit: arrayLimit, allowEmptyArrays: true }), { a: [] }, label + ': an empty array has no elements');
|
|
1676
|
+
sst.deepEqual(qs.parse({ 'a[]': 'b' }, options), { a: { 0: 'b' } }, label + ': a bracket key in object input becomes an object');
|
|
1677
|
+
sst.deepEqual(qs.parse({ a: ['b'] }, options), { a: ['b'] }, label + ': an array value in object input is left as-is');
|
|
1678
|
+
sst.deepEqual(qs.parse({ a: [] }, options), { a: [] }, label + ': an empty array value in object input is left as-is');
|
|
1679
|
+
|
|
1680
|
+
var error = arrayLimit === 0
|
|
1681
|
+
? new RangeError('Array limit exceeded. Only 0 elements allowed in an array.')
|
|
1682
|
+
: RangeError;
|
|
1683
|
+
sst['throws'](function () { qs.parse('a[]=b', throwOptions); }, error, label + ': throws on a bracket array');
|
|
1684
|
+
sst['throws'](function () { qs.parse('a[0]=b', throwOptions); }, error, label + ': throws on an indexed array');
|
|
1685
|
+
sst['throws'](function () { qs.parse('a=b&a=c', throwOptions); }, error, label + ': throws on combined duplicates');
|
|
1686
|
+
sst['throws'](function () { qs.parse('a[]=', throwOptions); }, error, label + ': throws on an empty-string element');
|
|
1687
|
+
sst['throws'](function () { qs.parse({ 'a[]': 'b' }, throwOptions); }, error, label + ': throws on a bracket key in object input');
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
sst.end();
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
st.test('zero and negative arrayLimit with comma', function (sst) {
|
|
1694
|
+
forEach([0, -1], function (arrayLimit) {
|
|
1695
|
+
var options = { arrayLimit: arrayLimit, comma: true };
|
|
1696
|
+
var throwOptions = { arrayLimit: arrayLimit, comma: true, throwOnLimitExceeded: true };
|
|
1697
|
+
var label = 'arrayLimit ' + arrayLimit;
|
|
1698
|
+
|
|
1699
|
+
sst.deepEqual(qs.parse('a=b', options), { a: 'b' }, label + ': a value without commas is unaffected');
|
|
1700
|
+
sst.deepEqual(qs.parse('a=b,c', options), { a: { 0: 'b', 1: 'c' } }, label + ': a comma value becomes an object');
|
|
1701
|
+
sst.deepEqual(qs.parse('a[]=b,c', options), { a: { 0: ['b', 'c'] } }, label + ': a bracket array holding a comma group becomes an object');
|
|
1702
|
+
sst.deepEqual(qs.parse('a[0]=b,c', options), { a: { 0: { 0: 'b', 1: 'c' } } }, label + ': an indexed comma group becomes an object');
|
|
1703
|
+
|
|
1704
|
+
var error = arrayLimit === 0
|
|
1705
|
+
? new RangeError('Array limit exceeded. Only 0 elements allowed in an array.')
|
|
1706
|
+
: RangeError;
|
|
1707
|
+
sst['throws'](function () { qs.parse('a=b,c', throwOptions); }, error, label + ': throws on a comma value');
|
|
1708
|
+
sst['throws'](function () { qs.parse('a[]=b,c', throwOptions); }, error, label + ': throws on a comma group under `[]=`');
|
|
1709
|
+
sst['throws'](function () { qs.parse('a[0]=b,c', throwOptions); }, error, label + ': throws on an indexed comma group');
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
sst.end();
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1432
1715
|
st.test('converts array to object if length is greater than limit', function (sst) {
|
|
1433
1716
|
var result = qs.parse('a[1]=1&a[2]=2&a[3]=3&a[4]=4&a[5]=5&a[6]=6', { arrayLimit: 5 });
|
|
1434
1717
|
|
|
@@ -1564,6 +1847,27 @@ test('parse()', function (t) {
|
|
|
1564
1847
|
sst.end();
|
|
1565
1848
|
});
|
|
1566
1849
|
|
|
1850
|
+
st.test('counts the elements of a sparse indexed array on merge, not its holes', function (sst) {
|
|
1851
|
+
sst.deepEqual(qs.parse('a[]=new&a[19]=19&a[0]=0'), { a: ['new', '19', '0'] }, 'an array merged into a sparse array');
|
|
1852
|
+
sst.deepEqual(qs.parse('a[]=new&a[19]=19&a[0]=0', { throwOnLimitExceeded: true }), { a: ['new', '19', '0'] }, 'and it does not throw');
|
|
1853
|
+
sst.deepEqual(qs.parse('a[0]=w&a[19]=x&a[]=y'), { a: ['w', 'x', 'y'] }, 'a bracket value appended after a high index');
|
|
1854
|
+
sst.deepEqual(qs.parse('a[19]=x&a=y'), { a: ['x', 'y'] }, 'a scalar merged into a sparse array');
|
|
1855
|
+
sst.deepEqual(qs.parse('a[19]=x&a=y', { throwOnLimitExceeded: true }), { a: ['x', 'y'] }, 'and it does not throw');
|
|
1856
|
+
|
|
1857
|
+
sst.deepEqual(
|
|
1858
|
+
qs.parse('a[1]=x&a[]=y&a[]=z', { arrayLimit: 2 }),
|
|
1859
|
+
{ a: { 0: 'y', 1: 'x', 2: 'z' } },
|
|
1860
|
+
'more elements than arrayLimit still converts when the array has holes'
|
|
1861
|
+
);
|
|
1862
|
+
sst['throws'](
|
|
1863
|
+
function () { qs.parse('a[1]=x&a[]=y&a[]=z', { arrayLimit: 2, throwOnLimitExceeded: true }); },
|
|
1864
|
+
new RangeError('Array limit exceeded. Only 2 elements allowed in an array.'),
|
|
1865
|
+
'and throws with throwOnLimitExceeded'
|
|
1866
|
+
);
|
|
1867
|
+
|
|
1868
|
+
sst.end();
|
|
1869
|
+
});
|
|
1870
|
+
|
|
1567
1871
|
st.test('enforces arrayLimit when a strictMerge conflict appends to its container', function (sst) {
|
|
1568
1872
|
var aliased = 'a[z]=o&a=p&a[y]=q&[a]=r';
|
|
1569
1873
|
sst['throws'](
|
|
@@ -1621,6 +1925,35 @@ test('parse()', function (t) {
|
|
|
1621
1925
|
sst.end();
|
|
1622
1926
|
});
|
|
1623
1927
|
|
|
1928
|
+
st.test('throws when a comma array at arrayLimit is combined with one more value', function (sst) {
|
|
1929
|
+
var error = new RangeError('Array limit exceeded. Only 2 elements allowed in an array.');
|
|
1930
|
+
sst['throws'](
|
|
1931
|
+
function () { qs.parse('a=x,y&a=z', { comma: true, arrayLimit: 2, throwOnLimitExceeded: true }); },
|
|
1932
|
+
error,
|
|
1933
|
+
'a plain value appended to a full comma array'
|
|
1934
|
+
);
|
|
1935
|
+
sst['throws'](
|
|
1936
|
+
function () { qs.parse('a=x&a=y,z', { comma: true, arrayLimit: 2, throwOnLimitExceeded: true }); },
|
|
1937
|
+
error,
|
|
1938
|
+
'a comma value appended to a plain value'
|
|
1939
|
+
);
|
|
1940
|
+
sst.end();
|
|
1941
|
+
});
|
|
1942
|
+
|
|
1943
|
+
st.test('does not throw when a duplicate of a full comma array is discarded or replaces it', function (sst) {
|
|
1944
|
+
sst.deepEqual(
|
|
1945
|
+
qs.parse('a=x,y&a=z', { comma: true, arrayLimit: 2, throwOnLimitExceeded: true, duplicates: 'first' }),
|
|
1946
|
+
{ a: ['x', 'y'] },
|
|
1947
|
+
'duplicates: first keeps the full array'
|
|
1948
|
+
);
|
|
1949
|
+
sst.deepEqual(
|
|
1950
|
+
qs.parse('a=x,y&a=z', { comma: true, arrayLimit: 2, throwOnLimitExceeded: true, duplicates: 'last' }),
|
|
1951
|
+
{ a: 'z' },
|
|
1952
|
+
'duplicates: last replaces the full array'
|
|
1953
|
+
);
|
|
1954
|
+
sst.end();
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1624
1957
|
st.test('silently combines to an overflow object when throwOnLimitExceeded is not set', function (sst) {
|
|
1625
1958
|
var result = qs.parse('a=1,2,3&a=4,5,6', { comma: true, arrayLimit: 5 });
|
|
1626
1959
|
sst.deepEqual(result, { a: { 0: '1', 1: '2', 2: '3', 3: '4', 4: '5', 5: '6' } }, 'converts to object without throwing');
|