Skip to content

Commit c0d4d60

Browse files
committed
Stricter input validation
1 parent c94be4d commit c0d4d60

File tree

1 file changed

+64
-30
lines changed

1 file changed

+64
-30
lines changed

src/index.ts

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
function checkEndOfField(value: string, i: number) {
2+
switch (value[i]) {
3+
case ",":
4+
return true;
5+
6+
case ")":
7+
if (i < value.length - 1) {
8+
throw new RangeError(
9+
"Invalid composite literal: end of input expected"
10+
);
11+
}
12+
return false;
13+
14+
default:
15+
throw new RangeError("Invalid composite literal: ) or , expected");
16+
}
17+
}
18+
119
/**
220
* Parses a composite value into the list of attributes.
321
* `NULL` attributes are returned as JavaScript `null`, every other value
@@ -20,63 +38,79 @@
2038
* {@link https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX Source}
2139
*/
2240
export function* parse(value: string) {
23-
let i = 0;
24-
let end;
41+
if (typeof value !== "string") {
42+
throw new TypeError("Invalid input: string expected");
43+
}
44+
45+
if (value[0] !== "(") {
46+
throw new RangeError("Invalid composite literal: ( expected");
47+
}
2548

26-
// remove leading and trailing whitespace and parentheses
27-
value = value.trim();
28-
value = value.substring(1, value.length - 1);
49+
let i = 1;
50+
let end;
2951

3052
while (true) {
53+
// expecting a new field
3154
switch (value[i]) {
3255
case undefined:
33-
yield null;
34-
return;
56+
throw new RangeError(
57+
"Invalid composite literal: unexpected end of input"
58+
);
3559

60+
case ")": // fallthrough
3661
case ",":
3762
yield null;
38-
i += 1;
39-
continue;
63+
64+
if (checkEndOfField(value, i)) {
65+
i += 1;
66+
continue;
67+
}
68+
return;
4069

4170
case '"':
4271
i += 1;
43-
// find the next double quote not escaped by doubling
44-
end = i + value.substring(i).search(/(?<=([^"]|^)("")*)"(?!")/);
72+
end = i + value.substring(i).search(/(?<=([^"]|^)("")*)"[^"]/);
4573
if (end < i) {
46-
throw new RangeError("couldn't find closing double quote");
74+
throw new RangeError(
75+
"Invalid composite literal: unterminated double quotes"
76+
);
4777
}
4878

79+
// Should we check if there are odd number of \ before the closing doulbe quotes?
80+
// Postgres doesn't use \ to escape " in it's output, but accepts it in the input.
81+
4982
yield value
5083
.substring(i, end)
5184
.replace(/""/g, '"')
5285
.replace(/\\\\/g, "\\");
5386

54-
switch (value[end + 1]) {
55-
case undefined:
56-
return;
57-
58-
case ",":
59-
i = end + 2;
60-
continue;
61-
62-
default:
63-
throw new RangeError(
64-
"comma or end-of-string expected after closing double quote"
65-
);
87+
if (checkEndOfField(value, end + 1)) {
88+
i = end + 2;
89+
continue;
6690
}
91+
return;
6792

6893
default:
69-
// find the next comma or go to the end of the string
70-
end = value.indexOf(",", i);
94+
end = value.indexOf(",", i + 1);
7195
if (end === -1) {
72-
yield value.substring(i);
73-
return;
96+
end = value.indexOf(")", i + 1);
97+
if (end === -1) {
98+
throw new RangeError(
99+
"Invalid composite literal: unexpected end of input"
100+
);
101+
}
74102
}
75103

104+
// Should we check if there are any \ in the field value?
105+
// Postgres doesn't use escaped characters in unquoted fields, but accepts it in the input.
106+
76107
yield value.substring(i, end);
77108

78-
i = end + 1;
79-
continue;
109+
if (checkEndOfField(value, end)) {
110+
i = end + 1;
111+
continue;
112+
}
113+
return;
80114
}
81115
}
82116
}

0 commit comments

Comments
 (0)