From 521da2bb7af032a58befb40903d3eb0715a272af Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 1 Jun 2026 14:30:43 +0800 Subject: [PATCH] Fix admin paste URL proxy routing --- .../eversolo-paste-url-fetch-provider.tsx | 62 ++++++++++++++++++- src/app/api/eversolo-paste-url/route.ts | 59 +++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/app/(payload)/admin/_components/eversolo-paste-url-fetch-provider.tsx b/src/app/(payload)/admin/_components/eversolo-paste-url-fetch-provider.tsx index 0763c65..dadc74e 100644 --- a/src/app/(payload)/admin/_components/eversolo-paste-url-fetch-provider.tsx +++ b/src/app/(payload)/admin/_components/eversolo-paste-url-fetch-provider.tsx @@ -8,7 +8,46 @@ const pasteFetchCollectDelay = 300 const pasteFetchIdleReleaseDelay = 250 const pasteFetchRetryDelays = [0, 300, 800] const pasteFetchConcurrency = 4 -const pasteFetchPatchVersion = 4 +const pasteFetchPatchVersion = 5 +const bypassPathPrefixes = ['/admin', '/api', '/_next'] +const assetPathPrefixes = ['/Attached/', '/Products/', '/Uploads/', '/controlapp/', '/files/', '/media/', '/videos/'] +const assetExtensions = new Set([ + '7z', + 'aac', + 'avi', + 'bmp', + 'css', + 'csv', + 'doc', + 'docx', + 'flac', + 'gif', + 'gz', + 'htm', + 'html', + 'ico', + 'jpeg', + 'jpg', + 'js', + 'json', + 'm4a', + 'mov', + 'mp3', + 'mp4', + 'pdf', + 'png', + 'rar', + 'svg', + 'tar', + 'txt', + 'wav', + 'webm', + 'webp', + 'xls', + 'xlsx', + 'xml', + 'zip', +]) type PatchedWindow = Window & { __eversoloPasteURLFetchPatchVersion?: number @@ -43,6 +82,23 @@ function getFetchMethod(input: RequestInfo | URL, init?: RequestInit) { return undefined } +function isBypassedURL(url: URL) { + if (url.origin === window.location.origin) return true + if (url.searchParams.has('_rsc')) return true + + return bypassPathPrefixes.some((prefix) => { + return url.pathname === prefix || url.pathname.startsWith(`${prefix}/`) + }) +} + +function isAssetURL(url: URL) { + if (assetPathPrefixes.some((prefix) => url.pathname.startsWith(prefix))) return true + + const extension = url.pathname.split('/').pop()?.split('.').pop()?.toLowerCase() + + return extension ? assetExtensions.has(extension) : false +} + function getProxiedPasteURL(input: RequestInfo | URL, init?: RequestInit) { const method = getFetchMethod(input, init) @@ -57,6 +113,10 @@ function getProxiedPasteURL(input: RequestInfo | URL, init?: RequestInit) { return null } + if (isBypassedURL(url) || !isAssetURL(url)) { + return null + } + return `${proxyPath}?src=${encodeURIComponent(url.href)}` } catch { return null diff --git a/src/app/api/eversolo-paste-url/route.ts b/src/app/api/eversolo-paste-url/route.ts index 8125233..2d614cc 100644 --- a/src/app/api/eversolo-paste-url/route.ts +++ b/src/app/api/eversolo-paste-url/route.ts @@ -6,14 +6,71 @@ export const runtime = 'nodejs' const retryDelays = [0, 300, 800, 1500] const fetchTimeoutMs = 10000 +const bypassPathPrefixes = ['/admin', '/api', '/_next'] +const assetPathPrefixes = ['/Attached/', '/Products/', '/Uploads/', '/controlapp/', '/files/', '/media/', '/videos/'] +const assetExtensions = new Set([ + '7z', + 'aac', + 'avi', + 'bmp', + 'css', + 'csv', + 'doc', + 'docx', + 'flac', + 'gif', + 'gz', + 'htm', + 'html', + 'ico', + 'jpeg', + 'jpg', + 'js', + 'json', + 'm4a', + 'mov', + 'mp3', + 'mp4', + 'pdf', + 'png', + 'rar', + 'svg', + 'tar', + 'txt', + 'wav', + 'webm', + 'webp', + 'xls', + 'xlsx', + 'xml', + 'zip', +]) + +function isBypassedURL(url: URL) { + if (url.searchParams.has('_rsc')) return true + + return bypassPathPrefixes.some((prefix) => { + return url.pathname === prefix || url.pathname.startsWith(`${prefix}/`) + }) +} + +function isAssetURL(url: URL) { + if (assetPathPrefixes.some((prefix) => url.pathname.startsWith(prefix))) return true + + const extension = url.pathname.split('/').pop()?.split('.').pop()?.toLowerCase() + + return extension ? assetExtensions.has(extension) : false +} function isAllowedPasteURL(value: string) { try { const url = new URL(value) - return eversoloPasteURLAllowList.some((allowItem) => { + const hostIsAllowed = eversoloPasteURLAllowList.some((allowItem) => { return url.protocol === `${allowItem.protocol}:` && url.hostname === allowItem.hostname }) + + return hostIsAllowed && !isBypassedURL(url) && isAssetURL(url) } catch { return false }