wip: linear URL-cleaning regexes

This commit is contained in:
4gray committed 2026-10-13 21:54:04 +02:00
1 parent ee35951a5e
commit 0b91d4d1a0
2 files changed
+18 -4

No files matched your search

@@ -96,6 +96,17 @@ describe('stripUrlCredentials', () => {
expect(stripUrlCredentials(url)).not.toMatch(/hunter2/);
});
it('stays fast on long runs a backtracking pattern would retry', () => {
const run = 'a'.repeat(50_000);
const started = performance.now();
for (const value of [run, `${run}.`.repeat(2), 'a.'.repeat(25_000)]) {
stripUrlCredentials(
`https://host.test/list?note=${encodeURIComponent(value)}`
);
}
expect(performance.now() - started).toBeLessThan(1_000);
});
it('returns null for text that is not an absolute URL', () => {
expect(stripUrlCredentials('get.php?username=joe')).toBeNull();
expect(stripUrlCredentials('')).toBeNull();
@@ -12,11 +12,14 @@ const SHORT_ACCOUNT_KEYS = new Set(['user', 'usr', 'pass', 'pas']);
/** How deep URLs nested in URLs are cleaned; anything deeper is dropped. */
const MAX_NESTING = 4;
const ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//iu;
const EMBEDDED_URL = /[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/giu;
// Linear on any input: a match starts only where a name can start (the
// lookbehinds), and every name part is bounded, so a long run of letters
// cannot make a scan retry from each position.
const ABSOLUTE_URL = /^[a-z][a-z0-9+.-]{0,31}:\/\//iu;
const EMBEDDED_URL = /(?<![a-z0-9+.-])[a-z][a-z0-9+.-]{0,31}:\/\/[^\s"'<>]+/giu;
/** `joe:hunter2@origin.test`: an authority's userinfo without a scheme. */
const BARE_USERINFO = /(?:^|[\s/])[^\s/:@]+:[^\s/@]*@[^\s/@]/u;
const KEYED_PAIR = /\b([a-z][a-z0-9_.-]*)\s*[=:]/giu;
const BARE_USERINFO = /(?:^|[\s/])[^\s/:@]{1,256}:[^\s/@]{0,256}@[^\s/@]/u;
const KEYED_PAIR = /(?<![a-z0-9_.-])([a-z][a-z0-9_.-]{0,63})\s{0,8}[=:]/giu;
function isDisplaySensitiveKey(key: string): boolean {
return SHORT_ACCOUNT_KEYS.has(key.toLowerCase()) || isSensitiveKey(key);