### What?
The `pasteURL` feature for Upload fields has been updated to support
both **client-side** and **server-side** URL fetching. Previously, users
could only paste URLs from the same domain as their Payload instance
(internal) or public domains, which led to **CORS** errors when trying
to fetch files from external URLs.
Now, users can choose between **client-side fetching** (default) and
**server-side fetching** using the new `pasteURL` option in the Upload
collection config.
### How?
- By default, Payload will attempt to fetch the file client-side
directly in the browser.
- To enable server-side fetching, you can configure the new `pasteURL`
option with an `allowList` of trusted domains.
- The new `/api/:collectionSlug/paste-url` endpoint is used to fetch
files server-side and stream them back to the browser.
#### Example
```
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
// pasteURL: false, // Can now disable the pasteURL option entirely by passing "false".
pasteURL: {
allowList: [
{
hostname: 'payloadcms.com', // required
pathname: '',
port: '',
protocol: 'https', // defaults to https - options: "https" | "http"
search: ''
},
{
hostname: 'example.com',
pathname: '/images/*',
},
],
},
},
}
```
### Why
This update provides more flexibility for users to paste URLs into
Upload fields without running into **CORS errors** and allows Payload to
securely fetch files from trusted domains.
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
import type { AllowList } from 'payload'
|
|
|
|
export const isURLAllowed = (url: string, allowList: AllowList): boolean => {
|
|
try {
|
|
const parsedUrl = new URL(url)
|
|
|
|
return allowList.some((allowItem) => {
|
|
return Object.entries(allowItem).every(([key, value]) => {
|
|
// Skip undefined or null values
|
|
if (!value) {
|
|
return true
|
|
}
|
|
// Compare protocol with colon
|
|
if (key === 'protocol') {
|
|
return typeof value === 'string' && parsedUrl.protocol === `${value}:`
|
|
}
|
|
|
|
if (key === 'pathname') {
|
|
// Convert wildcards to a regex
|
|
const regexPattern = value
|
|
.replace(/\*\*/g, '.*') // Match any path
|
|
.replace(/\*/g, '[^/]*') // Match any part of a path segment
|
|
const regex = new RegExp(`^${regexPattern}$`)
|
|
return regex.test(parsedUrl.pathname)
|
|
}
|
|
|
|
// Default comparison for all other properties (hostname, port, search)
|
|
return parsedUrl[key as keyof URL] === value
|
|
})
|
|
})
|
|
} catch {
|
|
return false // If the URL is invalid, deny by default
|
|
}
|
|
}
|