Skip to content

Commit 6997f35

Browse files
committed
fix(react-native): validate op-sqlite row carriers
1 parent fc67799 commit 6997f35

2 files changed

Lines changed: 44 additions & 11 deletions

File tree

packages/react-native-db-sqlite-persistence/src/op-sqlite-driver.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,20 @@ function unsupportedQueryResult(sql: string, details?: string): never {
152152
)
153153
}
154154

155+
function isValidRowList(
156+
rowsObject: OpSQLiteRowListLike,
157+
): rowsObject is OpSQLiteRowListLike & {
158+
length: number
159+
item: (index: number) => unknown
160+
} {
161+
return (
162+
typeof rowsObject.length === `number` &&
163+
Number.isSafeInteger(rowsObject.length) &&
164+
rowsObject.length >= 0 &&
165+
typeof rowsObject.item === `function`
166+
)
167+
}
168+
155169
function toRowArray(rowsValue: unknown): Array<unknown> | null {
156170
if (Array.isArray(rowsValue)) {
157171
return rowsValue
@@ -166,14 +180,10 @@ function toRowArray(rowsValue: unknown): Array<unknown> | null {
166180
return rowsObject._array
167181
}
168182

169-
if (
170-
typeof rowsObject.length === `number` &&
171-
typeof rowsObject.item === `function`
172-
) {
173-
const item = rowsObject.item as (index: number) => unknown
183+
if (isValidRowList(rowsObject)) {
174184
const rows: Array<unknown> = []
175185
for (let index = 0; index < rowsObject.length; index++) {
176-
rows.push(item(index))
186+
rows.push(rowsObject.item(index))
177187
}
178188
return rows
179189
}
@@ -191,11 +201,7 @@ function isRowCarrier(rowsValue: unknown): boolean {
191201
}
192202

193203
const rowsObject = rowsValue as OpSQLiteRowListLike
194-
return (
195-
Array.isArray(rowsObject._array) ||
196-
(typeof rowsObject.length === `number` &&
197-
typeof rowsObject.item === `function`)
198-
)
204+
return Array.isArray(rowsObject._array) || isValidRowList(rowsObject)
199205
}
200206

201207
function isStatementResultEnvelope(value: Record<string, unknown>): boolean {
@@ -340,6 +346,12 @@ function extractRowsFromExecuteResult(
340346

341347
if (Array.isArray(result)) {
342348
if (result.length === 0) {
349+
if (arrayResultMode === `statement-results`) {
350+
return unsupportedQueryResult(
351+
sql,
352+
`statement-result arrays must contain exactly one result`,
353+
)
354+
}
343355
return []
344356
}
345357

packages/react-native-db-sqlite-persistence/tests/op-sqlite-driver.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,27 @@ it(`materializes a declared statement row list exactly once`, async () => {
295295
expect(requestedIndexes).toEqual([0, 1])
296296
})
297297

298+
it.each([
299+
{ name: `NaN`, length: Number.NaN },
300+
{ name: `negative`, length: -1 },
301+
{ name: `fractional`, length: 1.5 },
302+
])(`rejects a $name statement row-list length`, async ({ length }) => {
303+
await expect(
304+
queryInjectedResult(
305+
[{ rowsAffected: 0, rows: { length, item: () => ({ id: `row` }) } }],
306+
`statement-results`,
307+
),
308+
).rejects.toThrow(`recognized statement envelope`)
309+
})
310+
311+
it(`rejects an empty array in statement-results mode`, async () => {
312+
await expect(queryInjectedResult([], `statement-results`)).rejects.toThrow(
313+
`statement-result arrays must contain exactly one result`,
314+
)
315+
await expect(queryInjectedResult([], `rows`)).resolves.toEqual([])
316+
await expect(queryInjectedResult([])).resolves.toEqual([])
317+
})
318+
298319
it(`reads op-sqlite execute rows when columnNames metadata is also present`, async () => {
299320
await expect(
300321
queryInjectedResult({

0 commit comments

Comments
 (0)