mirror of https://github.com/hunshcn/gh-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
4.6 KiB
164 lines
4.6 KiB
'use strict' |
|
|
|
/** |
|
* static files (404.html, sw.js, conf.js) |
|
*/ |
|
const ASSET_URL = 'https://hunshcn.github.io/gh-proxy' |
|
// 前缀,如果自定义路由为example.com/gh/*,将PREFIX改为 '/gh/',注意,少一个杠都会错! |
|
const PREFIX = '/' |
|
// git使用cnpmjs镜像、分支文件使用jsDelivr镜像的开关,0为关闭,默认开启 |
|
const Config = { |
|
jsdelivr: 1, |
|
cnpmjs: 1 |
|
} |
|
|
|
/** @type {RequestInit} */ |
|
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', |
|
}), |
|
} |
|
|
|
/** |
|
* @param {any} body |
|
* @param {number} status |
|
* @param {Object<string, string>} headers |
|
*/ |
|
function makeRes(body, status = 200, headers = {}) { |
|
headers['access-control-allow-origin'] = '*' |
|
return new Response(body, {status, headers}) |
|
} |
|
|
|
|
|
/** |
|
* @param {string} urlStr |
|
*/ |
|
function newUrl(urlStr) { |
|
try { |
|
return new URL(urlStr) |
|
} catch (err) { |
|
return null |
|
} |
|
} |
|
|
|
|
|
addEventListener('fetch', e => { |
|
const ret = fetchHandler(e) |
|
.catch(err => makeRes('cfworker error:\n' + err.stack, 502)) |
|
e.respondWith(ret) |
|
}) |
|
|
|
|
|
/** |
|
* @param {FetchEvent} e |
|
*/ |
|
async function fetchHandler(e) { |
|
const req = e.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://') |
|
const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i |
|
const exp2 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob)\/.*$/i |
|
const exp3 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i |
|
const exp4 = /^(?:https?:\/\/)?raw\.githubusercontent\.com\/.+?\/.+?\/.+?\/.+$/i |
|
if (path.search(exp1) === 0 || !Config.cnpmjs && (path.search(exp3) === 0 || path.search(exp4) === 0)) { |
|
return httpHandler(req, path) |
|
} else 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) |
|
}else{ |
|
path = path.replace('/blob/', '/raw/') |
|
return httpHandler(req, path) |
|
} |
|
} else if (path.search(exp3) === 0) { |
|
const newUrl = path.replace(/^(?:https?:\/\/)?github\.com/, 'https://github.com.cnpmjs.org') |
|
return Response.redirect(newUrl, 302) |
|
} else if (path.search(exp4) === 0) { |
|
const newUrl = path.replace(/(?<=com\/.+?\/.+?)\/(.+?\/)/, '@$1').replace(/^(?:https?:\/\/)?raw\.githubusercontent\.com/, 'https://cdn.jsdelivr.net/gh') |
|
return Response.redirect(newUrl, 302) |
|
} else { |
|
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) |
|
} |
|
|
|
let rawLen = '' |
|
|
|
const reqHdrNew = new Headers(reqHdrRaw) |
|
|
|
let urlStr = pathname |
|
if (urlStr.startsWith('github')) { |
|
urlStr = 'https://' + urlStr |
|
} |
|
const urlObj = newUrl(urlStr) |
|
|
|
/** @type {RequestInit} */ |
|
const reqInit = { |
|
method: req.method, |
|
headers: reqHdrNew, |
|
redirect: 'follow', |
|
body: req.body |
|
} |
|
return proxy(urlObj, reqInit, rawLen, 0) |
|
} |
|
|
|
|
|
/** |
|
* |
|
* @param {URL} urlObj |
|
* @param {RequestInit} reqInit |
|
*/ |
|
async function proxy(urlObj, reqInit, rawLen) { |
|
const res = await fetch(urlObj.href, reqInit) |
|
const resHdrOld = res.headers |
|
const resHdrNew = new Headers(resHdrOld) |
|
|
|
// verify |
|
if (rawLen) { |
|
const newLen = resHdrOld.get('content-length') || '' |
|
const badLen = (rawLen !== newLen) |
|
|
|
if (badLen) { |
|
return makeRes(res.body, 400, { |
|
'--error': `bad len: ${newLen}, except: ${rawLen}`, |
|
'access-control-expose-headers': '--error', |
|
}) |
|
} |
|
} |
|
const status = res.status |
|
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, |
|
}) |
|
} |
|
|
|
|