diff --git a/addons/addon-web-links/src/WebLinkProvider.ts b/addons/addon-web-links/src/WebLinkProvider.ts index 713f9c2389..66691f44c8 100644 --- a/addons/addon-web-links/src/WebLinkProvider.ts +++ b/addons/addon-web-links/src/WebLinkProvider.ts @@ -41,16 +41,18 @@ export class WebLinkProvider implements ILinkProvider { } } -function baseUrlString(url: URL): string { - if (url.password && url.username) { - return `${url.protocol}//${url.username}:${url.password}@${url.host}`; +function isUrl(urlString: string): boolean { + try { + const url = new URL(urlString); + const parsedBase = url.password && url.username + ? `${url.protocol}//${url.username}:${url.password}@${url.host}` + : url.username + ? `${url.protocol}//${url.username}@${url.host}` + : `${url.protocol}//${url.host}`; + return urlString.toLocaleLowerCase().startsWith(parsedBase.toLocaleLowerCase()); + } catch (e) { + return false; } - - if (url.username) { - return `${url.protocol}//${url.username}@${url.host}`; - } - - return `${url.protocol}//${url.host}`; } export class LinkComputer { @@ -67,15 +69,7 @@ export class LinkComputer { const text = match[0]; // check via URL if the matched text would form a proper url - // NOTE: This outsources the ugly url parsing to the browser. - // we check if the provided string resembles the URL-parsed one - // up to the end of the domain name (ignoring path and params) - try { - const url = new URL(text); - if (!text.startsWith(baseUrlString(url))) { - continue; - } - } catch (e) { + if (!isUrl(text)) { continue; } diff --git a/addons/addon-web-links/src/WebLinksAddon.ts b/addons/addon-web-links/src/WebLinksAddon.ts index 8902d8e0ad..b3f0548cc1 100644 --- a/addons/addon-web-links/src/WebLinksAddon.ts +++ b/addons/addon-web-links/src/WebLinksAddon.ts @@ -18,7 +18,7 @@ import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider'; // - final interpunction like ,.!? // - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls) // - unsafe chars from rfc1738: {}|\^~[]` -const strictUrlRegex = /https?:[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/; +const strictUrlRegex = /(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/; function handleLink(event: MouseEvent, uri: string): void { diff --git a/addons/addon-web-links/test/WebLinksAddon.api.ts b/addons/addon-web-links/test/WebLinksAddon.api.ts index 5b05ce2e71..99c11600db 100644 --- a/addons/addon-web-links/test/WebLinksAddon.api.ts +++ b/addons/addon-web-links/test/WebLinksAddon.api.ts @@ -123,6 +123,21 @@ describe('WebLinksAddon', () => { await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } }); }); }); + + // issue #4964 + it('uppercase in protocol and host, default ports', async () => { + const data = ` HTTP://EXAMPLE.COM \\r\\n` + + ` HTTPS://Example.com \\r\\n` + + ` HTTP://Example.com:80 \\r\\n` + + ` HTTP://Example.com:80/staysUpper \\r\\n` + + ` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`; + await writeSync(page, data); + await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`); + await pollForLinkAtCell(3, 1, `HTTPS://Example.com`); + await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`); + await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`); + await pollForLinkAtCell(3, 4, `HTTP://Ab:xY@abc.com:80/staysUpper`); + }); }); async function testHostName(hostname: string): Promise {