-
-
Save dexit/00cef98dee353772e219df40e8abadea to your computer and use it in GitHub Desktop.
CORS Anywhere with Cloudflare Workers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Configuration | |
const whitelistOrigins = [".*"]; // regexp for whitelisted origins | |
const blacklistUrls = []; // regexp for blacklisted urls | |
// Helper function to check if a URI or origin is whitelisted | |
function isListedInWhitelist(uri, listing) { | |
if (!uri) return true; // Allow null origins by default | |
return listing.some(pattern => new RegExp(pattern).test(uri)); | |
} | |
// Helper function to setup CORS headers | |
function setupCORSHeaders(headers, request, isPreflightRequest) { | |
const origin = request.headers.get("Origin"); | |
headers.set("Access-Control-Allow-Origin", origin || "*"); | |
if (isPreflightRequest) { | |
const requestMethod = request.headers.get("Access-Control-Request-Method"); | |
const requestHeaders = request.headers.get("Access-Control-Request-Headers"); | |
headers.set("Access-Control-Allow-Methods", requestMethod || "GET, POST, OPTIONS"); | |
if (requestHeaders) { | |
headers.set("Access-Control-Allow-Headers", requestHeaders); | |
} | |
headers.set("Access-Control-Max-Age", "86400"); // 24 hours | |
headers.delete("X-Content-Type-Options"); | |
} | |
return headers; | |
} | |
// Helper function to filter sensitive headers | |
function filterHeaders(headers) { | |
const filteredHeaders = new Headers(); | |
for (const [key, value] of headers.entries()) { | |
if (!/^(origin|referer|cf-|x-forw|x-cors-headers)/i.test(key)) { | |
filteredHeaders.append(key, value); | |
} | |
} | |
return filteredHeaders; | |
} | |
addEventListener("fetch", event => { | |
event.respondWith(handleRequest(event)); | |
}); | |
async function handleRequest(event) { | |
const request = event.request; | |
const url = new URL(request.url); | |
const isPreflightRequest = request.method === "OPTIONS"; | |
// Parse the target URL from the query string | |
const targetUrl = url.search.startsWith("?") ? | |
decodeURIComponent(decodeURIComponent(url.search.substr(1))) : null; | |
const originHeader = request.headers.get("Origin"); | |
// Show information page if no target URL is provided | |
if (!url.search.startsWith("?")) { | |
const responseHeaders = new Headers(); | |
setupCORSHeaders(responseHeaders, request, isPreflightRequest); | |
const cfData = request.cf || {}; | |
const info = [ | |
"CLOUDFLARE-CORS-ANYWHERE", | |
"", | |
"Usage:", | |
`${url.origin}/?https://api.example.com/data`, | |
"", | |
"Request Information:", | |
`Origin: ${originHeader || 'None'}`, | |
`IP: ${request.headers.get("CF-Connecting-IP") || 'Unknown'}`, | |
`Country: ${cfData.country || 'Unknown'}`, | |
`Datacenter: ${cfData.colo || 'Unknown'}`, | |
"", | |
"Rate Limits:", | |
"- 100,000 requests/day", | |
"- 1,000 requests/10 minutes" | |
].join("\n"); | |
return new Response(info, { | |
status: 200, | |
headers: responseHeaders | |
}); | |
} | |
// Check blacklist only - we're allowing all origins | |
if (blacklistUrls.length > 0 && isListedInWhitelist(targetUrl, blacklistUrls)) { | |
return new Response( | |
`<!DOCTYPE html> | |
<html> | |
<body> | |
<h1>CORS Proxy Access Denied</h1> | |
<p>This request is not allowed under the current security policy.</p> | |
<p><a href="https://github.com/Zibri/cloudflare-cors-anywhere">View Documentation</a></p> | |
</body> | |
</html>`, | |
{ | |
status: 403, | |
headers: { | |
"Content-Type": "text/html", | |
"X-Content-Type-Options": "nosniff" | |
} | |
} | |
); | |
} | |
// Handle the actual proxy request | |
try { | |
// Parse and apply any custom headers | |
let customHeaders = {}; | |
const corsHeaders = request.headers.get("x-cors-headers"); | |
if (corsHeaders) { | |
try { | |
customHeaders = JSON.parse(corsHeaders); | |
} catch (e) { | |
console.error("Failed to parse x-cors-headers:", e); | |
} | |
} | |
// Create filtered headers for the proxied request | |
const filteredHeaders = filterHeaders(request.headers); | |
Object.entries(customHeaders).forEach(([key, value]) => { | |
filteredHeaders.set(key, value); | |
}); | |
// Create and send the proxied request | |
const proxyRequest = new Request(targetUrl, { | |
method: isPreflightRequest ? "GET" : request.method, | |
headers: filteredHeaders, | |
body: isPreflightRequest ? null : request.body, | |
redirect: "follow" | |
}); | |
const response = await fetch(proxyRequest); | |
// Prepare the response headers | |
const responseHeaders = new Headers(response.headers); | |
setupCORSHeaders(responseHeaders, request, isPreflightRequest); | |
// Add exposed headers | |
const exposedHeaders = [...response.headers.keys()]; | |
exposedHeaders.push("cors-received-headers"); | |
responseHeaders.set("Access-Control-Expose-Headers", exposedHeaders.join(",")); | |
// Add received headers as JSON | |
const receivedHeaders = Object.fromEntries([...response.headers.entries()]); | |
responseHeaders.set("cors-received-headers", JSON.stringify(receivedHeaders)); | |
// Return the final response | |
return new Response( | |
isPreflightRequest ? null : await response.arrayBuffer(), | |
{ | |
status: isPreflightRequest ? 200 : response.status, | |
statusText: isPreflightRequest ? "OK" : response.statusText, | |
headers: responseHeaders | |
} | |
); | |
} catch (error) { | |
return new Response(`Proxy Error: ${error.message}`, { | |
status: 500, | |
headers: { | |
"Content-Type": "text/plain", | |
"Access-Control-Allow-Origin": originHeader || "*" | |
} | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment