pull/51/head
hunsh 2022-01-19 13:14:09 +08:00
parent d978d13efb
commit cbc8666401
2 changed files with 81 additions and 60 deletions

View File

@ -105,30 +105,36 @@ def iter_content(self, chunk_size=1, decode_unicode=False):
return chunks return chunks
def check_url(u):
for exp in (exp1, exp2, exp3, exp4, exp5):
m = exp.match(u)
if m:
return m
return False
@app.route('/<path:u>', methods=['GET', 'POST']) @app.route('/<path:u>', methods=['GET', 'POST'])
def proxy(u): def handler(u):
u = u if u.startswith('http') else 'https://' + u u = u if u.startswith('http') else 'https://' + u
if u.rfind('://', 3, 9) == -1: if u.rfind('://', 3, 9) == -1:
u = u.replace('s:/', 's://', 1) # uwsgi会将//传递为/ u = u.replace('s:/', 's://', 1) # uwsgi会将//传递为/
pass_by = False pass_by = False
for exp in (exp1, exp2, exp3, exp4, exp5): m = check_url(u)
m = exp.match(u) if m:
if m: m = tuple(m.groups())
m = tuple(m.groups()) if white_list:
if white_list: for i in white_list:
for i in white_list:
if m[:len(i)] == i or i[0] == '*' and len(m) == 2 and m[1] == i[1]:
break
else:
return Response('Forbidden by white list.', status=403)
for i in black_list:
if m[:len(i)] == i or i[0] == '*' and len(m) == 2 and m[1] == i[1]: if m[:len(i)] == i or i[0] == '*' and len(m) == 2 and m[1] == i[1]:
return Response('Forbidden by black list.', status=403)
for i in pass_list:
if m[:len(i)] == i or i[0] == '*' and len(m) == 2 and m[1] == i[1]:
pass_by = True
break break
break else:
return Response('Forbidden by white list.', status=403)
for i in black_list:
if m[:len(i)] == i or i[0] == '*' and len(m) == 2 and m[1] == i[1]:
return Response('Forbidden by black list.', status=403)
for i in pass_list:
if m[:len(i)] == i or i[0] == '*' and len(m) == 2 and m[1] == i[1]:
pass_by = True
break
else: else:
return Response('Invalid input.', status=403) return Response('Invalid input.', status=403)
@ -146,31 +152,39 @@ def proxy(u):
else: else:
if exp2.match(u): if exp2.match(u):
u = u.replace('/blob/', '/raw/', 1) u = u.replace('/blob/', '/raw/', 1)
headers = {} return proxy(u)
r_headers = dict(request.headers)
if 'Host' in r_headers:
r_headers.pop('Host')
try:
url = u + request.url.replace(request.base_url, '', 1)
if url.startswith('https:/') and not url.startswith('https://'):
url = 'https://' + url[7:]
r = requests.request(method=request.method, url=url, data=request.data, headers=r_headers, stream=True)
headers = dict(r.headers)
if 'Content-length' in r.headers and int(r.headers['Content-length']) > size_limit:
return redirect(u + request.url.replace(request.base_url, '', 1))
def generate(): def proxy(u, allow_redirects=False):
for chunk in iter_content(r, chunk_size=CHUNK_SIZE): headers = {}
yield chunk r_headers = dict(request.headers)
if 'Host' in r_headers:
r_headers.pop('Host')
try:
url = u + request.url.replace(request.base_url, '', 1)
if url.startswith('https:/') and not url.startswith('https://'):
url = 'https://' + url[7:]
r = requests.request(method=request.method, url=url, data=request.data, headers=r_headers, stream=True, allow_redirects=allow_redirects)
headers = dict(r.headers)
return Response(generate(), headers=headers, status=r.status_code) if 'Content-length' in r.headers and int(r.headers['Content-length']) > size_limit:
except Exception as e: return redirect(u + request.url.replace(request.base_url, '', 1))
headers['content-type'] = 'text/html; charset=UTF-8'
return Response('server error ' + str(e), status=500, headers=headers)
# else:
# return Response('Illegal input', status=403, mimetype='text/html; charset=UTF-8')
def generate():
for chunk in iter_content(r, chunk_size=CHUNK_SIZE):
yield chunk
if 'Location' in r.headers:
_location = r.headers.get('Location')
if check_url(_location):
headers['Location'] = '/' + _location
else:
return proxy(_location, True)
return Response(generate(), headers=headers, status=r.status_code)
except Exception as e:
headers['content-type'] = 'text/html; charset=UTF-8'
return Response('server error ' + str(e), status=500, headers=headers)
app.debug = True app.debug = True
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -22,6 +22,13 @@ const PREFLIGHT_INIT = {
}), }),
} }
const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive|suites)\/.*$/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
/** /**
* @param {any} body * @param {any} body
* @param {number} status * @param {number} status
@ -52,6 +59,15 @@ addEventListener('fetch', e => {
}) })
function checkUrl(u) {
for (let i of [exp1, exp2, exp3, exp4, exp5, ]) {
if (u.search(i) === 0) {
return true
}
}
return false
}
/** /**
* @param {FetchEvent} e * @param {FetchEvent} e
*/ */
@ -65,18 +81,13 @@ async function fetchHandler(e) {
} }
// cfworker 会把路径中的 `//` 合并成 `/` // cfworker 会把路径中的 `//` 合并成 `/`
path = urlObj.href.substr(urlObj.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://') path = urlObj.href.substr(urlObj.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://')
const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive|suites)\/.*$/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
if (path.search(exp1) === 0 || path.search(exp5) === 0 || !Config.cnpmjs && (path.search(exp3) === 0 || path.search(exp4) === 0)) { if (path.search(exp1) === 0 || path.search(exp5) === 0 || !Config.cnpmjs && (path.search(exp3) === 0 || path.search(exp4) === 0)) {
return httpHandler(req, path) return httpHandler(req, path)
} else if (path.search(exp2) === 0) { } else if (path.search(exp2) === 0) {
if (Config.jsdelivr){ if (Config.jsdelivr) {
const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh') const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh')
return Response.redirect(newUrl, 302) return Response.redirect(newUrl, 302)
}else{ } else {
path = path.replace('/blob/', '/raw/') path = path.replace('/blob/', '/raw/')
return httpHandler(req, path) return httpHandler(req, path)
} }
@ -106,8 +117,6 @@ function httpHandler(req, pathname) {
return new Response(null, PREFLIGHT_INIT) return new Response(null, PREFLIGHT_INIT)
} }
let rawLen = ''
const reqHdrNew = new Headers(reqHdrRaw) const reqHdrNew = new Headers(reqHdrRaw)
let urlStr = pathname let urlStr = pathname
@ -120,10 +129,10 @@ function httpHandler(req, pathname) {
const reqInit = { const reqInit = {
method: req.method, method: req.method,
headers: reqHdrNew, headers: reqHdrNew,
redirect: 'follow', redirect: 'manual',
body: req.body body: req.body
} }
return proxy(urlObj, reqInit, rawLen, 0) return proxy(urlObj, reqInit)
} }
@ -132,24 +141,22 @@ function httpHandler(req, pathname) {
* @param {URL} urlObj * @param {URL} urlObj
* @param {RequestInit} reqInit * @param {RequestInit} reqInit
*/ */
async function proxy(urlObj, reqInit, rawLen) { async function proxy(urlObj, reqInit) {
const res = await fetch(urlObj.href, reqInit) const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld) const resHdrNew = new Headers(resHdrOld)
// verify const status = res.status
if (rawLen) {
const newLen = resHdrOld.get('content-length') || ''
const badLen = (rawLen !== newLen)
if (badLen) { if (resHdrNew.has('location')) {
return makeRes(res.body, 400, { let _location = resHdrNew.get('location')
'--error': `bad len: ${newLen}, except: ${rawLen}`, if (checkUrl(_location))
'access-control-expose-headers': '--error', resHdrNew.set('location', PREFIX + _location)
}) else {
reqInit.redirect = 'follow'
return proxy(newUrl(_location), reqInit)
} }
} }
const status = res.status
resHdrNew.set('access-control-expose-headers', '*') resHdrNew.set('access-control-expose-headers', '*')
resHdrNew.set('access-control-allow-origin', '*') resHdrNew.set('access-control-allow-origin', '*')