37 lines
1.3 KiB
TypeScript
37 lines
1.3 KiB
TypeScript
/**
|
|||
|
|
* [Gitea #326] A page's og:url is chosen by the page itself, so a phishing
|
||
|
|
* link could serve `og:url = youtube.com/watch?v=…` and render as a playable
|
||
|
|
* YouTube card linking to the attacker. Only trust an og:url on the same
|
||
|
|
* provider as the link actually posted (including the provider's own
|
||
|
|
* short-link domains).
|
||
|
|
*/
|
||
|
|
const PROVIDER_ALIASES: Record<string, string> = {
|
||
|
|
'youtu.be': 'youtube.com',
|
||
|
|
'youtube-nocookie.com': 'youtube.com',
|
||
|
|
'spotify.link': 'spotify.com',
|
||
|
|
'redd.it': 'reddit.com',
|
||
|
|
'x.com': 'twitter.com',
|
||
|
|
'twitch.tv': 'twitch.tv',
|
||
|
|
'apple.co': 'apple.com',
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Pure: the provider a host belongs to (last two labels, aliases folded). Hosts
|
||
|
|
* under two-part public suffixes (`*.co.uk`) share a value, which is harmless
|
||
|
|
* here: no embeddable provider lives under one.
|
||
|
|
*/
|
||
|
|
export const providerOf = (host: string): string => {
|
||
|
|
const parts = host.toLowerCase().replace(/\.$/, '').split('.');
|
||
|
|
const base = parts.slice(-2).join('.');
|
||
|
|
return PROVIDER_ALIASES[base] ?? base;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Pure: whether `ogUrl` is on the same provider as the posted `url`. */
|
||
|
|
export function sameProvider(url: string, ogUrl: string): boolean {
|
||
|
|
try {
|
||
|
|
return providerOf(new URL(url).hostname) === providerOf(new URL(ogUrl).hostname);
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|