Skip to content

Commit e8a6ec9

Browse files
authoredNov 6, 2024··
qs.parse returns invalid key with brackets when using nested array query (#1751)
* add test to fix nested query * add fix to account for limitations of qs.parse * Add assertion for key wrapped in square brackets * update qs library that includes strictDepth=true * Revert "add test to fix nested query" This reverts commit a3ea344. * remove previous fix that manually changes the key. Just use qs configuration * add qs-test module * fix lint * update ts-expect-error for missing strictDepth type * fix spacing * Upgrade @types/qs to include strictDepth
1 parent 8168783 commit e8a6ec9

File tree

7 files changed

+135
-70
lines changed

7 files changed

+135
-70
lines changed
 

‎packages/host/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
"@types/matrix-js-sdk": "^11.0.1",
6969
"@types/ms": "^0.7.34",
7070
"@types/pluralize": "^0.0.30",
71-
"@types/qs": "^6.9.14",
71+
"@types/qs": "^6.9.17",
7272
"@types/qunit": "^2.11.3",
7373
"@types/rsvp": "^4.0.9",
7474
"@types/uuid": "^9.0.8",
@@ -159,7 +159,7 @@
159159
"pluralize": "^8.0.0",
160160
"prettier": "^3.0.3",
161161
"prettier-plugin-ember-template-tag": "^1.1.0",
162-
"qs": "^6.12.3",
162+
"qs": "^6.13.0",
163163
"qunit": "^2.20.0",
164164
"qunit-dom": "^2.0.0",
165165
"safe-stable-stringify": "^2.4.3",

‎packages/host/tests/unit/qs-test.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import qs from 'qs';
2+
import { module, test } from 'qunit';
3+
4+
import { parseQuery, Query } from '@cardstack/runtime-common/query';
5+
6+
module('Unit | qs | parse', function () {
7+
test('parseQuery errors out if the query is too deep', async function (assert) {
8+
assert.throws(
9+
() => parseQuery('a[b][c][d][e][f][g][h][i][j][k][l]=m'),
10+
/RangeError: Input depth exceeded depth option of 10 and strictDepth is true/,
11+
);
12+
});
13+
test('invertibility: applying stringify and parse on object will return the same object', async function (assert) {
14+
let testRealmURL = 'https://example.com/';
15+
let query: Query = {
16+
filter: {
17+
on: {
18+
module: `${testRealmURL}book`,
19+
name: 'Book',
20+
},
21+
every: [
22+
{
23+
eq: {
24+
'author.firstName': 'Cardy',
25+
},
26+
},
27+
{
28+
any: [
29+
{
30+
eq: {
31+
'author.lastName': 'Jones',
32+
},
33+
},
34+
{
35+
eq: {
36+
'author.lastName': 'Stackington Jr. III',
37+
},
38+
},
39+
],
40+
},
41+
],
42+
},
43+
sort: [
44+
{
45+
by: 'author.lastName',
46+
on: { module: `${testRealmURL}book`, name: 'Book' },
47+
},
48+
],
49+
};
50+
let queryString = qs.stringify(query);
51+
let parsedQuery: any = parseQuery(queryString);
52+
assert.deepEqual(parsedQuery, query);
53+
});
54+
});

‎packages/realm-server/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"@types/mime-types": "^2.1.1",
2323
"@types/node": "^18.18.5",
2424
"@types/pg": "^8.11.5",
25-
"@types/qs": "^6.9.14",
25+
"@types/qs": "^6.9.17",
2626
"@types/qunit": "^2.11.3",
2727
"@types/sane": "^2.0.1",
2828
"@types/supertest": "^2.0.12",
@@ -51,7 +51,7 @@
5151
"pg": "^8.11.5",
5252
"prettier": "^2.8.4",
5353
"prettier-plugin-ember-template-tag": "^1.1.0",
54-
"qs": "^6.12.3",
54+
"qs": "^6.13.0",
5555
"qunit": "^2.20.0",
5656
"sane": "^5.0.1",
5757
"sql-parser-cst": "^0.28.0",

‎packages/runtime-common/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"@types/jsonwebtoken": "^9.0.5",
2626
"@types/lodash": "^4.14.182",
2727
"@types/pluralize": "^0.0.30",
28-
"@types/qs": "^6.9.14",
28+
"@types/qs": "^6.9.17",
2929
"@types/uuid": "^9.0.8",
3030
"babel-import-util": "^1.2.2",
3131
"babel-plugin-ember-template-compilation": "^2.2.1",
@@ -45,7 +45,7 @@
4545
"loglevel": "^1.8.1",
4646
"marked": "^12.0.1",
4747
"pluralize": "^8.0.0",
48-
"qs": "^6.12.3",
48+
"qs": "^6.13.0",
4949
"qunit": "^2.20.0",
5050
"recast": "^0.23.4",
5151
"safe-stable-stringify": "^2.4.3",

‎packages/runtime-common/query.ts

+23-9
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,9 @@ function assertEveryFilter(
307307
`${pointer.join('/') || '/'}: every must be an array of Filters`,
308308
);
309309
} else {
310-
filter.every.every((value: any, index: number) =>
311-
assertFilter(value, pointer.concat(`[${index}]`)),
312-
);
310+
filter.every.forEach((value: any, index: number) => {
311+
assertFilter(value, pointer.concat(`[${index}]`));
312+
});
313313
}
314314
}
315315

