mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
feat(next/image): support new URL() for images.remotePatterns (#77692)
Configuring `remotePatterns` can be a hassle because you have to define
each of the parts. And if you forget a part, its a wildcard meaning it
will match anything. This is usually not desirable since most remote
images don't allow query strings.
This PR adds support for using an array of `URL` objects when defining
`images.remotePatterns`.
```js
module.exports = {
images: {
remotePatterns: [
new URL('https://res.cloudinary.com/my-account/**'),
new URL('https://s3.my-account.*.amazonaws.com/**'),
],
},
}
```
Note that wildcards must be explicit and anything missing is assumed to
no longer match.
This commit is contained in:
@@ -520,6 +520,16 @@ module.exports = {
|
||||
|
||||
To protect your application from malicious users, configuration is required in order to use external images. This ensures that only external images from your account can be served from the Next.js Image Optimization API. These external images can be configured with the `remotePatterns` property in your `next.config.js` file, as shown below:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
images: {
|
||||
remotePatterns: [new URL('https://example.com/account123/**')],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Versions of Next.js prior to 15.3.0 can configure `remotePatterns` using the object:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
images: {
|
||||
@@ -1106,6 +1116,7 @@ This `next/image` component uses browser native [lazy loading](https://caniuse.c
|
||||
|
||||
| Version | Changes |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `v15.3.0` | `remotePatterns` added support for array of `URL` objects. |
|
||||
| `v15.0.0` | `contentDispositionType` configuration default changed to `attachment`. |
|
||||
| `v14.2.23` | `qualities` configuration added. |
|
||||
| `v14.2.15` | `decoding` prop added and `localPatterns` configuration added. |
|
||||
|
||||
@@ -10,6 +10,21 @@ One of your pages that leverages the `next/image` component, passed a `src` valu
|
||||
|
||||
Add the protocol, hostname, port, and pathname to the `images.remotePatterns` config in `next.config.js`:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
images: {
|
||||
remotePatterns: [new URL('https://assets.example.com/account123/**')],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing older versions of Next.js
|
||||
|
||||
<details>
|
||||
<summary>Using Next.js prior to 15.3.0?</summary>
|
||||
|
||||
Older versions of Next.js can configure `images.remotePatterns` using the object:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
images: {
|
||||
@@ -26,7 +41,7 @@ module.exports = {
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing older versions of Next.js
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Using Next.js prior to 12.3.0?</summary>
|
||||
|
||||
@@ -668,5 +668,6 @@
|
||||
"667": "receiveExpiredTags is deprecated, and not expected to be called.",
|
||||
"668": "Internal Next.js error: Router action dispatched before initialization.",
|
||||
"669": "Invariant: --turbopack is set but the build used Webpack",
|
||||
"670": "Invariant: --turbopack is not set but the build used Turbopack. Add --turbopack to \"next start\"."
|
||||
"670": "Invariant: --turbopack is not set but the build used Turbopack. Add --turbopack to \"next start\".",
|
||||
"671": "Specified images.remotePatterns must have protocol \"http\" or \"https\" received \"%s\"."
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ async function writeImagesManifest(
|
||||
// By default, remotePatterns will allow no remote images ([])
|
||||
images.remotePatterns = (config?.images?.remotePatterns || []).map((p) => ({
|
||||
// Modifying the manifest should also modify matchRemotePattern()
|
||||
protocol: p.protocol,
|
||||
protocol: p.protocol?.replace(/:$/, '') as 'http' | 'https' | undefined,
|
||||
hostname: makeRe(p.hostname).source,
|
||||
port: p.port,
|
||||
pathname: makeRe(p.pathname ?? '**', { dot: true }).source,
|
||||
|
||||
@@ -525,13 +525,16 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
|
||||
.optional(),
|
||||
remotePatterns: z
|
||||
.array(
|
||||
z.strictObject({
|
||||
hostname: z.string(),
|
||||
pathname: z.string().optional(),
|
||||
port: z.string().max(5).optional(),
|
||||
protocol: z.enum(['http', 'https']).optional(),
|
||||
search: z.string().optional(),
|
||||
})
|
||||
z.union([
|
||||
z.instanceof(URL),
|
||||
z.strictObject({
|
||||
hostname: z.string(),
|
||||
pathname: z.string().optional(),
|
||||
port: z.string().max(5).optional(),
|
||||
protocol: z.enum(['http', 'https']).optional(),
|
||||
search: z.string().optional(),
|
||||
}),
|
||||
])
|
||||
)
|
||||
.max(50)
|
||||
.optional(),
|
||||
|
||||
@@ -364,6 +364,27 @@ function assignDefaults(
|
||||
)
|
||||
}
|
||||
|
||||
// We must convert URL to RemotePattern since URL has a colon in the protocol
|
||||
// and also has additional properties we want to filter out. Also, new URL()
|
||||
// accepts any protocol so we need manual validation here.
|
||||
images.remotePatterns = images.remotePatterns.map(
|
||||
({ protocol, hostname, port, pathname, search }) => {
|
||||
const proto = protocol?.replace(/:$/, '')
|
||||
if (!['http', 'https', undefined].includes(proto)) {
|
||||
throw new Error(
|
||||
`Specified images.remotePatterns must have protocol "http" or "https" received "${proto}".`
|
||||
)
|
||||
}
|
||||
return {
|
||||
protocol: proto as 'http' | 'https' | undefined,
|
||||
hostname,
|
||||
port,
|
||||
pathname,
|
||||
search,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// static images are automatically prefixed with assetPrefix
|
||||
// so we need to ensure _next/image allows downloading from
|
||||
// this resource
|
||||
|
||||
@@ -113,7 +113,7 @@ export type ImageConfigComplete = {
|
||||
contentDispositionType: 'inline' | 'attachment'
|
||||
|
||||
/** @see [Remote Patterns](https://nextjs.org/docs/api-reference/next/image#remotepatterns) */
|
||||
remotePatterns: RemotePattern[]
|
||||
remotePatterns: Array<URL | RemotePattern>
|
||||
|
||||
/** @see [Remote Patterns](https://nextjs.org/docs/api-reference/next/image#localPatterns) */
|
||||
localPatterns: LocalPattern[] | undefined
|
||||
|
||||
@@ -2,10 +2,12 @@ import type { RemotePattern } from './image-config'
|
||||
import { makeRe } from 'next/dist/compiled/picomatch'
|
||||
|
||||
// Modifying this function should also modify writeImagesManifest()
|
||||
export function matchRemotePattern(pattern: RemotePattern, url: URL): boolean {
|
||||
export function matchRemotePattern(
|
||||
pattern: RemotePattern | URL,
|
||||
url: URL
|
||||
): boolean {
|
||||
if (pattern.protocol !== undefined) {
|
||||
const actualProto = url.protocol.slice(0, -1)
|
||||
if (pattern.protocol !== actualProto) {
|
||||
if (pattern.protocol.replace(/:$/, '') !== url.protocol.replace(/:$/, '')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -41,7 +43,7 @@ export function matchRemotePattern(pattern: RemotePattern, url: URL): boolean {
|
||||
|
||||
export function hasRemoteMatch(
|
||||
domains: string[],
|
||||
remotePatterns: RemotePattern[],
|
||||
remotePatterns: Array<RemotePattern | URL>,
|
||||
url: URL
|
||||
): boolean {
|
||||
return (
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
images: {
|
||||
remotePatterns: [{ hostname: 'image-optimization-test.vercel.app' }],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
images: {
|
||||
remotePatterns: [new URL('https://image-optimization-test.vercel.app/**')],
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
@@ -567,6 +567,27 @@ describe('Image Optimizer', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when images.remotePatterns URL has invalid protocol', async () => {
|
||||
await nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
`{ images: { remotePatterns: [new URL('file://example.com/**')] } }`
|
||||
)
|
||||
let stderr = ''
|
||||
|
||||
app = await launchApp(appDir, await findPort(), {
|
||||
onStderr(msg) {
|
||||
stderr += msg || ''
|
||||
},
|
||||
})
|
||||
await waitFor(1000)
|
||||
await killApp(app).catch(() => {})
|
||||
await nextConfig.restore()
|
||||
|
||||
expect(stderr).toContain(
|
||||
`Specified images.remotePatterns must have protocol "http" or "https" received "file"`
|
||||
)
|
||||
})
|
||||
|
||||
it('should error when images.contentDispositionType is not valid', async () => {
|
||||
await nextConfig.replace(
|
||||
'{ /* replaceme */ }',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
images: {
|
||||
remotePatterns: [{ hostname: 'image-optimization-test.vercel.app' }],
|
||||
remotePatterns: [new URL('https://image-optimization-test.vercel.app/**')],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import {
|
||||
findPort,
|
||||
getImagesManifest,
|
||||
killApp,
|
||||
launchApp,
|
||||
nextBuild,
|
||||
@@ -17,7 +18,7 @@ let appPort
|
||||
let app
|
||||
let browser
|
||||
|
||||
function runTests() {
|
||||
function runTests(mode: 'server' | 'dev') {
|
||||
it('should load static unicode image', async () => {
|
||||
const src = await browser.elementById('static').getAttribute('src')
|
||||
expect(src).toMatch(
|
||||
@@ -65,6 +66,45 @@ function runTests() {
|
||||
const res = await fetch(fullSrc)
|
||||
expect(res.status).toBe(200)
|
||||
})
|
||||
if (mode === 'server') {
|
||||
it('should build correct images-manifest.json', async () => {
|
||||
const manifest = getImagesManifest(appDir)
|
||||
expect(manifest).toEqual({
|
||||
version: 1,
|
||||
images: {
|
||||
contentDispositionType: 'attachment',
|
||||
contentSecurityPolicy:
|
||||
"script-src 'none'; frame-src 'none'; sandbox;",
|
||||
dangerouslyAllowSVG: false,
|
||||
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
|
||||
disableStaticImages: false,
|
||||
domains: [],
|
||||
formats: ['image/webp'],
|
||||
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
||||
loader: 'default',
|
||||
loaderFile: '',
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname:
|
||||
'^(?:^(?:image\\-optimization\\-test\\.vercel\\.app)$)$',
|
||||
port: '',
|
||||
pathname:
|
||||
'^(?:\\/(?!\\.{1,2}(?:\\/|$))(?:(?:(?!(?:^|\\/)\\.{1,2}(?:\\/|$)).)*?))$',
|
||||
search: '',
|
||||
},
|
||||
],
|
||||
minimumCacheTTL: 60,
|
||||
path: '/_next/image',
|
||||
sizes: [
|
||||
640, 750, 828, 1080, 1200, 1920, 2048, 3840, 16, 32, 48, 64, 96,
|
||||
128, 256, 384,
|
||||
],
|
||||
unoptimized: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('Image Component Unicode Image URL', () => {
|
||||
@@ -82,7 +122,7 @@ describe('Image Component Unicode Image URL', () => {
|
||||
browser.close()
|
||||
}
|
||||
})
|
||||
runTests()
|
||||
runTests('dev')
|
||||
}
|
||||
)
|
||||
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
|
||||
@@ -100,7 +140,7 @@ describe('Image Component Unicode Image URL', () => {
|
||||
browser.close()
|
||||
}
|
||||
})
|
||||
runTests()
|
||||
runTests('server')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/* eslint-env jest */
|
||||
import { matchRemotePattern as m } from 'next/dist/shared/lib/match-remote-pattern'
|
||||
|
||||
describe('matchRemotePattern with URL', () => {
|
||||
it('should match literal protocol, hostname, no port, no search', () => {
|
||||
const p = new URL('https://example.com/**')
|
||||
expect(m(p, new URL('https://example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com:81/path/to/file'))).toBe(false)
|
||||
})
|
||||
|
||||
it('should match literal protocol, hostname, port 42', () => {
|
||||
const p = new URL('https://example.com:42/**')
|
||||
expect(m(p, new URL('https://example.com:42'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com.uk:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://com:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(false)
|
||||
})
|
||||
|
||||
it('should match literal protocol, hostname, port, pathname', () => {
|
||||
const p = new URL('https://example.com:42/path/to/file')
|
||||
expect(m(p, new URL('https://example.com:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(false)
|
||||
})
|
||||
|
||||
it('should match literal protocol, hostname, port, pathname, search', () => {
|
||||
const p = new URL(
|
||||
'https://example.com:42/path/to/file?q=1&a=two&s=!@$^&-_+/()[]{};:~'
|
||||
)
|
||||
expect(m(p, new URL('https://example.com:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com:42'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com:42/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('http://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('ftp://example.com/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:81/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file?q=1'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com:42/path/to/file?q=1&a=two'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(
|
||||
m(p, new URL('https://example.com:42/path/to/file?q=1&a=two&s'))
|
||||
).toBe(false)
|
||||
expect(
|
||||
m(p, new URL('https://example.com:42/path/to/file?q=1&a=two&s='))
|
||||
).toBe(false)
|
||||
expect(
|
||||
m(p, new URL('https://example.com:42/path/to/file?q=1&a=two&s=!@'))
|
||||
).toBe(false)
|
||||
expect(
|
||||
m(
|
||||
p,
|
||||
new URL(
|
||||
'https://example.com:42/path/to/file?q=1&a=two&s=!@$^&-_+/()[]{};:~'
|
||||
)
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
m(
|
||||
p,
|
||||
new URL(
|
||||
'https://example.com:42/path/to/file?q=1&s=!@$^&-_+/()[]{};:~&a=two'
|
||||
)
|
||||
)
|
||||
).toBe(false)
|
||||
expect(
|
||||
m(
|
||||
p,
|
||||
new URL(
|
||||
'https://example.com:42/path/to/file?a=two&q=1&s=!@$^&-_+/()[]{};:~'
|
||||
)
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('should match hostname pattern with single asterisk by itself', () => {
|
||||
const p = new URL('https://avatars.*.example.com')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(false)
|
||||
})
|
||||
|
||||
it('should match hostname pattern with single asterisk at beginning', () => {
|
||||
const p = new URL('https://avatars.*1.example.com')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo2.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.iad2.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.1.example.com'))).toBe(true)
|
||||
})
|
||||
|
||||
it('should match hostname pattern with single asterisk in middle', () => {
|
||||
const p = new URL('https://avatars.*a*.example.com')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo2.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.iad2.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://avatars.a.example.com'))).toBe(true)
|
||||
})
|
||||
|
||||
it('should match hostname pattern with single asterisk at end', () => {
|
||||
const p = new URL('https://avatars.ia*.example.com')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.sfo2.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.iad2.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://avatars.ia.example.com'))).toBe(true)
|
||||
})
|
||||
|
||||
it('should match hostname pattern with double asterisk', () => {
|
||||
const p = new URL('https://**.example.com')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://deep.sub.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://avatars.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://avatars.sfo1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://avatars.iad1.example.com'))).toBe(true)
|
||||
expect(m(p, new URL('https://more.avatars.iad1.example.com'))).toBe(true)
|
||||
})
|
||||
|
||||
it('should match pathname pattern with single asterisk by itself', () => {
|
||||
const p = new URL('https://example.com/act123/*/pic.jpg')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr6/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/team/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act456/team/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/.a/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/team/usr4/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
|
||||
})
|
||||
|
||||
it('should match pathname pattern with single asterisk at the beginning', () => {
|
||||
const p = new URL('https://example.com/act123/*4/pic.jpg')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/team4/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act456/team5/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/4/pic.jpg'))).toBe(true)
|
||||
})
|
||||
|
||||
it('should match pathname pattern with single asterisk in the middle', () => {
|
||||
const p = new URL('https://example.com/act123/*sr*/pic.jpg')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/.sr6/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/team4/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(m(p, new URL('https://example.com/act123/team5/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/sr/pic.jpg'))).toBe(true)
|
||||
})
|
||||
|
||||
it('should match pathname pattern with single asterisk at the end', () => {
|
||||
const p = new URL('https://example.com/act123/usr*/pic.jpg')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/team4/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(m(p, new URL('https://example.com/act456/team5/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com/act123/usr6/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('should match pathname pattern with double asterisk', () => {
|
||||
const p = new URL('https://example.com/act123/**')
|
||||
expect(m(p, new URL('https://com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com.uk'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/act123'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/picsjpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr4/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr5/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/usr6/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/team/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/.a/pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act123/team/.pic.jpg'))).toBe(true)
|
||||
expect(m(p, new URL('https://example.com/act456/team/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://example.com/team/pic.jpg'))).toBe(false)
|
||||
expect(m(p, new URL('https://sub.example.com/act123/team/pic.jpg'))).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user