diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:20:59 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2026-02-23 15:20:59 +0000 |
| commit | 113928aa84894ea8f65c247d9987527e792b32a9 (patch) | |
| tree | ec967d6195d9f7ec4f061449596611afe3a0950f /src/git/handlers.rs | |
| parent | 26f608e5011b9d1ad6036da75b89272835e69695 (diff) | |
| parent | e0ad39a489b3398f8208713bf728db0cb11475b0 (diff) | |
Merge master into 3ca0-announcements-purgatory
Diffstat (limited to 'src/git/handlers.rs')
| -rw-r--r-- | src/git/handlers.rs | 108 |
1 files changed, 99 insertions, 9 deletions
diff --git a/src/git/handlers.rs b/src/git/handlers.rs index 13d6ba0..f43cbb6 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs | |||
| @@ -100,6 +100,42 @@ pub async fn handle_info_refs( | |||
| 100 | .unwrap()) | 100 | .unwrap()) |
| 101 | } | 101 | } |
| 102 | 102 | ||
| 103 | /// Build an HTTP 200 OK response with an ERR pkt-line for git protocol errors. | ||
| 104 | /// | ||
| 105 | /// Per the git smart HTTP protocol spec, protocol-level errors (like "not our ref") | ||
| 106 | /// should be returned as HTTP 200 OK with the error message in pkt-line format: | ||
| 107 | /// `PKT-LINE("ERR" SP explanation-text)` | ||
| 108 | /// | ||
| 109 | /// This allows git clients to properly parse and display the error message. | ||
| 110 | fn build_git_protocol_error_response( | ||
| 111 | service: GitService, | ||
| 112 | error_message: &str, | ||
| 113 | ) -> Response<Full<Bytes>> { | ||
| 114 | // Format: "ERR <message>\n" | ||
| 115 | let err_content = format!("ERR {}\n", error_message.trim()); | ||
| 116 | let err_pktline = PktLine::data(err_content.as_bytes()).encode(); | ||
| 117 | |||
| 118 | Response::builder() | ||
| 119 | .status(StatusCode::OK) | ||
| 120 | .header("content-type", service.result_content_type()) | ||
| 121 | .header("cache-control", "no-cache") | ||
| 122 | .body(Full::new(Bytes::from(err_pktline))) | ||
| 123 | .unwrap() | ||
| 124 | } | ||
| 125 | |||
| 126 | /// Check if a git process failure is a protocol error (vs transport error). | ||
| 127 | /// | ||
| 128 | /// Protocol errors are communicated via stderr when git exits with code 128. | ||
| 129 | /// These should be returned to the client as HTTP 200 with ERR pkt-line. | ||
| 130 | /// | ||
| 131 | /// Transport errors (process spawn failures, I/O errors, signals) should | ||
| 132 | /// remain as HTTP 500 errors. | ||
| 133 | fn is_git_protocol_error(exit_code: Option<i32>, stderr: &[u8]) -> bool { | ||
| 134 | // Git uses exit code 128 for protocol/usage errors | ||
| 135 | // If there's stderr content, it's a protocol error message | ||
| 136 | exit_code == Some(128) && !stderr.is_empty() | ||
| 137 | } | ||
| 138 | |||
| 103 | /// Handle POST /git-upload-pack (clone/fetch) | 139 | /// Handle POST /git-upload-pack (clone/fetch) |
| 104 | pub async fn handle_upload_pack( | 140 | pub async fn handle_upload_pack( |
| 105 | repo_path: PathBuf, | 141 | repo_path: PathBuf, |
| @@ -121,7 +157,10 @@ pub async fn handle_upload_pack( | |||
| 121 | stdin | 157 | stdin |
| 122 | .write_all(&request_body) | 158 | .write_all(&request_body) |
| 123 | .await | 159 | .await |
| 124 | .map_err(GitError::IoError)?; | 160 | .map_err(|e| { |
| 161 | error!("Failed to write to git upload-pack stdin: {}", e); | ||
| 162 | GitError::IoError(e) | ||
| 163 | })?; | ||
| 125 | // Close stdin to signal end of input | 164 | // Close stdin to signal end of input |
| 126 | drop(stdin); | 165 | drop(stdin); |
| 127 | } | 166 | } |
| @@ -135,7 +174,10 @@ pub async fn handle_upload_pack( | |||
| 135 | stdout | 174 | stdout |
| 136 | .read_to_end(&mut output) | 175 | .read_to_end(&mut output) |
| 137 | .await | 176 | .await |
| 138 | .map_err(GitError::IoError)?; | 177 | .map_err(|e| { |
| 178 | error!("Failed to read git upload-pack stdout: {}", e); | ||
| 179 | GitError::IoError(e) | ||
| 180 | })?; | ||
| 139 | } | 181 | } |
| 140 | 182 | ||
| 141 | if let Some(stderr) = git.take_stderr() { | 183 | if let Some(stderr) = git.take_stderr() { |
| @@ -143,14 +185,35 @@ pub async fn handle_upload_pack( | |||
| 143 | stderr | 185 | stderr |
| 144 | .read_to_end(&mut stderr_output) | 186 | .read_to_end(&mut stderr_output) |
| 145 | .await | 187 | .await |
| 146 | .map_err(GitError::IoError)?; | 188 | .map_err(|e| { |
| 189 | error!("Failed to read git upload-pack stderr: {}", e); | ||
| 190 | GitError::IoError(e) | ||
| 191 | })?; | ||
| 147 | } | 192 | } |
| 148 | 193 | ||
| 149 | // Wait for process | 194 | // Wait for process |
| 150 | let status = git.wait().await.map_err(GitError::IoError)?; | 195 | let status = git.wait().await.map_err(|e| { |
| 196 | error!("Failed to wait for git upload-pack process: {}", e); | ||
| 197 | GitError::IoError(e) | ||
| 198 | })?; | ||
| 151 | 199 | ||
| 152 | if !status.success() { | 200 | if !status.success() { |
| 153 | let stderr_str = String::from_utf8_lossy(&stderr_output); | 201 | let stderr_str = String::from_utf8_lossy(&stderr_output); |
| 202 | |||
| 203 | // Check if this is a git protocol error (exit code 128 with stderr) | ||
| 204 | // Protocol errors should be returned as HTTP 200 with ERR pkt-line | ||
| 205 | if is_git_protocol_error(status.code(), &stderr_output) { | ||
| 206 | warn!( | ||
| 207 | "Git upload-pack protocol error (returning ERR pkt-line): {}", | ||
| 208 | stderr_str | ||
| 209 | ); | ||
| 210 | return Ok(build_git_protocol_error_response( | ||
| 211 | GitService::UploadPack, | ||
| 212 | &stderr_str, | ||
| 213 | )); | ||
| 214 | } | ||
| 215 | |||
| 216 | // Transport errors (spawn failures, signals, etc.) remain as HTTP 500 | ||
| 154 | error!("Git upload-pack failed: {}", stderr_str); | 217 | error!("Git upload-pack failed: {}", stderr_str); |
| 155 | return Err(GitError::GitFailed(status.code())); | 218 | return Err(GitError::GitFailed(status.code())); |
| 156 | } | 219 | } |
| @@ -206,7 +269,7 @@ pub async fn handle_receive_pack( | |||
| 206 | } | 269 | } |
| 207 | 270 | ||
| 208 | // GRASP Authorization Check | 271 | // GRASP Authorization Check |
| 209 | info!( | 272 | debug!( |
| 210 | "Authorizing push for {} owned by {} via database query", | 273 | "Authorizing push for {} owned by {} via database query", |
| 211 | identifier, owner_pubkey | 274 | identifier, owner_pubkey |
| 212 | ); | 275 | ); |
| @@ -251,7 +314,10 @@ pub async fn handle_receive_pack( | |||
| 251 | stdin | 314 | stdin |
| 252 | .write_all(&request_body) | 315 | .write_all(&request_body) |
| 253 | .await | 316 | .await |
| 254 | .map_err(GitError::IoError)?; | 317 | .map_err(|e| { |
| 318 | error!("Failed to write to git receive-pack stdin: {}", e); | ||
| 319 | GitError::IoError(e) | ||
| 320 | })?; | ||
| 255 | drop(stdin); | 321 | drop(stdin); |
| 256 | } | 322 | } |
| 257 | 323 | ||
| @@ -264,7 +330,10 @@ pub async fn handle_receive_pack( | |||
| 264 | stdout | 330 | stdout |
| 265 | .read_to_end(&mut output) | 331 | .read_to_end(&mut output) |
| 266 | .await | 332 | .await |
| 267 | .map_err(GitError::IoError)?; | 333 | .map_err(|e| { |
| 334 | error!("Failed to read git receive-pack stdout: {}", e); | ||
| 335 | GitError::IoError(e) | ||
| 336 | })?; | ||
| 268 | } | 337 | } |
| 269 | 338 | ||
| 270 | if let Some(stderr) = git.take_stderr() { | 339 | if let Some(stderr) = git.take_stderr() { |
| @@ -272,14 +341,35 @@ pub async fn handle_receive_pack( | |||
| 272 | stderr | 341 | stderr |
| 273 | .read_to_end(&mut stderr_output) | 342 | .read_to_end(&mut stderr_output) |
| 274 | .await | 343 | .await |
| 275 | .map_err(GitError::IoError)?; | 344 | .map_err(|e| { |
| 345 | error!("Failed to read git receive-pack stderr: {}", e); | ||
| 346 | GitError::IoError(e) | ||
| 347 | })?; | ||
| 276 | } | 348 | } |
| 277 | 349 | ||
| 278 | // Wait for process | 350 | // Wait for process |
| 279 | let status = git.wait().await.map_err(GitError::IoError)?; | 351 | let status = git.wait().await.map_err(|e| { |
| 352 | error!("Failed to wait for git receive-pack process: {}", e); | ||
| 353 | GitError::IoError(e) | ||
| 354 | })?; | ||
| 280 | 355 | ||
| 281 | if !status.success() { | 356 | if !status.success() { |
| 282 | let stderr_str = String::from_utf8_lossy(&stderr_output); | 357 | let stderr_str = String::from_utf8_lossy(&stderr_output); |
| 358 | |||
| 359 | // Check if this is a git protocol error (exit code 128 with stderr) | ||
| 360 | // Protocol errors should be returned as HTTP 200 with ERR pkt-line | ||
| 361 | if is_git_protocol_error(status.code(), &stderr_output) { | ||
| 362 | warn!( | ||
| 363 | "Git receive-pack protocol error (returning ERR pkt-line): {}", | ||
| 364 | stderr_str | ||
| 365 | ); | ||
| 366 | return Ok(build_git_protocol_error_response( | ||
| 367 | GitService::ReceivePack, | ||
| 368 | &stderr_str, | ||
| 369 | )); | ||
| 370 | } | ||
| 371 | |||
| 372 | // Transport errors (spawn failures, signals, etc.) remain as HTTP 500 | ||
| 283 | error!("Git receive-pack failed: {}", stderr_str); | 373 | error!("Git receive-pack failed: {}", stderr_str); |
| 284 | return Err(GitError::GitFailed(status.code())); | 374 | return Err(GitError::GitFailed(status.code())); |
| 285 | } | 375 | } |