mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 15:08:24 +00:00
fix(http): decompress gzip-encoded git request bodies
Modern git clients send Content-Encoding: gzip on POST requests to /git-upload-pack for efficiency. Without decompression, the compressed binary data was passed directly to git upload-pack, which expected pkt-line format, causing: fatal: protocol error: bad line length character: ?? error: RPC failed; HTTP 500 This was discovered in production when git clone requests consistently failed with HTTP 500 errors. The fix extracts the Content-Encoding header and uses flate2::GzDecoder to decompress gzip bodies before passing them to the git subprocess.
This commit is contained in:
+39
-3
@@ -159,14 +159,23 @@ impl Service<Request<Incoming>> for HttpService {
|
|||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.map(|s| s.to_string());
|
.map(|s| s.to_string());
|
||||||
|
|
||||||
|
// Extract Content-Encoding header to handle gzip-compressed request bodies
|
||||||
|
// Modern git clients send gzip-compressed POST bodies for efficiency
|
||||||
|
let content_encoding = req
|
||||||
|
.headers()
|
||||||
|
.get("content-encoding")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(|s| s.to_lowercase());
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"Git request: {} {} (npub={}, id={}, subpath={}, protocol={:?})",
|
"Git request: {} {} (npub={}, id={}, subpath={}, protocol={:?}, encoding={:?})",
|
||||||
method,
|
method,
|
||||||
path,
|
path,
|
||||||
npub,
|
npub,
|
||||||
identifier,
|
identifier,
|
||||||
subpath,
|
subpath,
|
||||||
git_protocol
|
git_protocol,
|
||||||
|
content_encoding
|
||||||
);
|
);
|
||||||
|
|
||||||
let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier);
|
let repo_path = git::resolve_repo_path(&git_data_path, &npub, &identifier);
|
||||||
@@ -175,12 +184,39 @@ impl Service<Request<Incoming>> for HttpService {
|
|||||||
|
|
||||||
return Box::pin(async move {
|
return Box::pin(async move {
|
||||||
// Collect request body once before the match statement
|
// Collect request body once before the match statement
|
||||||
let body_bytes = req
|
let raw_body = req
|
||||||
.collect()
|
.collect()
|
||||||
.await
|
.await
|
||||||
.map(|collected| collected.to_bytes())
|
.map(|collected| collected.to_bytes())
|
||||||
.unwrap_or_else(|_| Bytes::new());
|
.unwrap_or_else(|_| Bytes::new());
|
||||||
|
|
||||||
|
// Decompress gzip-encoded request bodies
|
||||||
|
// Git clients send Content-Encoding: gzip for POST requests
|
||||||
|
let body_bytes = if content_encoding.as_deref() == Some("gzip") {
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
let mut decoder = GzDecoder::new(&raw_body[..]);
|
||||||
|
let mut decompressed = Vec::new();
|
||||||
|
match decoder.read_to_end(&mut decompressed) {
|
||||||
|
Ok(_) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"Decompressed gzip body: {} -> {} bytes",
|
||||||
|
raw_body.len(),
|
||||||
|
decompressed.len()
|
||||||
|
);
|
||||||
|
Bytes::from(decompressed)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Failed to decompress gzip body: {}", e);
|
||||||
|
// Fall back to raw body (might work if not actually gzip)
|
||||||
|
raw_body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raw_body
|
||||||
|
};
|
||||||
|
|
||||||
let result = match (method.as_ref(), subpath.as_str()) {
|
let result = match (method.as_ref(), subpath.as_str()) {
|
||||||
// GET /info/refs?service=git-upload-pack or git-receive-pack
|
// GET /info/refs?service=git-upload-pack or git-receive-pack
|
||||||
(m, sp) if m == Method::GET && sp.starts_with("info/refs") => {
|
(m, sp) if m == Method::GET && sp.starts_with("info/refs") => {
|
||||||
|
|||||||
Reference in New Issue
Block a user