From 3a7fa1d1288c28eae0ee58b4c448c672ec3b69c2 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Tue, 27 Jan 2026 19:43:41 +0000 Subject: fix: return HTTP 200 with ERR pkt-line for git protocol errors Previously, all git upload-pack/receive-pack failures returned HTTP 500, but the git smart HTTP protocol requires protocol-level errors (like "not our ref") to be returned as HTTP 200 OK with an ERR pkt-line in the response body. Changes: - Add build_git_protocol_error_response() to create HTTP 200 responses with properly formatted ERR pkt-line ("ERR \n") - Add is_git_protocol_error() to detect protocol errors (exit code 128 with stderr content) vs transport errors - Update handle_upload_pack() and handle_receive_pack() to return protocol errors as HTTP 200 with ERR pkt-line - Keep HTTP 500 for actual transport errors (spawn failures, I/O errors, signals) This allows git clients to properly parse and display protocol error messages instead of seeing generic HTTP 500 errors. --- src/git/handlers.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) (limited to 'src/git') diff --git a/src/git/handlers.rs b/src/git/handlers.rs index 017eee4..e3a6ad4 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -99,6 +99,42 @@ pub async fn handle_info_refs( .unwrap()) } +/// Build an HTTP 200 OK response with an ERR pkt-line for git protocol errors. +/// +/// Per the git smart HTTP protocol spec, protocol-level errors (like "not our ref") +/// should be returned as HTTP 200 OK with the error message in pkt-line format: +/// `PKT-LINE("ERR" SP explanation-text)` +/// +/// This allows git clients to properly parse and display the error message. +fn build_git_protocol_error_response( + service: GitService, + error_message: &str, +) -> Response> { + // Format: "ERR \n" + let err_content = format!("ERR {}\n", error_message.trim()); + let err_pktline = PktLine::data(err_content.as_bytes()).encode(); + + Response::builder() + .status(StatusCode::OK) + .header("content-type", service.result_content_type()) + .header("cache-control", "no-cache") + .body(Full::new(Bytes::from(err_pktline))) + .unwrap() +} + +/// Check if a git process failure is a protocol error (vs transport error). +/// +/// Protocol errors are communicated via stderr when git exits with code 128. +/// These should be returned to the client as HTTP 200 with ERR pkt-line. +/// +/// Transport errors (process spawn failures, I/O errors, signals) should +/// remain as HTTP 500 errors. +fn is_git_protocol_error(exit_code: Option, stderr: &[u8]) -> bool { + // Git uses exit code 128 for protocol/usage errors + // If there's stderr content, it's a protocol error message + exit_code == Some(128) && !stderr.is_empty() +} + /// Handle POST /git-upload-pack (clone/fetch) pub async fn handle_upload_pack( repo_path: PathBuf, @@ -150,6 +186,21 @@ pub async fn handle_upload_pack( if !status.success() { let stderr_str = String::from_utf8_lossy(&stderr_output); + + // Check if this is a git protocol error (exit code 128 with stderr) + // Protocol errors should be returned as HTTP 200 with ERR pkt-line + if is_git_protocol_error(status.code(), &stderr_output) { + warn!( + "Git upload-pack protocol error (returning ERR pkt-line): {}", + stderr_str + ); + return Ok(build_git_protocol_error_response( + GitService::UploadPack, + &stderr_str, + )); + } + + // Transport errors (spawn failures, signals, etc.) remain as HTTP 500 error!("Git upload-pack failed: {}", stderr_str); return Err(GitError::GitFailed(status.code())); } @@ -277,6 +328,21 @@ pub async fn handle_receive_pack( if !status.success() { let stderr_str = String::from_utf8_lossy(&stderr_output); + + // Check if this is a git protocol error (exit code 128 with stderr) + // Protocol errors should be returned as HTTP 200 with ERR pkt-line + if is_git_protocol_error(status.code(), &stderr_output) { + warn!( + "Git receive-pack protocol error (returning ERR pkt-line): {}", + stderr_str + ); + return Ok(build_git_protocol_error_response( + GitService::ReceivePack, + &stderr_str, + )); + } + + // Transport errors (spawn failures, signals, etc.) remain as HTTP 500 error!("Git receive-pack failed: {}", stderr_str); return Err(GitError::GitFailed(status.code())); } -- cgit v1.2.3