Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 12 additions & 18 deletions addons/addon-web-links/src/WebLinkProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion addons/addon-web-links/src/WebLinksAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions addons/addon-web-links/test/WebLinksAddon.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ describe('WebLinksAddon', () => {
await evalLinkStateData('http://test:[email protected]/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:[email protected]: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:[email protected]:80/staysUpper`);
});
});

async function testHostName(hostname: string): Promise<void> {
Expand Down