├── README.md ├── LICENSE ├── dub_proxy.js ├── quay_proxy.js ├── google_proxy.js ├── github_proxy.js ├── gcr_proxy.js └── docker_proxy.js /README.md: -------------------------------------------------------------------------------- 1 | # cloudflare-workers 2 | 3 | Cloudflare Worker scripts 4 | 5 | 6 | ## deploy 7 | 8 | 首页:[https://workers.cloudflare.com](https://workers.cloudflare.com) 9 | 10 | 注册,登陆,Start building,取一个子域名,Create a Worker。 11 | 12 | 复制 js 到左侧代码框,Save and deploy。 13 | 14 | 15 | ## example 16 | 17 | - [https://github.com/cloudflare/worker-examples](https://github.com/cloudflare/worker-examples) 18 | 19 | ## limit 20 | 21 | - [https://developers.cloudflare.com/workers/platform/limits](https://developers.cloudflare.com/workers/platform/limits) 22 | 23 | ## how-workers-works 24 | 25 | - [https://developers.cloudflare.com/workers/learning/how-workers-works](https://developers.cloudflare.com/workers/learning/how-workers-works) 26 | 27 | ## cdn 28 | 29 | 虽然 Cloudflare Worker 使用 Cloudflare CDN,但分配给中国访客的 IP 并不友好(高延迟/高丢包/速度慢等)。 30 | 31 | 虽然 Cloudflare 公开了所有 IP 段 ,但想要在这么多 IP 中找到适合自己的,怕是要累死,可以通过 [CloudflareSpeedTest](https://github.com/XIU2/CloudflareSpeedTest) 来获取速度最快的地址。 32 | 33 | 将获取到的地址与 Cloudflare Worker 域名绑定即可。 34 | 35 | ``` 36 | 104.16.161.131 k8sgcr.lework.workers.dev 37 | ``` 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lework 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dub_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * static files (404.html, sw.js, conf.js) 5 | */ 6 | 7 | /** @type {RequestInit} */ 8 | const PREFLIGHT_INIT = { 9 | status: 204, 10 | headers: new Headers({ 11 | 'access-control-allow-origin': '*', 12 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', 13 | 'access-control-max-age': '1728000', 14 | }), 15 | } 16 | 17 | /** 18 | * @param {any} body 19 | * @param {number} status 20 | * @param {Object} headers 21 | */ 22 | function makeRes(body, status = 200, headers = {}) { 23 | headers['access-control-allow-origin'] = '*' 24 | return new Response(body, {status, headers}) 25 | } 26 | 27 | 28 | /** 29 | * @param {string} urlStr 30 | */ 31 | function newUrl(urlStr) { 32 | try { 33 | return new URL(urlStr) 34 | } catch (err) { 35 | return null 36 | } 37 | } 38 | 39 | 40 | addEventListener('fetch', e => { 41 | const ret = fetchHandler(e) 42 | .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) 43 | e.respondWith(ret) 44 | }) 45 | 46 | 47 | /** 48 | * @param {FetchEvent} e 49 | */ 50 | async function fetchHandler(e) { 51 | const getReqHeader = (key) => e.request.headers.get(key); 52 | 53 | let url = new URL(e.request.url); 54 | url.hostname = "code.dlang.org"; 55 | 56 | let parameter = { 57 | headers: { 58 | 'Host': 'code.dlang.org', 59 | 'User-Agent': getReqHeader("User-Agent"), 60 | 'Accept': getReqHeader("Accept"), 61 | 'Accept-Language': getReqHeader("Accept-Language"), 62 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 63 | 'Connection': 'keep-alive', 64 | 'Cache-Control': 'max-age=0' 65 | }, 66 | cacheTtl: 3600 67 | }; 68 | 69 | if (e.request.headers.has("Referer")) { 70 | parameter.headers.Referer = getReqHeader("Referer"); 71 | } 72 | 73 | if (e.request.headers.has("Origin")) { 74 | parameter.headers.Origin = getReqHeader("Origin"); 75 | } 76 | const exp = /.*\.zip$/ 77 | let redirect_url = "" 78 | if (e.request.url.search(exp)===0) { 79 | await fetch(new Request(url, e.request), parameter).then( r => { 80 | redirect_url = r.headers.get("Location") 81 | }) 82 | return httpHandler(e.request, redirect_url) 83 | } else { 84 | return fetch(new Request(url, e.request), parameter); 85 | } 86 | } 87 | 88 | 89 | /** 90 | * @param {Request} req 91 | * @param {string} pathname 92 | */ 93 | function httpHandler(req, pathname) { 94 | const reqHdrRaw = req.headers 95 | 96 | // preflight 97 | if (req.method === 'OPTIONS' && 98 | reqHdrRaw.has('access-control-request-headers') 99 | ) { 100 | return new Response(null, PREFLIGHT_INIT) 101 | } 102 | 103 | let rawLen = '' 104 | 105 | const reqHdrNew = new Headers(reqHdrRaw) 106 | 107 | const refer = reqHdrNew.get('referer') 108 | 109 | let urlStr = pathname 110 | if (urlStr.startsWith('github')) { 111 | urlStr = 'https://' + urlStr 112 | } 113 | const urlObj = newUrl(urlStr) 114 | 115 | /** @type {RequestInit} */ 116 | const reqInit = { 117 | method: req.method, 118 | headers: reqHdrNew, 119 | redirect: 'follow', 120 | body: req.body 121 | } 122 | return proxy(urlObj, reqInit, rawLen, 0) 123 | } 124 | 125 | 126 | /** 127 | * 128 | * @param {URL} urlObj 129 | * @param {RequestInit} reqInit 130 | */ 131 | async function proxy(urlObj, reqInit, rawLen) { 132 | const res = await fetch(urlObj.href, reqInit) 133 | const resHdrOld = res.headers 134 | const resHdrNew = new Headers(resHdrOld) 135 | 136 | // verify 137 | if (rawLen) { 138 | const newLen = resHdrOld.get('content-length') || '' 139 | const badLen = (rawLen !== newLen) 140 | 141 | if (badLen) { 142 | return makeRes(res.body, 400, { 143 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 144 | 'access-control-expose-headers': '--error', 145 | }) 146 | } 147 | } 148 | const status = res.status 149 | resHdrNew.set('access-control-expose-headers', '*') 150 | resHdrNew.set('access-control-allow-origin', '*') 151 | resHdrNew.set('Cache-Control', 'max-age=1500') 152 | 153 | resHdrNew.delete('content-security-policy') 154 | resHdrNew.delete('content-security-policy-report-only') 155 | resHdrNew.delete('clear-site-data') 156 | 157 | return new Response(res.body, { 158 | status, 159 | headers: resHdrNew 160 | }) 161 | } -------------------------------------------------------------------------------- /quay_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const hub_host = 'quay.io' 4 | const auth_url = 'https://quay.io' 5 | const workers_url = 'https://quay.lework.workers.dev' 6 | 7 | /** 8 | * static files (404.html, sw.js, conf.js) 9 | */ 10 | 11 | /** @type {RequestInit} */ 12 | const PREFLIGHT_INIT = { 13 | status: 204, 14 | headers: new Headers({ 15 | 'access-control-allow-origin': '*', 16 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', 17 | 'access-control-max-age': '1728000', 18 | }), 19 | } 20 | 21 | /** 22 | * @param {any} body 23 | * @param {number} status 24 | * @param {Object} headers 25 | */ 26 | function makeRes(body, status = 200, headers = {}) { 27 | headers['access-control-allow-origin'] = '*' 28 | return new Response(body, {status, headers}) 29 | } 30 | 31 | 32 | /** 33 | * @param {string} urlStr 34 | */ 35 | function newUrl(urlStr) { 36 | try { 37 | return new URL(urlStr) 38 | } catch (err) { 39 | return null 40 | } 41 | } 42 | 43 | 44 | addEventListener('fetch', e => { 45 | const ret = fetchHandler(e) 46 | .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) 47 | e.respondWith(ret) 48 | }) 49 | 50 | 51 | /** 52 | * @param {FetchEvent} e 53 | */ 54 | async function fetchHandler(e) { 55 | const getReqHeader = (key) => e.request.headers.get(key); 56 | 57 | let url = new URL(e.request.url); 58 | url.hostname = hub_host; 59 | 60 | let parameter = { 61 | headers: { 62 | 'Host': hub_host, 63 | 'User-Agent': getReqHeader("User-Agent"), 64 | 'Accept': getReqHeader("Accept"), 65 | 'Accept-Language': getReqHeader("Accept-Language"), 66 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 67 | 'Connection': 'keep-alive', 68 | 'Cache-Control': 'max-age=0' 69 | }, 70 | cacheTtl: 3600 71 | }; 72 | 73 | if (e.request.headers.has("Authorization")) { 74 | parameter.headers.Authorization = getReqHeader("Authorization"); 75 | } 76 | 77 | let original_response = await fetch(new Request(url, e.request), parameter) 78 | let original_response_clone = original_response.clone(); 79 | let original_text = original_response_clone.body; 80 | let response_headers = original_response.headers; 81 | let new_response_headers = new Headers(response_headers); 82 | let status = original_response.status; 83 | 84 | if (new_response_headers.get("Www-Authenticate")) { 85 | let auth = new_response_headers.get("Www-Authenticate"); 86 | let re = new RegExp(auth_url, 'g'); 87 | new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); 88 | } 89 | 90 | if (new_response_headers.get("Location")) { 91 | return httpHandler(e.request, new_response_headers.get("Location")) 92 | } 93 | 94 | let response = new Response(original_text, { 95 | status, 96 | headers: new_response_headers 97 | }) 98 | return response; 99 | 100 | } 101 | 102 | 103 | /** 104 | * @param {Request} req 105 | * @param {string} pathname 106 | */ 107 | function httpHandler(req, pathname) { 108 | const reqHdrRaw = req.headers 109 | 110 | // preflight 111 | if (req.method === 'OPTIONS' && 112 | reqHdrRaw.has('access-control-request-headers') 113 | ) { 114 | return new Response(null, PREFLIGHT_INIT) 115 | } 116 | 117 | let rawLen = '' 118 | 119 | const reqHdrNew = new Headers(reqHdrRaw) 120 | 121 | const refer = reqHdrNew.get('referer') 122 | 123 | let urlStr = pathname 124 | 125 | const urlObj = newUrl(urlStr) 126 | 127 | /** @type {RequestInit} */ 128 | const reqInit = { 129 | method: req.method, 130 | headers: reqHdrNew, 131 | redirect: 'follow', 132 | body: req.body 133 | } 134 | return proxy(urlObj, reqInit, rawLen, 0) 135 | } 136 | 137 | 138 | /** 139 | * 140 | * @param {URL} urlObj 141 | * @param {RequestInit} reqInit 142 | */ 143 | async function proxy(urlObj, reqInit, rawLen) { 144 | const res = await fetch(urlObj.href, reqInit) 145 | const resHdrOld = res.headers 146 | const resHdrNew = new Headers(resHdrOld) 147 | 148 | // verify 149 | if (rawLen) { 150 | const newLen = resHdrOld.get('content-length') || '' 151 | const badLen = (rawLen !== newLen) 152 | 153 | if (badLen) { 154 | return makeRes(res.body, 400, { 155 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 156 | 'access-control-expose-headers': '--error', 157 | }) 158 | } 159 | } 160 | const status = res.status 161 | resHdrNew.set('access-control-expose-headers', '*') 162 | resHdrNew.set('access-control-allow-origin', '*') 163 | resHdrNew.set('Cache-Control', 'max-age=1500') 164 | 165 | resHdrNew.delete('content-security-policy') 166 | resHdrNew.delete('content-security-policy-report-only') 167 | resHdrNew.delete('clear-site-data') 168 | 169 | return new Response(res.body, { 170 | status, 171 | headers: resHdrNew 172 | }) 173 | } -------------------------------------------------------------------------------- /google_proxy.js: -------------------------------------------------------------------------------- 1 | // Website you intended to retrieve for users. 2 | const upstream = 'www.google.com' 3 | 4 | // Custom pathname for the upstream website. 5 | const upstream_path = '/' 6 | 7 | // Website you intended to retrieve for users using mobile devices. 8 | const upstream_mobile = 'www.google.com' 9 | 10 | // Countries and regions where you wish to suspend your service. 11 | const blocked_region = ['KP', 'SY', 'PK', 'CU'] 12 | 13 | // IP addresses which you wish to block from using your service. 14 | const blocked_ip_address = ['0.0.0.0', '127.0.0.1'] 15 | 16 | // Whether to use HTTPS protocol for upstream address. 17 | const https = true 18 | 19 | // Whether to disable cache. 20 | const disable_cache = true 21 | 22 | // Replace texts. 23 | const replace_dict = { 24 | '$upstream': '$custom_domain', 25 | '//google.com': '' 26 | } 27 | 28 | addEventListener('fetch', event => { 29 | event.respondWith(fetchAndApply(event.request)); 30 | }) 31 | 32 | async function fetchAndApply(request) { 33 | 34 | const region = request.headers.get('cf-ipcountry').toUpperCase(); 35 | const ip_address = request.headers.get('cf-connecting-ip'); 36 | const user_agent = request.headers.get('user-agent'); 37 | 38 | let response = null; 39 | let url = new URL(request.url); 40 | let url_hostname = url.hostname; 41 | 42 | if (https == true) { 43 | url.protocol = 'https:'; 44 | } else { 45 | url.protocol = 'http:'; 46 | } 47 | 48 | if (await device_status(user_agent)) { 49 | var upstream_domain = upstream; 50 | } else { 51 | var upstream_domain = upstream_mobile; 52 | } 53 | 54 | url.host = upstream_domain; 55 | if (url.pathname == '/') { 56 | url.pathname = upstream_path; 57 | } else { 58 | url.pathname = upstream_path + url.pathname; 59 | } 60 | 61 | if (blocked_region.includes(region)) { 62 | response = new Response('Access denied: WorkersProxy is not available in your region yet.', { 63 | status: 403 64 | }); 65 | } else if (blocked_ip_address.includes(ip_address)) { 66 | response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', { 67 | status: 403 68 | }); 69 | } else { 70 | let method = request.method; 71 | let request_headers = request.headers; 72 | let new_request_headers = new Headers(request_headers); 73 | 74 | new_request_headers.set('Host', upstream_domain); 75 | new_request_headers.set('Referer', url.protocol + '//' + url_hostname); 76 | 77 | let original_response = await fetch(url.href, { 78 | method: method, 79 | headers: new_request_headers 80 | }) 81 | 82 | let original_response_clone = original_response.clone(); 83 | let original_text = null; 84 | let response_headers = original_response.headers; 85 | let new_response_headers = new Headers(response_headers); 86 | let status = original_response.status; 87 | 88 | if (disable_cache) { 89 | new_response_headers.set('Cache-Control', 'no-store'); 90 | } 91 | 92 | new_response_headers.set('access-control-allow-origin', '*'); 93 | new_response_headers.set('access-control-allow-credentials', true); 94 | new_response_headers.delete('content-security-policy'); 95 | new_response_headers.delete('content-security-policy-report-only'); 96 | new_response_headers.delete('clear-site-data'); 97 | 98 | if(new_response_headers.get("x-pjax-url")) { 99 | new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname)); 100 | } 101 | 102 | const content_type = new_response_headers.get('content-type'); 103 | if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) { 104 | original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname); 105 | } else { 106 | original_text = original_response_clone.body 107 | } 108 | 109 | response = new Response(original_text, { 110 | status, 111 | headers: new_response_headers 112 | }) 113 | } 114 | return response; 115 | } 116 | 117 | async function replace_response_text(response, upstream_domain, host_name) { 118 | let text = await response.text() 119 | 120 | var i, j; 121 | for (i in replace_dict) { 122 | j = replace_dict[i] 123 | if (i == '$upstream') { 124 | i = upstream_domain 125 | } else if (i == '$custom_domain') { 126 | i = host_name 127 | } 128 | 129 | if (j == '$upstream') { 130 | j = upstream_domain 131 | } else if (j == '$custom_domain') { 132 | j = host_name 133 | } 134 | 135 | let re = new RegExp(i, 'g') 136 | text = text.replace(re, j); 137 | } 138 | return text; 139 | } 140 | 141 | 142 | async function device_status(user_agent_info) { 143 | var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; 144 | var flag = true; 145 | for (var v = 0; v < agents.length; v++) { 146 | if (user_agent_info.indexOf(agents[v]) > 0) { 147 | flag = false; 148 | break; 149 | } 150 | } 151 | return flag; 152 | } -------------------------------------------------------------------------------- /github_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * static files (404.html, sw.js, conf.js) 5 | */ 6 | const ASSET_URL = 'https://hunshcn.github.io/gh-proxy' 7 | // 前缀,如果自定义路由为example.com/gh/*,将PREFIX改为 '/gh/',注意,少一个杠都会错! 8 | const PREFIX = '/' 9 | // git使用cnpmjs镜像、分支文件使用jsDelivr镜像的开关,0为关闭,默认开启 10 | const Config = { 11 | jsdelivr: 1, 12 | cnpmjs: 1 13 | } 14 | 15 | /** @type {RequestInit} */ 16 | const PREFLIGHT_INIT = { 17 | status: 204, 18 | headers: new Headers({ 19 | 'access-control-allow-origin': '*', 20 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', 21 | 'access-control-max-age': '1728000', 22 | }), 23 | } 24 | 25 | /** 26 | * @param {any} body 27 | * @param {number} status 28 | * @param {Object} headers 29 | */ 30 | function makeRes(body, status = 200, headers = {}) { 31 | headers['access-control-allow-origin'] = '*' 32 | return new Response(body, {status, headers}) 33 | } 34 | 35 | 36 | /** 37 | * @param {string} urlStr 38 | */ 39 | function newUrl(urlStr) { 40 | try { 41 | return new URL(urlStr) 42 | } catch (err) { 43 | return null 44 | } 45 | } 46 | 47 | 48 | addEventListener('fetch', e => { 49 | const ret = fetchHandler(e) 50 | .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) 51 | e.respondWith(ret) 52 | }) 53 | 54 | 55 | /** 56 | * @param {FetchEvent} e 57 | */ 58 | async function fetchHandler(e) { 59 | const req = e.request 60 | const urlStr = req.url 61 | const urlObj = new URL(urlStr) 62 | let path = urlObj.searchParams.get('q') 63 | if (path) { 64 | return Response.redirect('https://' + urlObj.host + PREFIX + path, 301) 65 | } 66 | // cfworker 会把路径中的 `//` 合并成 `/` 67 | path = urlObj.href.substr(urlObj.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://') 68 | const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i 69 | const exp2 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob)\/.*$/i 70 | const exp3 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i 71 | const exp4 = /^(?:https?:\/\/)?raw\.githubusercontent\.com\/.+?\/.+?\/.+?\/.+$/i 72 | const exp5 = /^(?:https?:\/\/)?gist\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+$/i 73 | if (path.search(exp1) === 0 || path.search(exp5) === 0 || !Config.cnpmjs && (path.search(exp3) === 0 || path.search(exp4) === 0)) { 74 | return httpHandler(req, path) 75 | } else if (path.search(exp2) === 0) { 76 | if (Config.jsdelivr){ 77 | const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh') 78 | return Response.redirect(newUrl, 302) 79 | }else{ 80 | path = path.replace('/blob/', '/raw/') 81 | return httpHandler(req, path) 82 | } 83 | } else if (path.search(exp3) === 0) { 84 | const newUrl = path.replace(/^(?:https?:\/\/)?github\.com/, 'https://github.com.cnpmjs.org') 85 | return Response.redirect(newUrl, 302) 86 | } else if (path.search(exp4) === 0) { 87 | const newUrl = path.replace(/(?<=com\/.+?\/.+?)\/(.+?\/)/, '@$1').replace(/^(?:https?:\/\/)?raw\.githubusercontent\.com/, 'https://cdn.jsdelivr.net/gh') 88 | return Response.redirect(newUrl, 302) 89 | } else { 90 | return fetch(ASSET_URL + path) 91 | } 92 | } 93 | 94 | 95 | /** 96 | * @param {Request} req 97 | * @param {string} pathname 98 | */ 99 | function httpHandler(req, pathname) { 100 | const reqHdrRaw = req.headers 101 | 102 | // preflight 103 | if (req.method === 'OPTIONS' && 104 | reqHdrRaw.has('access-control-request-headers') 105 | ) { 106 | return new Response(null, PREFLIGHT_INIT) 107 | } 108 | 109 | let rawLen = '' 110 | 111 | const reqHdrNew = new Headers(reqHdrRaw) 112 | 113 | let urlStr = pathname 114 | if (urlStr.startsWith('github')) { 115 | urlStr = 'https://' + urlStr 116 | } 117 | const urlObj = newUrl(urlStr) 118 | 119 | /** @type {RequestInit} */ 120 | const reqInit = { 121 | method: req.method, 122 | headers: reqHdrNew, 123 | redirect: 'follow', 124 | body: req.body 125 | } 126 | return proxy(urlObj, reqInit, rawLen, 0) 127 | } 128 | 129 | 130 | /** 131 | * 132 | * @param {URL} urlObj 133 | * @param {RequestInit} reqInit 134 | */ 135 | async function proxy(urlObj, reqInit, rawLen) { 136 | const res = await fetch(urlObj.href, reqInit) 137 | const resHdrOld = res.headers 138 | const resHdrNew = new Headers(resHdrOld) 139 | 140 | // verify 141 | if (rawLen) { 142 | const newLen = resHdrOld.get('content-length') || '' 143 | const badLen = (rawLen !== newLen) 144 | 145 | if (badLen) { 146 | return makeRes(res.body, 400, { 147 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 148 | 'access-control-expose-headers': '--error', 149 | }) 150 | } 151 | } 152 | const status = res.status 153 | resHdrNew.set('access-control-expose-headers', '*') 154 | resHdrNew.set('access-control-allow-origin', '*') 155 | 156 | resHdrNew.delete('content-security-policy') 157 | resHdrNew.delete('content-security-policy-report-only') 158 | resHdrNew.delete('clear-site-data') 159 | 160 | return new Response(res.body, { 161 | status, 162 | headers: resHdrNew, 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /gcr_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const hub_host = 'gcr.io' 4 | const auth_url = 'https://gcr.io' 5 | const workers_url = 'https://gcr.lework.workers.dev' 6 | 7 | /** 8 | * static files (404.html, sw.js, conf.js) 9 | */ 10 | 11 | /** @type {RequestInit} */ 12 | const PREFLIGHT_INIT = { 13 | status: 204, 14 | headers: new Headers({ 15 | 'access-control-allow-origin': '*', 16 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', 17 | 'Access-Control-Allow-Headers': "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type", 18 | 'access-control-max-age': '1728000', 19 | }), 20 | } 21 | 22 | /** 23 | * @param {any} body 24 | * @param {number} status 25 | * @param {Object} headers 26 | */ 27 | function makeRes(body, status = 200, headers = {}) { 28 | headers['access-control-allow-origin'] = '*' 29 | return new Response(body, {status, headers}) 30 | } 31 | 32 | 33 | /** 34 | * @param {string} urlStr 35 | */ 36 | function newUrl(urlStr) { 37 | try { 38 | return new URL(urlStr) 39 | } catch (err) { 40 | return null 41 | } 42 | } 43 | 44 | 45 | addEventListener('fetch', e => { 46 | const ret = fetchHandler(e) 47 | .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) 48 | e.respondWith(ret) 49 | }) 50 | 51 | 52 | /** 53 | * @param {FetchEvent} e 54 | */ 55 | async function fetchHandler(e) { 56 | if (e.request.method === 'OPTIONS' ) 57 | { 58 | return new Response(null, PREFLIGHT_INIT) 59 | } 60 | 61 | const getReqHeader = (key) => e.request.headers.get(key); 62 | 63 | let url = new URL(e.request.url); 64 | url.hostname = hub_host; 65 | 66 | let parameter = { 67 | headers: { 68 | 'Host': hub_host, 69 | 'User-Agent': getReqHeader("User-Agent"), 70 | 'Accept': getReqHeader("Accept"), 71 | 'Accept-Language': getReqHeader("Accept-Language"), 72 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 73 | 'Connection': 'keep-alive', 74 | 'Cache-Control': 'max-age=0' 75 | } 76 | }; 77 | 78 | if (e.request.headers.has("Authorization")) { 79 | parameter.headers.Authorization = getReqHeader("Authorization"); 80 | } 81 | 82 | let original_response = await fetch(new Request(url, e.request), parameter) 83 | let original_response_clone = original_response.clone(); 84 | let original_text = original_response_clone.body; 85 | let response_headers = original_response.headers; 86 | let new_response_headers = new Headers(response_headers); 87 | let status = original_response.status; 88 | 89 | if (new_response_headers.get("Www-Authenticate")) { 90 | let auth = new_response_headers.get("Www-Authenticate"); 91 | let re = new RegExp(auth_url, 'g'); 92 | new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); 93 | } 94 | 95 | if (new_response_headers.get("Location")) { 96 | return httpHandler(e.request, new_response_headers.get("Location")) 97 | } 98 | 99 | new_response_headers.set('Access-Control-Allow-Origin', '*') 100 | new_response_headers.set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS') 101 | new_response_headers.set('Access-Control-Allow-Headers', "Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type") 102 | 103 | let response = new Response(original_text, { 104 | status, 105 | headers: new_response_headers 106 | }) 107 | return response; 108 | 109 | } 110 | 111 | 112 | /** 113 | * @param {Request} req 114 | * @param {string} pathname 115 | */ 116 | function httpHandler(req, pathname) { 117 | const reqHdrRaw = req.headers 118 | 119 | // preflight 120 | if (req.method === 'OPTIONS' && 121 | reqHdrRaw.has('access-control-request-headers') 122 | ) { 123 | return new Response(null, PREFLIGHT_INIT) 124 | } 125 | 126 | let rawLen = '' 127 | 128 | const reqHdrNew = new Headers(reqHdrRaw) 129 | 130 | // 访问storage.googleapis.com时去除认证 131 | reqHdrNew.delete('Authorization') 132 | const refer = reqHdrNew.get('referer') 133 | 134 | let urlStr = pathname 135 | 136 | const urlObj = newUrl(urlStr) 137 | 138 | /** @type {RequestInit} */ 139 | const reqInit = { 140 | method: req.method, 141 | headers: reqHdrNew, 142 | redirect: 'follow', 143 | body: req.body 144 | } 145 | return proxy(urlObj, reqInit, rawLen, 0) 146 | } 147 | 148 | 149 | /** 150 | * 151 | * @param {URL} urlObj 152 | * @param {RequestInit} reqInit 153 | */ 154 | async function proxy(urlObj, reqInit, rawLen) { 155 | const res = await fetch(urlObj.href, reqInit) 156 | const resHdrOld = res.headers 157 | const resHdrNew = new Headers(resHdrOld) 158 | 159 | // verify 160 | if (rawLen) { 161 | const newLen = resHdrOld.get('content-length') || '' 162 | const badLen = (rawLen !== newLen) 163 | 164 | if (badLen) { 165 | return makeRes(res.body, 400, { 166 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 167 | 'access-control-expose-headers': '--error', 168 | }) 169 | } 170 | } 171 | const status = res.status 172 | resHdrNew.set('access-control-expose-headers', '*') 173 | resHdrNew.set('access-control-allow-origin', '*') 174 | 175 | resHdrNew.delete('content-security-policy') 176 | resHdrNew.delete('content-security-policy-report-only') 177 | resHdrNew.delete('clear-site-data') 178 | 179 | return new Response(res.body, { 180 | status, 181 | headers: resHdrNew 182 | }) 183 | } 184 | -------------------------------------------------------------------------------- /docker_proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const hub_host = 'registry-1.docker.io' 4 | const auth_url = 'https://auth.docker.io' 5 | const workers_url = 'https://docker.lework.workers.dev' 6 | /** 7 | * static files (404.html, sw.js, conf.js) 8 | */ 9 | 10 | /** @type {RequestInit} */ 11 | const PREFLIGHT_INIT = { 12 | status: 204, 13 | headers: new Headers({ 14 | 'access-control-allow-origin': '*', 15 | 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', 16 | 'access-control-max-age': '1728000', 17 | }), 18 | } 19 | 20 | /** 21 | * @param {any} body 22 | * @param {number} status 23 | * @param {Object} headers 24 | */ 25 | function makeRes(body, status = 200, headers = {}) { 26 | headers['access-control-allow-origin'] = '*' 27 | return new Response(body, {status, headers}) 28 | } 29 | 30 | 31 | /** 32 | * @param {string} urlStr 33 | */ 34 | function newUrl(urlStr) { 35 | try { 36 | return new URL(urlStr) 37 | } catch (err) { 38 | return null 39 | } 40 | } 41 | 42 | 43 | addEventListener('fetch', e => { 44 | const ret = fetchHandler(e) 45 | .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) 46 | e.respondWith(ret) 47 | }) 48 | 49 | 50 | /** 51 | * @param {FetchEvent} e 52 | */ 53 | async function fetchHandler(e) { 54 | const getReqHeader = (key) => e.request.headers.get(key); 55 | 56 | let url = new URL(e.request.url); 57 | 58 | if (url.pathname === '/token') { 59 | let token_parameter = { 60 | headers: { 61 | 'Host': 'auth.docker.io', 62 | 'User-Agent': getReqHeader("User-Agent"), 63 | 'Accept': getReqHeader("Accept"), 64 | 'Accept-Language': getReqHeader("Accept-Language"), 65 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 66 | 'Connection': 'keep-alive', 67 | 'Cache-Control': 'max-age=0' 68 | } 69 | }; 70 | let token_url = auth_url + url.pathname + url.search 71 | return fetch(new Request(token_url, e.request), token_parameter) 72 | } 73 | 74 | url.hostname = hub_host; 75 | 76 | let parameter = { 77 | headers: { 78 | 'Host': hub_host, 79 | 'User-Agent': getReqHeader("User-Agent"), 80 | 'Accept': getReqHeader("Accept"), 81 | 'Accept-Language': getReqHeader("Accept-Language"), 82 | 'Accept-Encoding': getReqHeader("Accept-Encoding"), 83 | 'Connection': 'keep-alive', 84 | 'Cache-Control': 'max-age=0' 85 | }, 86 | cacheTtl: 3600 87 | }; 88 | 89 | if (e.request.headers.has("Authorization")) { 90 | parameter.headers.Authorization = getReqHeader("Authorization"); 91 | } 92 | 93 | let original_response = await fetch(new Request(url, e.request), parameter) 94 | let original_response_clone = original_response.clone(); 95 | let original_text = original_response_clone.body; 96 | let response_headers = original_response.headers; 97 | let new_response_headers = new Headers(response_headers); 98 | let status = original_response.status; 99 | 100 | if (new_response_headers.get("Www-Authenticate")) { 101 | let auth = new_response_headers.get("Www-Authenticate"); 102 | let re = new RegExp(auth_url, 'g'); 103 | new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); 104 | } 105 | 106 | if (new_response_headers.get("Location")) { 107 | return httpHandler(e.request, new_response_headers.get("Location")) 108 | } 109 | 110 | let response = new Response(original_text, { 111 | status, 112 | headers: new_response_headers 113 | }) 114 | return response; 115 | 116 | } 117 | 118 | 119 | /** 120 | * @param {Request} req 121 | * @param {string} pathname 122 | */ 123 | function httpHandler(req, pathname) { 124 | const reqHdrRaw = req.headers 125 | 126 | // preflight 127 | if (req.method === 'OPTIONS' && 128 | reqHdrRaw.has('access-control-request-headers') 129 | ) { 130 | return new Response(null, PREFLIGHT_INIT) 131 | } 132 | 133 | let rawLen = '' 134 | 135 | const reqHdrNew = new Headers(reqHdrRaw) 136 | 137 | const refer = reqHdrNew.get('referer') 138 | 139 | let urlStr = pathname 140 | 141 | const urlObj = newUrl(urlStr) 142 | 143 | /** @type {RequestInit} */ 144 | const reqInit = { 145 | method: req.method, 146 | headers: reqHdrNew, 147 | redirect: 'follow', 148 | body: req.body 149 | } 150 | return proxy(urlObj, reqInit, rawLen, 0) 151 | } 152 | 153 | 154 | /** 155 | * 156 | * @param {URL} urlObj 157 | * @param {RequestInit} reqInit 158 | */ 159 | async function proxy(urlObj, reqInit, rawLen) { 160 | const res = await fetch(urlObj.href, reqInit) 161 | const resHdrOld = res.headers 162 | const resHdrNew = new Headers(resHdrOld) 163 | 164 | // verify 165 | if (rawLen) { 166 | const newLen = resHdrOld.get('content-length') || '' 167 | const badLen = (rawLen !== newLen) 168 | 169 | if (badLen) { 170 | return makeRes(res.body, 400, { 171 | '--error': `bad len: ${newLen}, except: ${rawLen}`, 172 | 'access-control-expose-headers': '--error', 173 | }) 174 | } 175 | } 176 | const status = res.status 177 | resHdrNew.set('access-control-expose-headers', '*') 178 | resHdrNew.set('access-control-allow-origin', '*') 179 | resHdrNew.set('Cache-Control', 'max-age=1500') 180 | 181 | resHdrNew.delete('content-security-policy') 182 | resHdrNew.delete('content-security-policy-report-only') 183 | resHdrNew.delete('clear-site-data') 184 | 185 | return new Response(res.body, { 186 | status, 187 | headers: resHdrNew 188 | }) 189 | } --------------------------------------------------------------------------------