Created
June 19, 2024 12:31
-
-
Save youthlin/5b08689784a67e61317073169e80b0ac to your computer and use it in GitHub Desktop.
This file contains 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
// see: | |
// https://github.com/hunshcn/gh-proxy | |
// 首页静态文件 | |
const ASSET_URL = 'https://hunshcn.github.io/gh-proxy/' | |
// 前缀, 如果自定义路由为 example.com/gh/*, 将 PREFIX 改为 '/gh/', 注意, 少一个杠都会错! | |
const PREFIX = '/' | |
// 分支文件使用 jsDelivr 镜像的开关, 0 为关闭, 默认关闭 | |
const Config = { | |
jsdelivr: 0 | |
} | |
// 白名单,路径里面有包含字符的才会通过,e.g. ['/username/'] | |
const whiteList = [] | |
const PREFLIGHT_INIT = { | |
status: 204, | |
headers: new Headers({ | |
'access-control-allow-origin': '*', | |
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', | |
'access-control-max-age': '1728000', | |
}), | |
} | |
const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i | |
const exp2 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob|raw)\/.*$/i | |
const exp3 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i | |
const exp4 = /^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+?\/.+$/i | |
const exp5 = /^(?:https?:\/\/)?gist\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+$/i | |
const exp6 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/tags.*$/i | |
/** | |
* 入口 | |
* @param {Request} request | |
* @returns {Promise<Response>} | |
*/ | |
async function fetchHandler(request) { | |
const req = request | |
const urlStr = req.url | |
const urlObj = new URL(urlStr) | |
let path = urlObj.searchParams.get('q') | |
if (path) { | |
return Response.redirect('https://' + urlObj.host + PREFIX + path, 301) | |
} | |
// cfworker 会把路径中的 `//` 合并成 `/` | |
path = urlObj.href.substr(urlObj.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://') | |
// 普通文件 | |
if (path.search(exp1) === 0 || | |
path.search(exp3) === 0 || | |
path.search(exp4) === 0 || | |
path.search(exp5) === 0 || | |
path.search(exp6) === 0) { | |
return httpHandler(req, path) | |
} | |
// blob/raw | |
if (path.search(exp2) === 0) { | |
if (Config.jsdelivr) { | |
const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh') | |
return Response.redirect(newUrl, 302) | |
} | |
path = path.replace('/blob/', '/raw/') | |
return httpHandler(req, path) | |
} | |
// raw.github.com | |
if (path.search(exp4) === 0) { | |
const newUrl = path.replace(/(?<=com\/.+?\/.+?)\/(.+?\/)/, '@$1').replace(/^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com/, 'https://cdn.jsdelivr.net/gh') | |
return Response.redirect(newUrl, 302) | |
} | |
// 其他 返回静态首页 | |
return fetch(ASSET_URL + path) | |
} | |
/** | |
* @param {Request} req | |
* @param {string} pathname | |
*/ | |
function httpHandler(req, pathname) { | |
const reqHdrRaw = req.headers | |
// preflight | |
if (req.method === 'OPTIONS' && | |
reqHdrRaw.has('access-control-request-headers') | |
) { | |
return new Response(null, PREFLIGHT_INIT) | |
} | |
const reqHdrNew = new Headers(reqHdrRaw) | |
let urlStr = pathname | |
let allow = whiteList.length == 0 | |
for (let i of whiteList) { | |
if (urlStr.includes(i)) { | |
allow = true | |
break | |
} | |
} | |
if (!allow) { | |
return new Response("blocked", { status: 403 }) | |
} | |
if (urlStr.search(/^https?:\/\//) !== 0) { | |
urlStr = 'https://' + urlStr | |
} | |
const urlObj = newUrl(urlStr) | |
/** @type {RequestInit} */ | |
const reqInit = { | |
method: req.method, | |
headers: reqHdrNew, | |
redirect: 'manual', | |
body: req.body | |
} | |
return proxy(urlObj, reqInit) | |
} | |
/** | |
* @param {string} urlStr | |
*/ | |
function newUrl(urlStr) { | |
try { | |
return new URL(urlStr) | |
} catch (err) { | |
return null | |
} | |
} | |
/** | |
* @param {URL} urlObj | |
* @param {RequestInit} reqInit | |
* @returns {Promise<Response>} | |
*/ | |
async function proxy(urlObj, reqInit) { | |
const res = await fetch(urlObj.href, reqInit) | |
const resHdrOld = res.headers | |
const resHdrNew = new Headers(resHdrOld) | |
const status = res.status | |
if (resHdrNew.has('location')) { | |
let _location = resHdrNew.get('location') | |
if (checkUrl(_location)) | |
resHdrNew.set('location', PREFIX + _location) | |
else { | |
reqInit.redirect = 'follow' | |
return proxy(newUrl(_location), reqInit) | |
} | |
} | |
resHdrNew.set('access-control-expose-headers', '*') | |
resHdrNew.set('access-control-allow-origin', '*') | |
resHdrNew.delete('content-security-policy') | |
resHdrNew.delete('content-security-policy-report-only') | |
resHdrNew.delete('clear-site-data') | |
return new Response(res.body, { | |
status, | |
headers: resHdrNew, | |
}) | |
} | |
function checkUrl(u) { | |
for (let i of [exp1, exp2, exp3, exp4, exp5, exp6]) { | |
if (u.search(i) === 0) { | |
return true | |
} | |
} | |
return false | |
} | |
function makeRes(body, status = 200, headers = {}) { | |
headers['access-control-allow-origin'] = '*' | |
return new Response(body, { status, headers }) | |
} | |
/** | |
* @typedef {Object} Env | |
*/ | |
export default { | |
/** | |
* @param {Request} request | |
* @param {Env} env | |
* @param {ExecutionContext} ctx | |
* @returns {Promise<Response>} | |
*/ | |
async fetch(request, env, ctx) { | |
const url = new URL(request.url); | |
console.log(`Hello ${navigator.userAgent} at path ${url.pathname}!`); | |
return await fetchHandler(request) | |
.catch(err => makeRes('cfworker error:\n' + err.stack, 502)) | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment