From ffcb43927505d1e441088fac536dc56af0349283 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 27 Feb 2025 08:43:26 -0800 Subject: [PATCH] map tokens --- .../src/utils/isomorphic/urlMatch.ts | 40 ++++++++++++++----- tests/page/interception.spec.ts | 12 +++++- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/urlMatch.ts b/packages/playwright-core/src/utils/isomorphic/urlMatch.ts index a519314f37..55fc6c7d72 100644 --- a/packages/playwright-core/src/utils/isomorphic/urlMatch.ts +++ b/packages/playwright-core/src/utils/isomorphic/urlMatch.ts @@ -101,18 +101,38 @@ export function urlMatches(baseURL: string | undefined, urlString: string, match // Allow http(s) baseURL to match ws(s) urls. if (baseURL && /^https?:\/\//.test(baseURL) && /^wss?:\/\//.test(urlString)) baseURL = baseURL.replace(/^http/, 'ws'); - // Resolve match relative to baseURL only if baseURL is set and match is not an absolute URL. - // Otherwise, leave it unchanged to prevent the URL constructor from interpreting glob symbols - // like ?, {, and }, which could alter the pattern. - if (baseURL && !parseURL(match)) - match = constructURLBasedOnBaseURL(baseURL, match); + + const tokenMap = new Map(); + function mapToken(original: string, replacement: string) { + tokenMap.set(replacement, original); + return replacement; + } + // Glob symbols may be escaped in the URL and some of them such as ? affect resolution, + // so we replace them with safe components first. + const relativePath = match.split('/').map((token, index) => { + if (token === '.' || token === '..' || token === '') + return token; + // Handle special case of http*:// + if (index === 0 && token.endsWith(':')) { + return mapToken(token, 'http:'); + } else { + const questionIndex = token.indexOf('?'); + if (questionIndex === -1) + return mapToken(token, `$_${index}_$`); + if (questionIndex === 0) + return mapToken(token, `?$_${index}_$`); + const newPrefix = mapToken(token.substring(0, questionIndex), `$_${index}_$`); + const newSuffix = mapToken(token.substring(questionIndex), `?$_${index}_$`); + return newPrefix + newSuffix; + } + }).join('/'); + let resolved = constructURLBasedOnBaseURL(baseURL, relativePath); + for (const [token, original] of tokenMap) + resolved = resolved.replace(token, original); + match = resolved; } - if (isString(match)) { - const tryWithoutTrailingSlash = urlString.endsWith('/') && !match.endsWith('/'); + if (isString(match)) match = globToRegex(match); - if (tryWithoutTrailingSlash && match.test(urlString.substring(0, urlString.length - 1))) - return true; - } if (isRegExp(match)) return match.test(urlString); const url = parseURL(urlString); diff --git a/tests/page/interception.spec.ts b/tests/page/interception.spec.ts index 891adeaff0..c93d7c3233 100644 --- a/tests/page/interception.spec.ts +++ b/tests/page/interception.spec.ts @@ -105,13 +105,23 @@ it('should work with glob', async () => { expect(globToRegex('\\[')).toEqual(/^\[$/); expect(globToRegex('[a-z]')).toEqual(/^[a-z]$/); expect(globToRegex('$^+.\\*()|\\?\\{\\}\\[\\]')).toEqual(/^\$\^\+\.\*\(\)\|\?\{\}\[\]$/); + + expect(urlMatches(undefined, 'http://playwright.dev/', 'http://playwright.dev')).toBeTruthy(); + expect(urlMatches(undefined, 'http://playwright.dev/?a=b', 'http://playwright.dev?a=b')).toBeTruthy(); + expect(urlMatches(undefined, 'http://playwright.dev/', 'h*://playwright.dev')).toBeTruthy(); + expect(urlMatches(undefined, 'http://api.playwright.dev/?x=y', 'http://*.playwright.dev?x=y')).toBeTruthy(); + expect(urlMatches(undefined, 'http://playwright.dev/foo/bar', '**/foo/**')).toBeTruthy(); + expect(urlMatches('http://playwright.dev/foo/', 'http://playwright.dev/foo/bar?x=y', './bar?x=y')).toBeTruthy(); + + // This is not supported, we treat ? as a query separator. + expect(urlMatches(undefined, 'http://playwright.dev/', 'http://playwright.?ev')).toBeFalsy(); }); it('should intercept by glob', async function({ page, server, isAndroid }) { it.skip(isAndroid); await page.goto(server.EMPTY_PAGE); - await page.route('http://localhos*?/?oo', async route => { + await page.route('http://localhos**/?oo', async route => { await route.fulfill({ status: 200, body: 'intercepted',