Skip to content

Commit 82c490e

Browse files
authored
Fix parsing large escaped strings from Postgres structures (#420)
* Fix parsing large escaped strings from Postgres structures * Build in chunks
1 parent c050acd commit 82c490e

File tree

3 files changed

+42
-17
lines changed

3 files changed

+42
-17
lines changed

.changeset/shiny-jars-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/service-jpgwire': patch
3+
---
4+
5+
Fix stack overflow when parsing large escaped strings in structured values from Postgres.

packages/jpgwire/src/structure_parser.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export class StructureParser {
1414
return this.source.charCodeAt(this.offset);
1515
}
1616

17+
private currentChar(): string {
18+
return this.source.charAt(this.offset);
19+
}
20+
1721
private get isAtEnd(): boolean {
1822
return this.offset == this.source.length;
1923
}
@@ -69,40 +73,47 @@ export class StructureParser {
6973
this.consume(CHAR_CODE_DOUBLE_QUOTE);
7074

7175
const start = this.offset;
72-
const charCodes: number[] = [];
73-
let previousWasBackslash = false;
76+
let buffer = '';
7477

7578
while (true) {
76-
const char = this.currentCharCode();
79+
const startPosition = this.offset;
7780

78-
if (previousWasBackslash) {
79-
if (char != CHAR_CODE_DOUBLE_QUOTE && char != CHAR_CODE_BACKSLASH) {
80-
this.error('Expected escaped double quote or escaped backslash');
81-
}
82-
charCodes.push(char);
83-
previousWasBackslash = false;
84-
} else if (char == CHAR_CODE_DOUBLE_QUOTE) {
81+
// Skip past "boring" chars that we just need to add to the buffer.
82+
let char = this.currentCharCode();
83+
while (char != CHAR_CODE_BACKSLASH && char != CHAR_CODE_DOUBLE_QUOTE) {
84+
this.advance();
85+
char = this.currentCharCode();
86+
}
87+
88+
if (this.offset > startPosition) {
89+
buffer += this.source.substring(startPosition, this.offset);
90+
}
91+
92+
if (char == CHAR_CODE_DOUBLE_QUOTE) {
8593
if (this.offset != start && allowEscapingWithDoubleDoubleQuote) {
8694
// If the next character is also a double quote, that escapes a single double quote
8795
if (this.offset < this.source.length - 1 && this.peek() == CHAR_CODE_DOUBLE_QUOTE) {
8896
this.offset += 2;
89-
charCodes.push(CHAR_CODE_DOUBLE_QUOTE);
97+
buffer += STRING_DOUBLE_QUOTE;
9098
continue;
9199
}
92100
}
93101

94102
break; // End of string.
95103
} else if (char == CHAR_CODE_BACKSLASH) {
96-
previousWasBackslash = true;
97-
} else {
98-
charCodes.push(char);
99-
}
104+
this.advance(); // Consume the backslash
105+
const nextChar = this.currentCharCode();
106+
if (nextChar != CHAR_CODE_DOUBLE_QUOTE && nextChar != CHAR_CODE_BACKSLASH) {
107+
this.error('Expected escaped double quote or escaped backslash');
108+
}
100109

101-
this.advance();
110+
buffer += this.currentChar();
111+
this.advance();
112+
}
102113
}
103114

104115
this.consume(CHAR_CODE_DOUBLE_QUOTE);
105-
return String.fromCharCode(...charCodes);
116+
return buffer;
106117
}
107118

108119
unquotedString(endedBy: number[], illegal: number[]): string {
@@ -289,6 +300,7 @@ export type Range<T> =
289300
export type ElementOrArray<T> = null | T | ElementOrArray<T>[];
290301

291302
const CHAR_CODE_DOUBLE_QUOTE = 0x22;
303+
const STRING_DOUBLE_QUOTE = '"';
292304
const CHAR_CODE_BACKSLASH = 0x5c;
293305
export const CHAR_CODE_COMMA = 0x2c;
294306
export const CHAR_CODE_SEMICOLON = 0x3b;

packages/jpgwire/test/structure_parser.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ describe('StructureParser', () => {
2929
expect(parseArray('{"foo"}')).toStrictEqual(['foo']);
3030
expect(parseArray('{"fo\\"o"}')).toStrictEqual(['fo"o']);
3131
expect(parseArray('{"fo\\\\o"}')).toStrictEqual(['fo\\o']);
32+
33+
// Regression test for https://github.com/powersync-ja/powersync-service/issues/419
34+
let largeStringValue = '';
35+
for (let i = 0; i < 65000; i++) {
36+
largeStringValue += 'hello world';
37+
}
38+
39+
expect(parseArray(`{"${largeStringValue}"}`)).toStrictEqual([largeStringValue]);
3240
});
3341

3442
test('nested', () => {

0 commit comments

Comments
 (0)