Skip to content

Commit b891ab9

Browse files
committed
Add backtrack protection to 6.x
1 parent 28a5b27 commit b891ab9

File tree

5 files changed

+165
-54
lines changed

5 files changed

+165
-54
lines changed

package-lock.json

Lines changed: 102 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@types/node": "^20.4.9",
3737
"@types/semver": "^7.3.1",
3838
"@vitest/coverage-v8": "^1.4.0",
39+
"recheck": "^4.4.5",
3940
"semver": "^7.3.5",
4041
"size-limit": "^11.1.2",
4142
"typescript": "^5.1.6"

redos.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { checkSync } from "recheck";
2+
import { pathToRegexp } from "./src/index.js";
3+
4+
let safe = 0;
5+
let fail = 0;
6+
7+
const tests = ["/:x{/foobar/:y}?-:z"];
8+
9+
for (const path of tests) {
10+
const regexp = pathToRegexp(path);
11+
const result = checkSync(regexp.source, regexp.flags);
12+
if (result.status === "safe") {
13+
safe++;
14+
console.log("Safe:", path, String(regexp));
15+
} else {
16+
fail++;
17+
console.log("Fail:", path, String(regexp));
18+
}
19+
}
20+
21+
console.log("Safe:", safe, "Fail:", fail);

src/index.spec.ts

