Skip to content

Commit 8de0f14

Browse files
committed
fix(query): handle comments and improve single selection
close #3054
1 parent 2dc38cf commit 8de0f14

File tree

3 files changed

+13
-6
lines changed

3 files changed

+13
-6
lines changed

src/runtime/internal/query.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export const collectionQueryBuilder = <T extends keyof Collections>(collection:
137137
function buildQuery(opts: { count?: { field: string, distinct: boolean }, limit?: number } = {}) {
138138
let query = 'SELECT '
139139
if (opts?.count) {
140-
query += `COUNT(${opts.count.distinct ? 'DISTINCT' : ''} ${opts.count.field}) as count`
140+
query += `COUNT(${opts.count.distinct ? 'DISTINCT ' : ''}${opts.count.field}) as count`
141141
}
142142
else {
143143
const fields = Array.from(new Set(params.selectedFields))

src/runtime/internal/security.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const SQL_COMMANDS = /SELECT|INSERT|UPDATE|DELETE|DROP|ALTER/i
2+
const SQL_CLEANUN_REGEX = /(['"`])(?:\\.|[^\\])*?\1|\/\*[\s\S]*?\*\//g
3+
const SQL_COUNT_REGEX = /COUNT\((DISTINCT )?[a-z_]\w+\)/i
4+
const SQL_SELECT_REGEX = /^SELECT (.*) FROM (\w+)( WHERE .*)? ORDER BY (["\w,\s]+) (ASC|DESC)( LIMIT \d+)?( OFFSET \d+)?$/
25

36
/**
47
* Assert that the query is safe
@@ -10,7 +13,7 @@ const SQL_COMMANDS = /SELECT|INSERT|UPDATE|DELETE|DROP|ALTER/i
1013
* @returns True if the query is safe, false otherwise
1114
*/
1215
export function assertSafeQuery(sql: string, collection: string) {
13-
const match = sql.match(/^SELECT (.*) FROM (\w+)( WHERE .*)? ORDER BY (["\w,\s]+) (ASC|DESC)( LIMIT \d+)?( OFFSET \d+)?$/)
16+
const match = sql.match(SQL_SELECT_REGEX)
1417
if (!match) {
1518
throw new Error('Invalid query')
1619
}
@@ -22,8 +25,8 @@ export function assertSafeQuery(sql: string, collection: string) {
2225
if (columns.length === 1) {
2326
if (
2427
columns[0] !== '*'
25-
&& !columns[0].startsWith('COUNT(')
26-
&& !columns[0].match(/^COUNT\((DISTINCT )?[a-z_]\w+\) as count$/)
28+
&& !columns[0].match(SQL_COUNT_REGEX)
29+
&& !columns[0].match(/^"[a-z_]\w+"$/)
2730
) {
2831
throw new Error('Invalid query')
2932
}
@@ -42,7 +45,7 @@ export function assertSafeQuery(sql: string, collection: string) {
4245
if (!where.startsWith(' WHERE (') || !where.endsWith(')')) {
4346
throw new Error('Invalid query')
4447
}
45-
const noString = where?.replace(/(['"`])(?:\\.|[^\\])*?\1/g, '')
48+
const noString = where?.replace(SQL_CLEANUN_REGEX, '')
4649
if (noString.match(SQL_COMMANDS)) {
4750
throw new Error('Invalid query')
4851
}

test/unit/assertSafeQuery.test.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ describe('decompressSQLDump', () => {
3434
'SELECT * FROM _content_test WHERE id = 1 ORDER BY id DESC LIMIT 10 OFFSET 10': false,
3535
'SELECT * FROM _content_test WHERE (id = 1) ORDER BY id DESC LIMIT 10 OFFSET 10': true,
3636
'SELECT * FROM _content_test WHERE (id = \'");\'); select * from ((SELECT * FROM sqlite_master where 1 <> "") as t) ORDER BY type DESC': false,
37+
'SELECT "body" FROM _content_test ORDER BY body ASC': true,
38+
// Advanced
39+
'SELECT COUNT(*) UNION SELECT name /**/FROM sqlite_master-- FROM _content_test WHERE (1=1) ORDER BY id ASC': false,
40+
'SELECT * FROM _content_test WHERE (id /*\'*/IN (SELECT id FROM _content_test) /*\'*/) ORDER BY id ASC': false,
3741
}
3842

3943
Object.entries(queries).forEach(([query, isValid]) => {
@@ -47,7 +51,7 @@ describe('decompressSQLDump', () => {
4751
})
4852
})
4953

50-
it('throws error if query is not valid', async () => {
54+
it('all queries should be valid', async () => {
5155
await collectionQueryBuilder(mockCollection, mockFetch).all()
5256
expect(() => assertSafeQuery(mockFetch.mock.lastCall![1], mockCollection)).not.toThrow()
5357

0 commit comments

Comments
 (0)