Last active
October 25, 2025 18:08
-
-
Save chenxuan520/f1861d22bd42ef7082380d3a819fefe0 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| // 利用CloudFlare搭建一个专属的Docker镜像加速和代理工具 | |
| // this origin file from https://github.com/cmliu/CF-Workers-docker.io | |
| // 教程为 https://wp.gxnas.com/14739.html | |
| // Docker镜像仓库主机地址 | |
| let hub_host = 'registry-1.docker.io'; | |
| // Docker认证服务器地址 | |
| const auth_url = 'https://auth.docker.io'; | |
| // 自定义的工作服务器地址 | |
| let workers_url = 'https://docker.011203.xyz/'; | |
| let 屏蔽爬虫UA = ['netcraft']; | |
| // 根据主机名选择对应的上游地址 | |
| function routeByHosts(host) { | |
| // 定义路由表 | |
| const routes = { | |
| // 生产环境 | |
| "quay": "quay.io", | |
| "gcr": "gcr.io", | |
| "k8s-gcr": "k8s.gcr.io", | |
| "k8s": "registry.k8s.io", | |
| "ghcr": "ghcr.io", | |
| "cloudsmith": "docker.cloudsmith.io", | |
| "nvcr": "nvcr.io", | |
| // 测试环境 | |
| "test": "registry-1.docker.io", | |
| }; | |
| if (host in routes) return [ routes[host], false ]; | |
| else return [ hub_host, true ]; | |
| } | |
| /** @type {RequestInit} */ | |
| const PREFLIGHT_INIT = { | |
| // 预检请求配置 | |
| headers: new Headers({ | |
| 'access-control-allow-origin': '*', // 允许所有来源 | |
| 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法 | |
| '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 }) // 返回新构造的响应 | |
| } | |
| /** | |
| * 构造新的URL对象 | |
| * @param {string} urlStr URL字符串 | |
| */ | |
| function newUrl(urlStr) { | |
| try { | |
| return new URL(urlStr) // 尝试构造新的URL对象 | |
| } catch (err) { | |
| return null // 构造失败返回null | |
| } | |
| } | |
| function isUUID(uuid) { | |
| // 定义一个正则表达式来匹配 UUID 格式 | |
| const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; | |
| // 使用正则表达式测试 UUID 字符串 | |
| return uuidRegex.test(uuid); | |
| } | |
| async function nginx() { | |
| const text = ` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Welcome to nginx!</title> | |
| <style> | |
| body { | |
| width: 35em; | |
| margin: 0 auto; | |
| font-family: Tahoma, Verdana, Arial, sans-serif; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Welcome to nginx!</h1> | |
| <p>If you see this page, the nginx web server is successfully installed and | |
| working. Further configuration is required.</p> | |
| <p>For online documentation and support please refer to | |
| <a href="http://nginx.org/">nginx.org</a>.<br/> | |
| Commercial support is available at | |
| <a href="http://nginx.com/">nginx.com</a>.</p> | |
| <p><em>Thank you for using nginx.</em></p> | |
| </body> | |
| </html> | |
| ` | |
| return text; | |
| } | |
| async function searchInterface() { | |
| const text = ` | |
| <html> | |
| <head> | |
| <title>Docker Hub Search</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100vh; | |
| margin: 0; | |
| background-color: #0d1117; | |
| color: #c9d1d9; | |
| position: relative; | |
| } | |
| .logo { | |
| margin-bottom: 20px; | |
| cursor: pointer; /* 添加鼠标指针样式,表示可点击 */ | |
| } | |
| h2 { | |
| color: #fff; | |
| font-weight: 500; | |
| margin: 10px 0; | |
| text-align: center; /* 新增:使h2文本居中 */ | |
| } | |
| .search-container, .image-url-container { | |
| display: flex; | |
| align-items: center; | |
| } | |
| #search-input, #image-url-input { | |
| padding: 10px 12px; | |
| font-size: 14px; | |
| line-height: 20px; | |
| color: #c9d1d9; | |
| background-color: #161b22; | |
| border: 1px solid #30363d; | |
| border-radius: 6px 0 0 6px; | |
| width: 300px; | |
| margin-right: 0; | |
| outline: none; | |
| } | |
| #search-input:focus, #image-url-input:focus { | |
| border-color: #1f6feb; | |
| box-shadow: 0 0 0 3px rgba(31, 111, 235, 0.3); | |
| } | |
| #search-button { | |
| padding: 10px 16px; | |
| background-color: #238636; | |
| border: 1px solid rgba(240, 246, 252, 0.1); | |
| border-radius: 0 6px 6px 0; | |
| cursor: pointer; | |
| color: #fff; | |
| font-size: 14px; | |
| font-weight: 500; | |
| line-height: 20px; | |
| transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1); | |
| transition-property: color, background-color, border-color; | |
| } | |
| #search-button:hover { | |
| background-color: #2ea043; | |
| } | |
| #generate-button { | |
| padding: 10px 16px; | |
| background-color: #1f6feb; | |
| border: 1px solid rgba(240, 246, 252, 0.1); | |
| border-radius: 0 6px 6px 0; | |
| cursor: pointer; | |
| color: #fff; | |
| font-size: 14px; | |
| font-weight: 500; | |
| line-height: 20px; | |
| transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1); | |
| transition-property: color, background-color, border-color; | |
| } | |
| #generate-button:hover { | |
| background-color: #388bfd; | |
| } | |
| #search-button svg, #generate-button svg { | |
| width: 16px; | |
| height: 16px; | |
| fill: currentColor; | |
| } | |
| .image-url-container { | |
| margin-top: 20px; | |
| } | |
| .command-container { | |
| margin-top: 15px; | |
| display: none; | |
| background-color: rgba(255, 255, 255, 0.05); | |
| padding: 10px 15px; | |
| border-radius: 6px; | |
| box-sizing: border-box; | |
| position: relative; | |
| min-width: 300px; | |
| width: fit-content; | |
| max-width: 100%; | |
| } | |
| #clone-command { | |
| text-align: left; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |
| font-size: 14px; | |
| color: #c9d1d9; | |
| white-space: nowrap; | |
| padding-right: 30px; | |
| } | |
| #copy-button { | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| color: #8b949e; | |
| transition: color 0.2s; | |
| padding: 0; | |
| position: absolute; | |
| right: 10px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| #copy-button:hover { | |
| color: #c9d1d9; | |
| } | |
| #copy-button svg { | |
| width: 16px; | |
| height: 16px; | |
| fill: currentColor; | |
| } | |
| h5 { | |
| color: #8b949e; | |
| font-weight: 400; | |
| font-size: 14px; | |
| } | |
| .github-corner { | |
| position: absolute; | |
| top: 0; | |
| right: 0; | |
| border: 0; | |
| z-index: 100; | |
| } | |
| .github-corner svg { | |
| fill: #161b22; | |
| color: #fff; | |
| border: 0; | |
| } | |
| .github-corner:hover .octo-arm { | |
| animation: octocat-wave 560ms ease-in-out; | |
| } | |
| @keyframes octocat-wave { | |
| 0%, 100% { | |
| transform: rotate(0); | |
| } | |
| 20%, 60% { | |
| transform: rotate(-25deg); | |
| } | |
| 40%, 80% { | |
| transform: rotate(10deg); | |
| } | |
| } | |
| /* 新增:默认隐藏搜索相关元素 */ | |
| #search-section { | |
| display: none; | |
| opacity: 0; | |
| transition: opacity 0.5s ease-in-out; | |
| } | |
| /* 新增:显示搜索框的动画效果 */ | |
| #search-section.show { | |
| display: block; | |
| opacity: 1; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <a href="https://gist.github.com/chenxuan520/f1861d22bd42ef7082380d3a819fefe0" class="github-corner" aria-label="View source on GitHub"> | |
| <svg width="80" height="80" viewBox="0 0 250 250" style="fill:#161b22; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"> | |
| <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> | |
| <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path> | |
| <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path> | |
| </svg> | |
| </a> | |
| <div class="logo"> | |
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="#ffffff" width="100" height="75"> | |
| <path d="M23.763 6.886c-.065-.053-.673-.512-1.954-.512-.32 0-.659.03-1.01.087-.248-1.703-1.651-2.533-1.716-2.57l-.345-.2-.227.328a4.596 4.596 0 0 0-.611 1.433c-.23.972-.09 1.884.403 2.666-.596.331-1.546.418-1.744.42H.752a.753.753 0 0 0-.75.749c-.007 1.456.233 2.864.692 4.07.545 1.43 1.355 2.483 2.409 3.13 1.181.725 3.104 1.14 5.276 1.14 1.016 0 2.03-.092 2.93-.266 1.417-.273 2.705-.742 3.826-1.391a10.497 10.497 0 0 0 2.61-2.14c1.252-1.42 1.998-3.005 2.553-4.408.075.003.148.005.221.005 1.371 0 2.215-.55 2.68-1.01.505-.5.685-.998.704-1.053L24 7.076l-.237-.19Z"> | |
| </path> | |
| <path d="M2.216 8.075h2.119a.186.186 0 0 0 .185-.186V6a.186.186 0 0 0-.185-.186H2.216A.186.186 0 0 0 2.031 6v1.89c0 .103.083.186.185.186Zm2.92 0h2.118a.185.185 0 0 0 .185-.186V6a.185.185 0 0 0-.185-.186H5.136A.185.185 0 0 0 4.95 6v1.89c0 .103.083.186.186.186Zm2.964 0h2.118a.186.186 0 0 0 .185-.186V6a.186.186 0 0 0-.185-.186H8.1A.185.185 0 0 0 7.914 6v1.89c0 .103.083.186.186.186Zm2.928 0h2.119a.185.185 0 0 0 .185-.186V6a.185.185 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm-5.892-2.72h2.118a.185.185 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186H5.136a.186.186 0 0 0-.186.186v1.89c0 .103.083.186.186.186Zm2.964 0h2.118a.186.186 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186H8.1a.186.186 0 0 0-.186.186v1.89c0 .103.083.186.186.186Zm2.928 0h2.119a.185.185 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm0-2.72h2.119a.186.186 0 0 0 .185-.186V.56a.185.185 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm2.955 5.44h2.118a.185.185 0 0 0 .186-.186V6a.185.185 0 0 0-.186-.186h-2.118a.185.185 0 0 0-.185.186v1.89c0 .103.083.186.185.186Z"> | |
| </path> | |
| </svg> | |
| </div> | |
| <!-- 新增:将搜索相关元素包装在一个容器中 --> | |
| <div id="search-section"> | |
| <h2>Search docker</h2> | |
| <br> | |
| <div class="search-container"> | |
| <input type="text" id="search-input" placeholder="Search Docker Hub"> | |
| <button id="search-button"> | |
| <svg focusable="false" aria-hidden="true" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" fill="currentColor"> | |
| </path> | |
| </svg> | |
| </button> | |
| </div> | |
| <br> | |
| </div> | |
| <h2>Pull docker</h2> | |
| <div class="image-url-container"> | |
| <input type="text" id="image-url-input" placeholder="Enter image name (e.g. author/image:tag)"> | |
| <button id="generate-button"> | |
| <svg focusable="false" aria-hidden="true" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M8 1a.5.5 0 0 1 .5.5v9.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 11.293V1.5A.5.5 0 0 1 8 1z" fill="currentColor"> | |
| </path> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="command-container"> | |
| <div id="clone-command"></div> | |
| <button id="copy-button"> | |
| <svg focusable="false" aria-hidden="true" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2zm2-1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H6z" fill="currentColor"> | |
| </path> | |
| </svg> | |
| </button> | |
| </div> | |
| <script> | |
| // 新增:点击logo显示搜索框 | |
| document.querySelector('.logo').addEventListener('click', function() { | |
| const searchSection = document.getElementById('search-section'); | |
| searchSection.classList.toggle('show'); | |
| // 如果显示了搜索框,自动聚焦到搜索输入框 | |
| if (searchSection.classList.contains('show')) { | |
| document.getElementById('search-input').focus(); | |
| } | |
| }); | |
| function performSearch() { | |
| const query = document.getElementById('search-input').value; | |
| if (query) { | |
| window.location.href = '/search?q=' + encodeURIComponent(query); | |
| } | |
| } | |
| document.getElementById('search-button').addEventListener('click', performSearch); | |
| document.getElementById('search-input').addEventListener('keypress', function (event) { | |
| if (event.key === 'Enter') { | |
| performSearch(); | |
| } | |
| }); | |
| function generateCommand() { | |
| const imageUrl = document.getElementById('image-url-input').value.trim(); | |
| const cloneCommandDiv = document.getElementById('clone-command'); | |
| const commandContainer = document.querySelector('.command-container'); | |
| if (imageUrl) { | |
| // 处理不带域名的情况,自动添加域名前缀 | |
| const domain = window.location.host; | |
| let fullImageUrl = imageUrl; | |
| if (!imageUrl.startsWith('http') && !imageUrl.startsWith(domain)) { | |
| // 确保没有多余的斜杠 | |
| if (imageUrl.startsWith('/')) { | |
| fullImageUrl = domain + imageUrl; | |
| } else { | |
| fullImageUrl = domain + '/' + imageUrl; | |
| } | |
| } | |
| const command = 'docker pull '+fullImageUrl+';' | |
| +'<br>docker tag '+fullImageUrl+' '+ imageUrl+';' | |
| +'<br>docker rmi '+fullImageUrl+';'; | |
| cloneCommandDiv.innerHTML = command; | |
| commandContainer.style.display = 'block'; | |
| } else { | |
| commandContainer.style.display = 'none'; | |
| } | |
| } | |
| document.getElementById('generate-button').addEventListener('click', generateCommand); | |
| document.getElementById('image-url-input').addEventListener('keypress', function (event) { | |
| if (event.key === 'Enter') { | |
| generateCommand(); | |
| } | |
| }); | |
| document.getElementById('copy-button').addEventListener('click', function () { | |
| const cloneCommandDiv = document.getElementById('clone-command'); | |
| const command = cloneCommandDiv.textContent; | |
| if (command) { | |
| const textArea = document.createElement('textarea'); | |
| textArea.value = command; | |
| document.body.appendChild(textArea); | |
| textArea.select(); | |
| document.execCommand('copy'); | |
| document.body.removeChild(textArea); | |
| // 显示复制成功的反馈 | |
| const originalSVG = this.innerHTML; | |
| this.innerHTML = '<svg focusable="false" aria-hidden="true" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" fill="currentColor"></path></svg>'; | |
| setTimeout(() => { | |
| this.innerHTML = originalSVG; | |
| }, 2000); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| `; | |
| return text; | |
| } | |
| export default { | |
| async fetch(request, env, ctx) { | |
| const getReqHeader = (key) => request.headers.get(key); // 获取请求头 | |
| let url = new URL(request.url); // 解析请求URL | |
| const userAgentHeader = request.headers.get('User-Agent'); | |
| const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null"; | |
| if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA)); | |
| workers_url = `https://${url.hostname}`; | |
| const pathname = url.pathname; | |
| // 获取请求参数中的 ns | |
| const ns = url.searchParams.get('ns'); | |
| const hostname = url.searchParams.get('hubhost') || url.hostname; | |
| const hostTop = hostname.split('.')[0]; // 获取主机名的第一部分 | |
| let checkHost; // 在这里定义 checkHost 变量 | |
| // 如果存在 ns 参数,优先使用它来确定 hub_host | |
| if (ns) { | |
| if (ns === 'docker.io') { | |
| hub_host = 'registry-1.docker.io'; // 设置上游地址为 registry-1.docker.io | |
| } else { | |
| hub_host = ns; // 直接使用 ns 作为 hub_host | |
| } | |
| } else { | |
| checkHost = routeByHosts(hostTop); | |
| hub_host = checkHost[0]; // 获取上游地址 | |
| } | |
| const fakePage = checkHost ? checkHost[1] : false; // 确保 fakePage 不为 undefined | |
| console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n首页: ${fakePage}`); | |
| const isUuid = isUUID(pathname.split('/')[1].split('/')[0]); | |
| if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0) { | |
| // 首页改成一个nginx伪装页 | |
| return new Response(await nginx(), { | |
| headers: { | |
| 'Content-Type': 'text/html; charset=UTF-8', | |
| }, | |
| }); | |
| } | |
| const conditions = [ | |
| isUuid, | |
| pathname.includes('/_'), | |
| pathname.includes('/r/'), | |
| pathname.includes('/v2/repositories'), | |
| pathname.includes('/v2/user'), | |
| pathname.includes('/v2/orgs'), | |
| pathname.includes('/v2/_catalog'), | |
| pathname.includes('/v2/categories'), | |
| pathname.includes('/v2/feature-flags'), | |
| pathname.includes('search'), | |
| pathname.includes('source'), | |
| pathname == '/', | |
| pathname == '/favicon.ico', | |
| pathname == '/auth/profile', | |
| ]; | |
| if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) { | |
| if (env.URL302) { | |
| return Response.redirect(env.URL302, 302); | |
| } else if (env.URL) { | |
| if (env.URL.toLowerCase() == 'nginx') { | |
| //首页改成一个nginx伪装页 | |
| return new Response(await nginx(), { | |
| headers: { | |
| 'Content-Type': 'text/html; charset=UTF-8', | |
| }, | |
| }); | |
| } else return fetch(new Request(env.URL, request)); | |
| } else if (url.pathname == '/'){ | |
| return new Response(await searchInterface(), { | |
| headers: { | |
| 'Content-Type': 'text/html; charset=UTF-8', | |
| }, | |
| }); | |
| } else if (url.pathname == '/signup' || url.pathname == '/login') { | |
| return Response.redirect('https://hub.docker.com/',302) | |
| } | |
| const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search); | |
| // 复制原始请求的标头 | |
| const headers = new Headers(request.headers); | |
| // 确保 Host 头部被替换为 hub.docker.com | |
| headers.set('Host', 'registry.hub.docker.com'); | |
| const newRequest = new Request(newUrl, { | |
| method: request.method, | |
| headers: headers, | |
| body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null, | |
| redirect: 'follow' | |
| }); | |
| return fetch(newRequest); | |
| } | |
| // 修改包含 %2F 和 %3A 的请求 | |
| if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) { | |
| let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F'); | |
| url = new URL(modifiedUrl); | |
| console.log(`handle_url: ${url}`); | |
| } | |
| // 处理token请求 | |
| if (url.pathname.includes('/token')) { | |
| let token_parameter = { | |
| headers: { | |
| 'Host': 'auth.docker.io', | |
| 'User-Agent': getReqHeader("User-Agent"), | |
| 'Accept': getReqHeader("Accept"), | |
| 'Accept-Language': getReqHeader("Accept-Language"), | |
| 'Accept-Encoding': getReqHeader("Accept-Encoding"), | |
| 'Connection': 'keep-alive', | |
| 'Cache-Control': 'max-age=0' | |
| } | |
| }; | |
| let token_url = auth_url + url.pathname + url.search; | |
| return fetch(new Request(token_url, request), token_parameter); | |
| } | |
| // 修改 /v2/ 请求路径 | |
| if ( hub_host == 'registry-1.docker.io' && /^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) { | |
| //url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/'); | |
| url.pathname = '/v2/library/' + url.pathname.split('/v2/')[1]; | |
| console.log(`modified_url: ${url.pathname}`); | |
| } | |
| // 更改请求的主机名 | |
| url.hostname = hub_host; | |
| // 构造请求参数 | |
| let parameter = { | |
| headers: { | |
| 'Host': hub_host, | |
| 'User-Agent': getReqHeader("User-Agent"), | |
| 'Accept': getReqHeader("Accept"), | |
| 'Accept-Language': getReqHeader("Accept-Language"), | |
| 'Accept-Encoding': getReqHeader("Accept-Encoding"), | |
| 'Connection': 'keep-alive', | |
| 'Cache-Control': 'max-age=0' | |
| }, | |
| cacheTtl: 3600 // 缓存时间 | |
| }; | |
| // 添加Authorization头 | |
| if (request.headers.has("Authorization")) { | |
| parameter.headers.Authorization = getReqHeader("Authorization"); | |
| } | |
| // 发起请求并处理响应 | |
| let original_response = await fetch(new Request(url, request), parameter); | |
| let original_response_clone = original_response.clone(); | |
| let original_text = original_response_clone.body; | |
| let response_headers = original_response.headers; | |
| let new_response_headers = new Headers(response_headers); | |
| let status = original_response.status; | |
| // 修改 Www-Authenticate 头 | |
| if (new_response_headers.get("Www-Authenticate")) { | |
| let auth = new_response_headers.get("Www-Authenticate"); | |
| let re = new RegExp(auth_url, 'g'); | |
| new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); | |
| } | |
| // 处理重定向 | |
| if (new_response_headers.get("Location")) { | |
| return httpHandler(request, new_response_headers.get("Location")); | |
| } | |
| // 返回修改后的响应 | |
| let response = new Response(original_text, { | |
| status, | |
| headers: new_response_headers | |
| }); | |
| return response; | |
| } | |
| }; | |
| /** | |
| * 处理HTTP请求 | |
| * @param {Request} req 请求对象 | |
| * @param {string} pathname 请求路径 | |
| */ | |
| function httpHandler(req, pathname) { | |
| const reqHdrRaw = req.headers; | |
| // 处理预检请求 | |
| if (req.method === 'OPTIONS' && | |
| reqHdrRaw.has('access-control-request-headers') | |
| ) { | |
| return new Response(null, PREFLIGHT_INIT); | |
| } | |
| let rawLen = ''; | |
| const reqHdrNew = new Headers(reqHdrRaw); | |
| // 修复 Missing x-amz-content-sha256 问题 | |
| reqHdrNew.delete("Authorization"); | |
| const refer = reqHdrNew.get('referer'); | |
| let urlStr = pathname; | |
| const urlObj = newUrl(urlStr); | |
| /** @type {RequestInit} */ | |
| const reqInit = { | |
| method: req.method, | |
| headers: reqHdrNew, | |
| redirect: 'follow', | |
| body: req.body | |
| }; | |
| return proxy(urlObj, reqInit, rawLen); | |
| } | |
| /** | |
| * 代理请求 | |
| * @param {URL} urlObj URL对象 | |
| * @param {RequestInit} reqInit 请求初始化对象 | |
| * @param {string} rawLen 原始长度 | |
| */ | |
| async function proxy(urlObj, reqInit, rawLen) { | |
| const res = await fetch(urlObj.href, reqInit); | |
| const resHdrOld = res.headers; | |
| const resHdrNew = new Headers(resHdrOld); | |
| // 验证长度 | |
| 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.set('Cache-Control', 'max-age=1500'); | |
| // 删除不必要的头 | |
| 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 | |
| }); | |
| } | |
| async function ADD(envadd) { | |
| var addtext = envadd.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 | |
| if (addtext.charAt(0) == ',') addtext = addtext.slice(1); | |
| if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, addtext.length - 1); | |
| const add = addtext.split(','); | |
| return add; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment