diff --git a/src/routes/ft/get_file.rs b/src/routes/ft/get_file.rs index b6d69b2..048160e 100644 --- a/src/routes/ft/get_file.rs +++ b/src/routes/ft/get_file.rs @@ -141,11 +141,19 @@ async fn ft_get_file_inner(state: &AppState, path: &str) -> Result 0 { - compressed_size as i64 - } else { - s3_content_length.unwrap_or(0) - }; + // Use S3 content-length as source of truth for Content-Length header. + // DB compressed_size can diverge from actual S3 object size (e.g. different gzip + // encodings across uploads with same content hash). + let content_length = s3_content_length.unwrap_or(compressed_size as i64); + if compressed_size > 0 + && let Some(s3_len) = s3_content_length + && s3_len != compressed_size as i64 + { + error!( + "compressed_size mismatch for hash {}: db={} s3={}", + hash, compressed_size, s3_len + ); + } debug!( "GET {} returning Last-Modified: {} (unix: {})", @@ -208,11 +216,16 @@ async fn serve_file(state: &AppState, path: &str) -> Result> { let _ = guard.release().await; - let content_length = if compressed_size > 0 { - compressed_size as i64 - } else { - s3_content_length.unwrap_or(0) - }; + let content_length = s3_content_length.unwrap_or(compressed_size as i64); + if compressed_size > 0 + && let Some(s3_len) = s3_content_length + && s3_len != compressed_size as i64 + { + error!( + "compressed_size mismatch for hash {}: db={} s3={}", + hash, compressed_size, s3_len + ); + } let body = Body::new(byte_stream.into_inner()); Ok(build_ft_file_response( diff --git a/src/routes/ft/head_file.rs b/src/routes/ft/head_file.rs index 425a02c..e1092a3 100644 --- a/src/routes/ft/head_file.rs +++ b/src/routes/ft/head_file.rs @@ -113,17 +113,20 @@ async fn ft_head_file_inner(state: &AppState, path: &str) -> Result 0 { - compressed_size as i64 - } else { - match state.s3storage.object_exists_with_size(&hash).await { - Ok(Some(size)) => size, - _ => 0, + // Always query S3 for the true object size. DB compressed_size can diverge from + // actual S3 object size (e.g. different gzip encodings across uploads with same + // content hash). Falls back to DB value if S3 HEAD fails. + let content_length = match state.s3storage.object_exists_with_size(&hash).await { + Ok(Some(s3_size)) => { + if compressed_size > 0 && s3_size != compressed_size as i64 { + error!( + "compressed_size mismatch for hash {}: db={} s3={}", + hash, compressed_size, s3_size + ); + } + s3_size } + _ => compressed_size as i64, }; Ok(build_ft_file_response(