Lines changed: 14 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,7 @@ const TESTS: Test[] = [
13531353
prefix: ".",
13541354
suffix: "",
13551355
modifier: "+",
1356-
pattern: "[^\\/#\\?]+?",
1356+
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
13571357
},
13581358
],
13591359
[
@@ -1397,7 +1397,7 @@ const TESTS: Test[] = [
13971397
prefix: ".",
13981398
suffix: "",
13991399
modifier: "",
1400-
pattern: "[^\\/#\\?]+?",
1400+
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
14011401
},
14021402
".",
14031403
],
@@ -1430,13 +1430,13 @@ const TESTS: Test[] = [
14301430
prefix: ".",
14311431
suffix: "",
14321432
modifier: "",
1433-
pattern: "[^\\/#\\?]+?",
1433+
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
14341434
},
14351435
],
14361436
[
14371437
["/route.html", ["/route.html", "route", "html"]],
14381438
["/route", null],
1439-
["/route.html.json", ["/route.html.json", "route", "html.json"]],
1439+
["/route.html.json", ["/route.html.json", "route.html", "json"]],
14401440
],
14411441
[
14421442
[{}, null],
@@ -1459,13 +1459,13 @@ const TESTS: Test[] = [
14591459
prefix: ".",
14601460
suffix: "",
14611461
modifier: "?",
1462-
pattern: "[^\\/#\\?]+?",
1462+
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
14631463
},
14641464
],
14651465
[
14661466
["/route", ["/route", "route", undefined]],
14671467
["/route.json", ["/route.json", "route", "json"]],
1468-
["/route.json.html", ["/route.json.html", "route", "json.html"]],
1468+
["/route.json.html", ["/route.json.html", "route.json", "html"]],
14691469
],
14701470
[
14711471
[{ test: "route" }, "/route"],
@@ -1491,13 +1491,13 @@ const TESTS: Test[] = [
14911491
prefix: ".",
14921492
suffix: "",
14931493
modifier: "?",
1494-
pattern: "[^\\/#\\?]+?",
1494+
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
14951495
},
14961496
],
14971497
[
14981498
["/route", ["/route", "route", undefined]],
14991499
["/route.json", ["/route.json", "route", "json"]],
1500-
["/route.json.html", ["/route.json.html", "route", "json.html"]],
1500+
["/route.json.html", ["/route.json.html", "route.json", "html"]],
15011501
],
15021502
[
15031503
[{ test: "route" }, "/route"],
@@ -2084,7 +2084,7 @@ const TESTS: Test[] = [
20842084
prefix: "",
20852085
suffix: "",
20862086
modifier: "?",
2087-
pattern: "[^\\/#\\?]+?",
2087+
pattern: "(?:(?!\\()[^\\/#\\?])+?",
20882088
},
20892089
")",
20902090
],
@@ -2290,7 +2290,7 @@ const TESTS: Test[] = [
22902290
prefix: ".",
22912291
suffix: "",
22922292
modifier: "",
2293-
pattern: "[^\\/#\\?]+?",
2293+
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
22942294
},
22952295
],
22962296
[
@@ -2356,14 +2356,14 @@ const TESTS: Test[] = [
23562356
[
23572357
{
23582358
name: "foo",
2359-
pattern: "[^\\/#\\?]+?",
2359+
pattern: "(?:(?!\\$)[^\\/#\\?])+?",
23602360
prefix: "$",
23612361
suffix: "",
23622362
modifier: "",
23632363
},
23642364
{
23652365
name: "bar",
2366-
pattern: "[^\\/#\\?]+?",
2366+
pattern: "(?:(?!\\$)[^\\/#\\?])+?",
23672367
prefix: "$",
23682368
suffix: "",
23692369
modifier: "?",
@@ -2392,14 +2392,14 @@ const TESTS: Test[] = [
23922392
},
23932393
{
23942394
name: "attr2",
2395-
pattern: "[^\\/#\\?]+?",
2395+
pattern: "(?:(?!-)[^\\/#\\?])+?",
23962396
prefix: "-",
23972397
suffix: "",
23982398
modifier: "?",
23992399
},
24002400
{
24012401
name: "attr3",
2402-
pattern: "[^\\/#\\?]+?",
2402+
pattern: "(?:(?!-)[^\\/#\\?])+?",
24032403
prefix: "-",
24042404
suffix: "",
24052405
modifier: "?",
@@ -2597,39 +2597,6 @@ const TESTS: Test[] = [
25972597
[{ foo: "#" }, null],
25982598
],
25992599
],
2600-
/**
2601-
* https://github.com/pillarjs/path-to-regexp/issues/260
2602-
*/
2603-
[
2604-
":name*",
2605-
undefined,
2606-
[
2607-
{
2608-
name: "name",
2609-
prefix: "",
2610-
suffix: "",
2611-
modifier: "*",
2612-
pattern: "[^\\/#\\?]+?",
2613-
},
2614-
],
2615-
[["foobar", ["foobar", "foobar"]]],
2616-
[[{ name: "foobar" }, "foobar"]],
2617-
],
2618-
[
2619-
":name+",
2620-
undefined,
2621-
[
2622-
{
2623-
name: "name",
2624-
prefix: "",
2625-
suffix: "",
2626-
modifier: "+",
2627-
pattern: "[^\\/#\\?]+?",
2628-
},
2629-
],
2630-
[["foobar", ["foobar", "foobar"]]],
2631-
[[{ name: "foobar" }, "foobar"]],
2632-
],
26332600
];
26342601

26352602
/**

src/index.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ export interface ParseOptions {
139139
*/
140140
export function parse(str: string, options: ParseOptions = {}): Token[] {
141141
const tokens = lexer(str);
142-
const { prefixes = "./" } = options;
143-
const defaultPattern = `[^${escapeString(options.delimiter || "/#?")}]+?`;
142+
const { prefixes = "./", delimiter = "/#?" } = options;
144143
const result: Token[] = [];
145144
let key = 0;
146145
let i = 0;
@@ -166,6 +165,25 @@ export function parse(str: string, options: ParseOptions = {}): Token[] {
166165
return result;
167166
};
168167

168+
const isSafe = (value: string): boolean => {
169+
for (const char of delimiter) if (value.indexOf(char) > -1) return true;
170+
return false;
171+
};
172+
173+
const safePattern = (prefix: string) => {
174+
const prev = result[result.length - 1];
175+
const prevText = prefix || (prev && typeof prev === "string" ? prev : "");
176+
177+
if (prev && !prevText) {
178+
throw new TypeError(
179+
`No support for parameters without text between them after "${(prev as Key).name}"`,
180+
);
181+
}
182+
183+
if (!prevText || isSafe(prevText)) return `[^${escapeString(delimiter)}]+?`;
184+
return `(?:(?!${escapeString(prevText)})[^${escapeString(delimiter)}])+?`;
185+
};
186+
169187
while (i < tokens.length) {
170188
const char = tryConsume("CHAR");
171189
const name = tryConsume("NAME");
@@ -188,7 +206,7 @@ export function parse(str: string, options: ParseOptions = {}): Token[] {
188206
name: name || key++,
189207
prefix,
190208
suffix: "",
191-
pattern: pattern || defaultPattern,
209+
pattern: pattern || safePattern(prefix),
192210
modifier: tryConsume("MODIFIER") || "",
193211
});
194212
continue;
@@ -216,7 +234,7 @@ export function parse(str: string, options: ParseOptions = {}): Token[] {
216234

217235
result.push({
218236
name: name || (pattern ? key++ : ""),
219-
pattern: name && !pattern ? defaultPattern : pattern,
237+
pattern: name && !pattern ? safePattern(prefix) : pattern,
220238
prefix,
221239
suffix,
222240
modifier: tryConsume("MODIFIER") || "",
@@ -564,10 +582,12 @@ export function tokensToRegexp(
564582
}
565583
} else {
566584
if (token.modifier === "+" || token.modifier === "*") {
567-
route += `((?:${token.pattern})${token.modifier})`;
568-
} else {
569-
route += `(${token.pattern})${token.modifier}`;
585+
throw new TypeError(
586+
`Can not repeat ${token.name} with no prefix or suffix, e.g. "/:param${token.modifier}"`,
587+
);
570588
}
589+
590+
route += `(${token.pattern})${token.modifier}`;
571591
}
572592
} else {
573593
route += `(?:${prefix}${suffix})${token.modifier}`;

0 commit comments

Comments
 (0)