Files
payloadcms/packages/ui/src/utilities/isURLAllowed.ts
Patrik 38a06e7bd3 feat: adds support for both client-side and server-side remote URL uploads fetching (#10004)
### 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.
2025-01-17 09:16:29 -05:00

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