@@ -348,9 +348,10 @@ function assertEqFilter(
348348
if (typeof filter.eq !== 'object' || filter.eq == null) {
349349
throw new Error(`${pointer.join('/') || '/'}: eq must be an object`);
350350
}
351-
Object.entries(filter.eq).every(([key, value]) =>
352-
assertJSONValue(value, pointer.concat(key)),
353-
);
351+
Object.entries(filter.eq).forEach(([key, value]) => {
352+
assertKey(key, pointer);
353+
assertJSONValue(value, pointer.concat(key));
354+
});
354355
}
355356

356357
function assertContainsFilter(
@@ -371,9 +372,10 @@ function assertContainsFilter(
371372
if (typeof filter.contains !== 'object' || filter.contains == null) {
372373
throw new Error(`${pointer.join('/') || '/'}: contains must be an object`);
373374
}
374-
Object.entries(filter.contains).every(([key, value]) =>
375-
assertJSONValue(value, pointer.concat(key)),
376-
);
375+
Object.entries(filter.contains).forEach(([key, value]) => {
376+
assertKey(key, pointer);
377+
assertJSONValue(value, pointer.concat(key));
378+
});
377379
}
378380

379381
function assertRangeFilter(
@@ -419,3 +421,15 @@ function assertRangeFilter(
419421
});
420422
});
421423
}
424+
425+
export function assertKey(key: string, pointer: string[]) {
426+
if (key.startsWith('[') && key.endsWith(']')) {
427+
throw new Error(
428+
`${pointer.join('/')}: field names cannot be wrapped in brackets: ${key}`,
429+
);
430+
}
431+
}
432+
433+
export const parseQuery = (queryString: string) => {
434+
return qs.parse(queryString, { depth: 10, strictDepth: true });
435+
};

‎packages/runtime-common/realm.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ import {
5959
SupportedMimeType,
6060
lookupRouteTable,
6161
} from './router';
62-
import { assertQuery } from './query';
62+
import { assertQuery, parseQuery } from './query';
6363
import type { Readable } from 'stream';
6464
import { type CardDef } from 'https://cardstack.com/base/card-api';
6565
import type * as CardAPI from 'https://cardstack.com/base/card-api';
@@ -76,7 +76,6 @@ import { fetcher } from './fetcher';
7676
import { RealmIndexQueryEngine } from './realm-index-query-engine';
7777
import { RealmIndexUpdater } from './realm-index-updater';
7878

79-
import qs from 'qs';
8079
import {
8180
MatrixBackendAuthentication,
8281
Utils,
@@ -1548,7 +1547,7 @@ export class Realm {
15481547
request.headers.get('X-Boxel-Building-Index'),
15491548
);
15501549

1551-
let cardsQuery = qs.parse(new URL(request.url).search.slice(1));
1550+
let cardsQuery = parseQuery(new URL(request.url).search.slice(1));
15521551
assertQuery(cardsQuery);
15531552

15541553
let doc = await this.#realmIndexQueryEngine.search(cardsQuery, {
@@ -1572,7 +1571,8 @@ export class Realm {
15721571
request.headers.get('X-Boxel-Building-Index'),
15731572
);
15741573

1575-
let parsedQueryString = qs.parse(new URL(request.url).search.slice(1));
1574+
let href = new URL(request.url).search.slice(1);
1575+
let parsedQueryString = parseQuery(href);
15761576
let htmlFormat = parsedQueryString.prerenderedHtmlFormat as string;
15771577
let cardUrls = parsedQueryString.cardUrls as string[];
15781578

0 commit comments

Comments
 (0)