mirror of
https://codeberg.org/Toasterson/ips.git
synced 2026-04-10 13:20:42 +00:00
Add content-type handling for JSON catalog artifacts and weak ETag for manifests
- Ensured correct `Content-Type` header for catalog artifacts (`catalog.attrs` and `catalog.*`) in HTTP responses. - Added SHA-1 based weak ETag generation for manifest responses to improve caching and legacy compatibility. - Updated `integration_tests` to validate content-type and ETag correctness. - Added new dependency `sha1` for hashing support.
This commit is contained in:
parent
e87d1a3166
commit
cff3d5d960
5 changed files with 38 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2120,6 +2120,7 @@ dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha1",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
nix = { version = "0.30", features = ["signal", "process", "user", "fs"] }
|
nix = { version = "0.30", features = ["signal", "process", "user", "fs"] }
|
||||||
|
sha1 = "0.10"
|
||||||
|
|
||||||
# Telemetry
|
# Telemetry
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use crate::repo::DepotRepo;
|
||||||
use crate::errors::DepotError;
|
use crate::errors::DepotError;
|
||||||
use tower_http::services::ServeFile;
|
use tower_http::services::ServeFile;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
|
use axum::http::header;
|
||||||
|
|
||||||
pub async fn get_catalog_v1(
|
pub async fn get_catalog_v1(
|
||||||
State(repo): State<Arc<DepotRepo>>,
|
State(repo): State<Arc<DepotRepo>>,
|
||||||
|
|
@ -19,7 +20,14 @@ pub async fn get_catalog_v1(
|
||||||
let result = service.oneshot(req).await;
|
let result = service.oneshot(req).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(res) => Ok(res.into_response()),
|
Ok(mut res) => {
|
||||||
|
// Ensure correct content-type for JSON catalog artifacts regardless of file extension
|
||||||
|
let is_catalog_json = filename == "catalog.attrs" || filename.starts_with("catalog.");
|
||||||
|
if is_catalog_json {
|
||||||
|
res.headers_mut().insert(header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"));
|
||||||
|
}
|
||||||
|
Ok(res.into_response())
|
||||||
|
},
|
||||||
Err(e) => Err(DepotError::Server(e.to_string())),
|
Err(e) => Err(DepotError::Server(e.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::repo::DepotRepo;
|
||||||
use crate::errors::DepotError;
|
use crate::errors::DepotError;
|
||||||
use libips::fmri::Fmri;
|
use libips::fmri::Fmri;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use sha1::Digest as _;
|
||||||
|
|
||||||
pub async fn get_manifest(
|
pub async fn get_manifest(
|
||||||
State(repo): State<Arc<DepotRepo>>,
|
State(repo): State<Arc<DepotRepo>>,
|
||||||
|
|
@ -16,9 +17,16 @@ pub async fn get_manifest(
|
||||||
let fmri = Fmri::from_str(&fmri_str).map_err(|e| DepotError::Repo(libips::repository::RepositoryError::Other(e.to_string())))?;
|
let fmri = Fmri::from_str(&fmri_str).map_err(|e| DepotError::Repo(libips::repository::RepositoryError::Other(e.to_string())))?;
|
||||||
|
|
||||||
let content = repo.get_manifest_text(&publisher, &fmri)?;
|
let content = repo.get_manifest_text(&publisher, &fmri)?;
|
||||||
|
// Compute weak ETag from SHA-1 of manifest content (legacy friendly)
|
||||||
|
let mut hasher = sha1::Sha1::new();
|
||||||
|
hasher.update(content.as_bytes());
|
||||||
|
let etag = format!("\"{}\"", format!("{:x}", hasher.finalize()));
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
[(header::CONTENT_TYPE, "text/plain")],
|
[
|
||||||
content
|
(header::CONTENT_TYPE, "text/plain"),
|
||||||
|
(header::ETAG, etag.as_str()),
|
||||||
|
],
|
||||||
|
content,
|
||||||
).into_response())
|
).into_response())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,4 +230,21 @@ async fn test_ini_only_repo_serving_catalog() {
|
||||||
let body = resp.text().await.unwrap();
|
let body = resp.text().await.unwrap();
|
||||||
assert!(body.contains("package-count"));
|
assert!(body.contains("package-count"));
|
||||||
assert!(body.contains("parts"));
|
assert!(body.contains("parts"));
|
||||||
|
|
||||||
|
// Also fetch individual catalog parts
|
||||||
|
for part in ["catalog.base.C", "catalog.dependency.C", "catalog.summary.C"].iter() {
|
||||||
|
let url = format!("{}/{}/catalog/1/{}", base_url, publisher, part);
|
||||||
|
let resp = client.get(&url).send().await.unwrap();
|
||||||
|
assert!(resp.status().is_success(), "{} status: {:?}", part, resp.status());
|
||||||
|
let ct = resp.headers().get("content-type").unwrap().to_str().unwrap().to_string();
|
||||||
|
assert!(ct.contains("application/json"), "content-type for {} was {}", part, ct);
|
||||||
|
let txt = resp.text().await.unwrap();
|
||||||
|
assert!(!txt.is_empty(), "{} should not be empty", part);
|
||||||
|
if *part == "catalog.base.C" {
|
||||||
|
assert!(txt.contains(&publisher) && txt.contains("version"), "base part should contain publisher and version");
|
||||||
|
} else {
|
||||||
|
// dependency/summary may be empty for this test package; at least ensure signature is present
|
||||||
|
assert!(txt.contains("_SIGNATURE"), "{} should contain a signature field", part);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue