代码展示
/**
* 私有网关终极版 - Ultimate Version
* 1. 极简 UI(仅保留直接访问)
* 2. 静态资源强缓存拦截(大幅减少 Worker 请求消耗)
* 3. 终极修复:完美支持 YouTube 视频流加载、GitHub SPA 搜索
* 4. 终极修复:解决 Google/Cloudflare "异常流量" 防火墙拦截
*/
const CONFIG = {
USER: "admin",
PASS: "admin",
TITLE: "私有安全网关",
AUTH_COOKIE: "p_token_final",
AUTH_VALUE: "v_ultimate_safe"
};
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// --- 1. 拦截无效请求 ---
if (url.pathname === '/favicon.ico' || url.pathname === '/robots.txt') {
return new Response(null, { status: 204 });
}
const cookieHeader = request.headers.get("Cookie") || "";
// --- 2. 登录处理接口 ---
if (url.pathname === "/--login--" && request.method === "POST") {
try {
const body = await request.json();
if (body.u === CONFIG.USER && body.p === CONFIG.PASS) {
return new Response("OK", {
headers: {
"Set-Cookie": `${CONFIG.AUTH_COOKIE}=${CONFIG.AUTH_VALUE}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000`,
"Cache-Control": "no-store, no-cache, must-revalidate"
}
});
}
return new Response("Fail", { status: 401 });
} catch (e) {
return new Response("Error", { status: 400 });
}
}
// --- 3. 权限校验 ---
const isAuthed = cookieHeader.includes(`${CONFIG.AUTH_COOKIE}=${CONFIG.AUTH_VALUE}`);
if (!isAuthed) {
return new Response(getLoginUI(), {
headers: {
"Content-Type": "text/html;charset=UTF-8",
"Cache-Control": "no-store, no-cache, must-revalidate, proxy-revalidate"
}
});
}
// --- 4. 后台书签存储 ---
if (url.pathname === "/--links--" && request.method === "POST") {
const body = await request.text();
if (env.LINKS_KV) await env.LINKS_KV.put("user_links", body);
return new Response("Saved", { headers: { "Cache-Control": "no-store" } });
}
// --- 5. 首页直出渲染 ---
if (url.pathname === "/" && url.search === "") {
let linksData = "[]";
if (env.LINKS_KV) {
try { linksData = await env.LINKS_KV.get("user_links") || "[]"; } catch(e) {}
}
return new Response(getIndexUI(linksData), {
headers: { "Content-Type": "text/html;charset=UTF-8", "Cache-Control": "no-store, no-cache, must-revalidate" }
});
}
// --- 6. 代理路由逻辑 (修复 YouTube/GitHub SPA 相对路径丢失问题) ---
let targetStr = url.pathname.slice(1) + url.search;
let finalUrl;
if (targetStr.startsWith('http://') || targetStr.startsWith('https://')) {
finalUrl = targetStr;
} else {
const firstSegment = targetStr.split('/')[0].split('?')[0];
const isDomain = /^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?$/.test(firstSegment) ||
/^\d{1,3}(\.\d{1,3}){3}(:\d+)?$/.test(firstSegment) ||
/^localhost(:\d+)?$/.test(firstSegment);
if (isDomain) {
finalUrl = 'https://' + targetStr;
} else {
// 【核心修复 1】: 内部无缝重写 (Internal Rewrite) 替代 302 重定向
// 完美保留 POST 请求的数据包,彻底解决 YouTube 加载白屏和 GitHub 搜索 1003 报错
const referer = request.headers.get('Referer');
if (referer) {
try {
const refUrl = new URL(referer);
const match = refUrl.pathname.match(/^\/(https?:\/\/[^\/]+)/);
if (match) {
finalUrl = match[1] + '/' + targetStr;
}
} catch(e) {}
}
if (!finalUrl) {
return new Response("⚠️ 代理请求解析失败:缺少目标域名。请返回首页重新输入完整网址。", { status: 400 });
}
}
}
try {
const targetUrl = new URL(finalUrl);
const cleanHeaders = new Headers();
// 【核心修复 2】: 尊重原生 Sec-Fetch-* 头,不强制篡改,解决 Google 检测到异常流量导致拦截
const whitelist = ['user-agent', 'accept', 'accept-language', 'content-type', 'cookie', 'range', 'dnt', 'upgrade-insecure-requests'];
for (let [k, v] of request.headers.entries()) {
let key = k.toLowerCase();
// 放行白名单和所有合法的 Sec- 客户端原生指纹特征
if ((whitelist.includes(key) || key.startsWith('sec-')) && !key.startsWith('cf-')) {
if (key === 'cookie' && v) {
const filtered = v.split(';').filter(c => !c.trim().startsWith(CONFIG.AUTH_COOKIE)).join(';');
if (filtered) cleanHeaders.set(k, filtered);
} else {
cleanHeaders.set(k, v);
}
}
}
cleanHeaders.set("Origin", targetUrl.origin);
cleanHeaders.set("Referer", targetUrl.href);
// 透传真实客户端 IP,避免 Cloudflare 节点群体污染导致被 Google 限流
const clientIP = request.headers.get('cf-connecting-ip');
if (clientIP) cleanHeaders.set('X-Forwarded-For', clientIP);
const response = await fetch(targetUrl.href, {
method: request.method,
headers: cleanHeaders,
body: (request.method !== "GET" && request.method !== "HEAD") ? request.body : null,
redirect: "manual"
});
if ([301, 302, 303, 307, 308].includes(response.status)) {
const loc = response.headers.get("Location");
if (loc) {
const absLoc = new URL(loc, targetUrl.href).href;
return Response.redirect(`https://${url.host}/${absLoc}`, response.status);
}
}
let resHeaders = new Headers(response.headers);
const contentType = response.headers.get("content-type") || "";
resHeaders.set("Access-Control-Allow-Origin", "*");
resHeaders.delete("content-security-policy");
resHeaders.delete("content-security-policy-report-only");
resHeaders.delete("x-frame-options");
resHeaders.delete("cross-origin-embedder-policy");
resHeaders.delete("cross-origin-opener-policy");
if (typeof response.headers.getSetCookie === 'function') {
resHeaders.delete("set-cookie");
for (const c of response.headers.getSetCookie()) {
let rewritten = c.replace(/Domain=[^;]+/ig, '').replace(/SameSite=Strict/ig, 'SameSite=Lax');
resHeaders.append("set-cookie", rewritten);
}
}
if (request.method === "GET" && contentType.match(/(image|css|javascript|font)/i)) {
resHeaders.set("Cache-Control", "public, max-age=2592000");
}
if (contentType.includes("text/html")) {
return new HTMLRewriter()
.on("head", {
element(el) {
el.append(`
<script>
(function() {
const proxyBase = window.location.origin + "/";
const targetOrigin = "${targetUrl.origin}";
// 【核心修复 3】强行干掉 Service Worker,防止 YouTube 本地缓存脚本阻断网络
if (navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations().then(regs => regs.forEach(r => r.unregister()));
}
const wrap = (u) => {
if (!u || typeof u !== 'string' || u.startsWith('data:') || u.startsWith('javascript:') || u.startsWith('blob:') || u.startsWith('#')) return u;
try {
const abs = new URL(u, targetOrigin).href;
if (!abs.startsWith(proxyBase)) return proxyBase + abs;
} catch(e) {}
return u;
};
// 深度重写 Fetch 请求,捕获 YouTube 特有的 Request 对象发起逻辑
const origFetch = window.fetch;
window.fetch = async (input, init) => {
try {
if (input instanceof Request) {
return origFetch(new Request(wrap(input.url), input), init);
}
return origFetch(wrap(input.toString()), init);
} catch(e) {
return origFetch(input, init);
}
};
const _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function() {
if (arguments[1]) arguments[1] = wrap(arguments[1].toString());
return _open.apply(this, arguments);
};
})();
</script>
`, { html: true });
}
})
.on("a", new TagHandler("href", url.host, targetUrl))
.on("form", new TagHandler("action", url.host, targetUrl))
.on("link", new TagHandler("href", url.host, targetUrl))
.on("img", new TagHandler("src", url.host, targetUrl))
.on("script", new TagHandler("src", url.host, targetUrl))
.transform(new Response(response.body, { status: response.status, headers: resHeaders }));
}
return new Response(response.body, { status: response.status, headers: resHeaders });
} catch (e) {
return new Response("⚠️ 代理请求失败: " + e.message, { status: 500 });
}
}
};
class TagHandler {
constructor(attr, proxyHost, targetUrl) { this.attr = attr; this.proxyHost = proxyHost; this.targetUrl = targetUrl; }
element(el) {
let val = el.getAttribute(this.attr);
if (val && !val.startsWith("data:") && !val.startsWith("javascript:") && !val.startsWith("#")) {
try { el.setAttribute(this.attr, "https://" + this.proxyHost + "/" + new URL(val, this.targetUrl.href).href); } catch (e) {}
}
el.removeAttribute("integrity");
el.removeAttribute("nonce");
}
}
function getIndexUI(linksData) {
return `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>${CONFIG.TITLE}</title>
<style>
:root { --bg: #0f172a; --card: #1e293b; --text: #f8fafc; --accent: #38bdf8; --danger: #ef4444; }
body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; display: flex; flex-direction: column; align-items: center; min-height: 100vh; margin: 0; padding-top: 5vh; }
.container { width: 90%; max-width: 650px; text-align: center; }
.search-card { background: var(--card); padding: 2.5rem; border-radius: 1.5rem; box-shadow: 0 20px 50px rgba(0,0,0,0.3); margin-bottom: 2rem; }
h1 { font-size: 1.8rem; margin-bottom: 1.5rem; color: var(--accent); }
.input-box { width: 100%; padding: 14px 25px; border-radius: 50px; border: 1px solid #334155; background: #0f172a; color: #fff; font-size: 1rem; outline: none; box-sizing: border-box; margin-bottom: 1.5rem; }
.btn-grid { display: flex; justify-content: center; }
button { width: 100%; max-width: 250px; padding: 14px 10px; border-radius: 30px; border: none; cursor: pointer; font-weight: 700; font-size: 1rem; transition: 0.2s; background: var(--accent); color: #0f172a; }
button:hover { opacity: 0.9; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(56,189,248,0.3); }
.nav-section { width: 100%; text-align: left; }
.nav-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; padding: 0 10px; border-left: 4px solid var(--accent); }
.nav-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.nav-item { position: relative; background: var(--card); padding: 15px; border-radius: 12px; text-align: center; cursor: pointer; border: 1px solid transparent; transition: 0.3s; }
.nav-item:hover { border-color: var(--accent); background: #2d3a4f; }
.nav-item .delete-btn { position: absolute; top: 5px; right: 5px; color: var(--danger); font-size: 14px; width: 22px; height: 22px; display: none; background: rgba(0,0,0,0.5); border-radius: 50%; line-height: 20px; }
.nav-item:hover .delete-btn { display: block; }
.footer { font-size: 0.75rem; color: #64748b; margin-top: 4rem; text-align: center; }
#status { font-size: 0.8rem; color: var(--accent); margin-left: 10px; display: none; }
</style>
</head>
<body>
<div class="container">
<div class="search-card">
<h1>🌐 ${CONFIG.TITLE}</h1>
<input type="text" id="q" class="input-box" placeholder="输入目标 URL 或域名..." autofocus>
<div class="btn-grid">
<button onclick="go()">🚀 直 接 访 问</button>
</div>
</div>
<div class="nav-section">
<div class="nav-header">
<h3 style="margin:0">🔖 个人书签中心 <span id="status">同步成功</span></h3>
<span style="font-size:0.8rem; color:var(--accent); cursor:pointer" onclick="addLink()">+ 永久添加</span>
</div>
<div id="navGrid" class="nav-grid"></div>
</div>
</div>
<div class="footer">
Cloudflare KV 云端同步已激活 • Worker 全面兼容 SPA 修复版
</div>
<script>
function go() {
let v = document.getElementById('q').value.trim();
if(!v) return;
if(v.toLowerCase() === 'google') v = 'google.com';
if(v.toLowerCase() === 'github') v = 'github.com';
if(v.toLowerCase() === 'youtube') v = 'youtube.com';
let u = v.startsWith('http') ? v : 'https://' + v;
location.href = "/" + u;
}
document.getElementById('q').onkeypress=e=>{ if(e.key==='Enter') go() };
let links = ${linksData};
async function saveLinks() {
showStatus('正在保存云端...');
try {
await fetch('/--links--', { method: 'POST', body: JSON.stringify(links) });
showStatus('云端已更新', 2000);
} catch(e) { showStatus('保存失败', 3000); }
}
function renderLinks() {
const grid = document.getElementById('navGrid');
grid.innerHTML = links.length ? '' : '<div style="grid-column:1/-1;text-align:center;color:#64748b;padding:20px;">点击“+ 永久添加”开始建立云端书签</div>';
links.forEach((item, i) => {
const div = document.createElement('div');
div.className = 'nav-item';
div.onclick = () => location.href = "/" + item.url;
div.innerHTML = \`<div>\${item.name}</div><div class="delete-btn" onclick="event.stopPropagation(); deleteLink(\${i})">×</div>\`;
grid.appendChild(div);
});
}
function addLink() {
const name = prompt("网站名称:");
let url = prompt("网址 (如 google.com):");
if(name && url) {
if(!url.startsWith('http')) url = 'https://' + url;
links.push({ name, url });
renderLinks();
saveLinks();
}
}
function deleteLink(i) {
if(confirm('删除此云端书签?')) {
links.splice(i, 1);
renderLinks();
saveLinks();
}
}
function showStatus(msg, ms) {
const s = document.getElementById('status');
s.innerText = msg; s.style.display = 'inline';
if(ms) setTimeout(() => s.style.display = 'none', ms);
}
renderLinks();
</script>
</body>
</html>`;
}
function getLoginUI() {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录</title>
<style>
body{background:#0f172a;color:#fff;display:flex;justify-content:center;align-items:center;height:100vh;margin:0}
.card{background:#1e293b;padding:2.5rem;border-radius:1.5rem;text-align:center;width:90%;max-width:320px;box-sizing:border-box;box-shadow:0 10px 30px rgba(0,0,0,0.5)}
input{width:100%;padding:14px;margin:10px 0;background:#0f172a;border:1px solid #334155;color:#fff;border-radius:30px;text-align:center;box-sizing:border-box;outline:none;font-size:16px}
button{width:100%;padding:14px;margin-top:15px;background:#38bdf8;border:none;border-radius:30px;cursor:pointer;font-weight:700;color:#0f172a;font-size:16px;transition:0.2s}
button:hover{opacity:0.9;transform:translateY(-1px)}
</style>
</head>
<body>
<div class="card">
<h2 style="margin-top:0;margin-bottom:20px;color:#38bdf8">🔒 身份验证</h2>
<input id="u" placeholder="请输入用户名" autocomplete="off">
<input id="p" type="password" placeholder="请输入密码" onkeypress="if(event.key==='Enter'){ l(); return false; }">
<button id="btn" onclick="l()">进 入 网 关</button>
</div>
<script>
async function l(){
const u = document.getElementById('u').value.trim();
const p = document.getElementById('p').value.trim();
const btn = document.getElementById('btn');
if(!u || !p) return alert('⚠️ 用户名和密码不能为空');
btn.innerText = '正在验证...';
try {
const r = await fetch('/--login--', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({u, p})
});
if (r.ok) {
btn.innerText = '验证成功!';
btn.style.background = '#4ade80';
setTimeout(() => location.href = '/', 300);
} else {
alert('❌ 账号或密码错误,请重试');
btn.innerText = '进 入 网 关';
}
} catch (e) {
alert('⚠️ 无法连接到服务器,请检查网络');
btn.innerText = '进 入 网 关';
}
}
</script>
</body>
</html>`;
}