Skip to content

Commit

Permalink
Add backtrack protection to 6.x
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed Sep 12, 2024
1 parent 28a5b27 commit bf0365b
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 54 deletions.
102 changes: 102 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@types/node": "^20.4.9",
"@types/semver": "^7.3.1",
"@vitest/coverage-v8": "^1.4.0",
"recheck": "^4.4.5",
"semver": "^7.3.5",
"size-limit": "^11.1.2",
"typescript": "^5.1.6"
Expand Down
21 changes: 21 additions & 0 deletions redos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { checkSync } from "recheck";
import { pathToRegexp } from "./src/index.js";

let safe = 0;
let fail = 0;

const tests = ["/:x{/foobar/:y}?-:z"];

for (const path of tests) {
const regexp = pathToRegexp(path);
const result = checkSync(regexp.source, regexp.flags);
if (result.status === "safe") {
safe++;
console.log("Safe:", path, String(regexp));
} else {
fail++;
console.log("Fail:", path, String(regexp));
}
}

console.log("Safe:", safe, "Fail:", fail);
61 changes: 14 additions & 47 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1353,7 +1353,7 @@ const TESTS: Test[] = [
prefix: ".",
suffix: "",
modifier: "+",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
},
],
[
Expand Down Expand Up @@ -1397,7 +1397,7 @@ const TESTS: Test[] = [
prefix: ".",
suffix: "",
modifier: "",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
},
".",
],
Expand Down Expand Up @@ -1430,13 +1430,13 @@ const TESTS: Test[] = [
prefix: ".",
suffix: "",
modifier: "",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
},
],
[
["/route.html", ["/route.html", "route", "html"]],
["/route", null],
["/route.html.json", ["/route.html.json", "route", "html.json"]],
["/route.html.json", ["/route.html.json", "route.html", "json"]],
],
[
[{}, null],
Expand All @@ -1459,13 +1459,13 @@ const TESTS: Test[] = [
prefix: ".",
suffix: "",
modifier: "?",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
},
],
[
["/route", ["/route", "route", undefined]],
["/route.json", ["/route.json", "route", "json"]],
["/route.json.html", ["/route.json.html", "route", "json.html"]],
["/route.json.html", ["/route.json.html", "route.json", "html"]],
],
[
[{ test: "route" }, "/route"],
Expand All @@ -1491,13 +1491,13 @@ const TESTS: Test[] = [
prefix: ".",
suffix: "",
modifier: "?",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
},
],
[
["/route", ["/route", "route", undefined]],
["/route.json", ["/route.json", "route", "json"]],
["/route.json.html", ["/route.json.html", "route", "json.html"]],
["/route.json.html", ["/route.json.html", "route.json", "html"]],
],
[
[{ test: "route" }, "/route"],
Expand Down Expand Up @@ -2084,7 +2084,7 @@ const TESTS: Test[] = [
prefix: "",
suffix: "",
modifier: "?",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\()[^\\/#\\?])+?",
},
")",
],
Expand Down Expand Up @@ -2290,7 +2290,7 @@ const TESTS: Test[] = [
prefix: ".",
suffix: "",
modifier: "",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\.)[^\\/#\\?])+?",
},
],
[
Expand Down Expand Up @@ -2356,14 +2356,14 @@ const TESTS: Test[] = [
[
{
name: "foo",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\$)[^\\/#\\?])+?",
prefix: "$",
suffix: "",
modifier: "",
},
{
name: "bar",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!\\$)[^\\/#\\?])+?",
prefix: "$",
suffix: "",
modifier: "?",
Expand Down Expand Up @@ -2392,14 +2392,14 @@ const TESTS: Test[] = [
},
{
name: "attr2",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!-)[^\\/#\\?])+?",
prefix: "-",
suffix: "",
modifier: "?",
},
{
name: "attr3",
pattern: "[^\\/#\\?]+?",
pattern: "(?:(?!-)[^\\/#\\?])+?",
prefix: "-",
suffix: "",
modifier: "?",
Expand Down Expand Up @@ -2597,39 +2597,6 @@ const TESTS: Test[] = [
[{ foo: "#" }, null],
],
],
/**
* https://github.com/pillarjs/path-to-regexp/issues/260
*/
[
":name*",
undefined,
[
{
name: "name",
prefix: "",
suffix: "",
modifier: "*",
pattern: "[^\\/#\\?]+?",
},
],
[["foobar", ["foobar", "foobar"]]],
[[{ name: "foobar" }, "foobar"]],
],
[
":name+",
undefined,
[
{
name: "name",
prefix: "",
suffix: "",
modifier: "+",
pattern: "[^\\/#\\?]+?",
},
],
[["foobar", ["foobar", "foobar"]]],
[[{ name: "foobar" }, "foobar"]],
],
];

/**
Expand Down
34 changes: 27 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ export interface ParseOptions {
*/
export function parse(str: string, options: ParseOptions = {}): Token[] {
const tokens = lexer(str);
const { prefixes = "./" } = options;
const defaultPattern = `[^${escapeString(options.delimiter || "/#?")}]+?`;
const { prefixes = "./", delimiter = "/#?" } = options;
const result: Token[] = [];
let key = 0;
let i = 0;
Expand All @@ -166,6 +165,25 @@ export function parse(str: string, options: ParseOptions = {}): Token[] {
return result;
};

const isSafe = (value: string): boolean => {
for (const char of delimiter) if (value.indexOf(char) > -1) return true;
return false;
};

const safePattern = (prefix: string) => {
const prev = result[result.length - 1];
const prevText = prefix || (prev && typeof prev === "string" ? prev : "");

if (prev && !prevText) {
throw new TypeError(
`No support for parameters without text between them after "${(prev as Key).name}"`,
);
}

if (!prevText || isSafe(prevText)) return `[^${escapeString(delimiter)}]+?`;
return `(?:(?!${escapeString(prevText)})[^${escapeString(delimiter)}])+?`;
};

while (i < tokens.length) {
const char = tryConsume("CHAR");
const name = tryConsume("NAME");
Expand All @@ -188,7 +206,7 @@ export function parse(str: string, options: ParseOptions = {}): Token[] {
name: name || key++,
prefix,
suffix: "",
pattern: pattern || defaultPattern,
pattern: pattern || safePattern(prefix),
modifier: tryConsume("MODIFIER") || "",
});
continue;
Expand Down Expand Up @@ -216,7 +234,7 @@ export function parse(str: string, options: ParseOptions = {}): Token[] {

result.push({
name: name || (pattern ? key++ : ""),
pattern: name && !pattern ? defaultPattern : pattern,
pattern: name && !pattern ? safePattern(prefix) : pattern,
prefix,
suffix,
modifier: tryConsume("MODIFIER") || "",
Expand Down Expand Up @@ -564,10 +582,12 @@ export function tokensToRegexp(
}
} else {
if (token.modifier === "+" || token.modifier === "*") {
route += `((?:${token.pattern})${token.modifier})`;
} else {
route += `(${token.pattern})${token.modifier}`;
throw new TypeError(
`Can not repeat ${token.name} with no prefix or suffix, e.g. "/:param${token.modifier}"`,
);
}

route += `(${token.pattern})${token.modifier}`;
}
} else {
route += `(?:${prefix}${suffix})${token.modifier}`;
Expand Down

0 comments on commit bf0365b

Please sign in to comment.