From 6ca96490c3413830fbb2d3d74e71408ec04883c5 Mon Sep 17 00:00:00 2001 From: Xi Xu Date: Tue, 22 Jul 2025 12:32:37 +0800 Subject: [PATCH] Improve Git protocol request and header handling Expanded detection of Git requests by checking for Git-specific content types and improved user agent checks. Updated header handling to copy all headers for Git operations, ensuring better protocol compliance, and preserved all upstream response headers for Git requests. Also fixed parsing of platform from URL and improved body handling for POST requests. --- src/index.js | 97 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/src/index.js b/src/index.js index 1e2a933..a8f206d 100644 --- a/src/index.js +++ b/src/index.js @@ -51,9 +51,9 @@ function isGitRequest(request, url) { return true; } - // Check for Git user agents + // Check for Git user agents (more comprehensive check) const userAgent = request.headers.get("User-Agent") || ""; - if (userAgent.includes("git/")) { + if (userAgent.includes("git/") || userAgent.startsWith("git/")) { return true; } @@ -63,6 +63,15 @@ function isGitRequest(request, url) { return service === "git-upload-pack" || service === "git-receive-pack"; } + // Check for Git-specific content types + const contentType = request.headers.get("Content-Type") || ""; + if ( + contentType.includes("git-upload-pack") || + contentType.includes("git-receive-pack") + ) { + return true; + } + return false; } @@ -147,7 +156,7 @@ async function handleRequest(request, env, ctx) { } // Parse platform and path - const [_, platform, ...pathParts] = url.pathname.split("/"); + const [_, platform] = url.pathname.split("/"); if (!platform || !CONFIG.PLATFORMS[platform]) { return new Response("Invalid or missing platform", { status: 400, @@ -182,43 +191,54 @@ async function handleRequest(request, env, ctx) { // Add body for POST requests (Git operations) if (request.method === "POST" && isGit) { - fetchOptions.body = await request.arrayBuffer(); + // For Git operations, we need to preserve the original body stream + fetchOptions.body = request.body; } // Set appropriate headers for Git vs regular requests if (isGit) { - // Copy important headers for Git operations - const gitHeaders = [ - "Content-Type", - "Content-Length", - "Authorization", - "User-Agent", - "Accept", - "Accept-Encoding", - "Git-Protocol", - ]; - - gitHeaders.forEach((header) => { - const value = request.headers.get(header); - if (value) { - fetchOptions.headers.set(header, value); + // For Git operations, copy all headers from the original request + // This ensures Git protocol compliance + for (const [key, value] of request.headers.entries()) { + // Skip headers that might cause issues with proxying + if ( + !["host", "connection", "upgrade", "proxy-connection"].includes( + key.toLowerCase() + ) + ) { + fetchOptions.headers.set(key, value); } - }); + } // Set Git-specific headers if not present if (!fetchOptions.headers.has("User-Agent")) { fetchOptions.headers.set("User-Agent", "git/2.34.1"); } - // Ensure proper content type for Git operations + // For Git upload-pack requests, ensure proper content type if ( request.method === "POST" && - !fetchOptions.headers.has("Content-Type") + url.pathname.endsWith("/git-upload-pack") ) { - fetchOptions.headers.set( - "Content-Type", - "application/x-git-upload-pack-request" - ); + if (!fetchOptions.headers.has("Content-Type")) { + fetchOptions.headers.set( + "Content-Type", + "application/x-git-upload-pack-request" + ); + } + } + + // For Git receive-pack requests, ensure proper content type + if ( + request.method === "POST" && + url.pathname.endsWith("/git-receive-pack") + ) { + if (!fetchOptions.headers.has("Content-Type")) { + fetchOptions.headers.set( + "Content-Type", + "application/x-git-receive-pack-request" + ); + } } } else { // Regular file download headers @@ -261,10 +281,12 @@ async function handleRequest(request, env, ctx) { CONFIG.TIMEOUT_SECONDS * 1000 ); - response = await fetch(targetUrl, { - ...fetchOptions, - signal: controller.signal, - }); + // For Git operations, don't use Cloudflare-specific options + const finalFetchOptions = isGit + ? { ...fetchOptions, signal: controller.signal } + : { ...fetchOptions, signal: controller.signal }; + + response = await fetch(targetUrl, finalFetchOptions); clearTimeout(timeoutId); @@ -333,17 +355,10 @@ async function handleRequest(request, env, ctx) { const headers = new Headers(response.headers); if (isGit) { - // For Git operations, preserve important headers - const preserveHeaders = [ - "Content-Type", - "Content-Length", - "Transfer-Encoding", - "Content-Encoding", - "Cache-Control", - ]; - - // Don't add security headers that might interfere with Git - headers.set("X-Content-Type-Options", "nosniff"); + // For Git operations, preserve all headers from the upstream response + // Git protocol is very sensitive to header changes + // Don't add any additional headers that might interfere with Git protocol + // The response headers from GitHub/GitLab should be passed through as-is } else { // Regular file download headers headers.set("Cache-Control", `public, max-age=${CONFIG.CACHE_DURATION}`);