summaryrefslogtreecommitdiff
path: root/packages/integrations/cloudflare/src/utils/assets.ts
blob: 21ba28690bfbf300271e69e2c307137fb24b9485 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { isRemotePath } from '@astrojs/internal-helpers/path';
import type { AstroConfig, ImageMetadata, RemotePattern } from 'astro';

export function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata {
	return typeof src === 'object';
}
export function matchHostname(url: URL, hostname?: string, allowWildcard?: boolean) {
	if (!hostname) {
		return true;
	}
	if (!allowWildcard || !hostname.startsWith('*')) {
		return hostname === url.hostname;
	}
	if (hostname.startsWith('**.')) {
		const slicedHostname = hostname.slice(2); // ** length
		return slicedHostname !== url.hostname && url.hostname.endsWith(slicedHostname);
	}
	if (hostname.startsWith('*.')) {
		const slicedHostname = hostname.slice(1); // * length
		const additionalSubdomains = url.hostname
			.replace(slicedHostname, '')
			.split('.')
			.filter(Boolean);
		return additionalSubdomains.length === 1;
	}

	return false;
}
export function matchPort(url: URL, port?: string) {
	return !port || port === url.port;
}
export function matchProtocol(url: URL, protocol?: string) {
	return !protocol || protocol === url.protocol.slice(0, -1);
}
export function matchPathname(url: URL, pathname?: string, allowWildcard?: boolean) {
	if (!pathname) {
		return true;
	}
	if (!allowWildcard || !pathname.endsWith('*')) {
		return pathname === url.pathname;
	}
	if (pathname.endsWith('/**')) {
		const slicedPathname = pathname.slice(0, -2); // ** length
		return slicedPathname !== url.pathname && url.pathname.startsWith(slicedPathname);
	}
	if (pathname.endsWith('/*')) {
		const slicedPathname = pathname.slice(0, -1); // * length
		const additionalPathChunks = url.pathname
			.replace(slicedPathname, '')
			.split('/')
			.filter(Boolean);
		return additionalPathChunks.length === 1;
	}

	return false;
}
export function matchPattern(url: URL, remotePattern: RemotePattern) {
	return (
		matchProtocol(url, remotePattern.protocol) &&
		matchHostname(url, remotePattern.hostname, true) &&
		matchPort(url, remotePattern.port) &&
		matchPathname(url, remotePattern.pathname, true)
	);
}
export function isRemoteAllowed(
	src: string,
	{
		domains = [],
		remotePatterns = [],
	}: Partial<Pick<AstroConfig['image'], 'domains' | 'remotePatterns'>>,
): boolean {
	if (!isRemotePath(src)) return false;

	const url = new URL(src);
	return (
		domains.some((domain) => matchHostname(url, domain)) ||
		remotePatterns.some((remotePattern) => matchPattern(url, remotePattern))
	);
}
export function isString(path: unknown): path is string {
	return typeof path === 'string' || path instanceof String;
}