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;
}
|