diff --git a/proxmox-http/Cargo.toml b/proxmox-http/Cargo.toml index 9ece24eb..4455ba85 100644 --- a/proxmox-http/Cargo.toml +++ b/proxmox-http/Cargo.toml @@ -26,6 +26,11 @@ proxmox-async = { workspace = true, optional = true } proxmox-sys = { workspace = true, optional = true } proxmox-io = { workspace = true, optional = true } proxmox-lang = { workspace = true, optional = true } +proxmox-compression = { workspace = true, optional = true } + +[dev-dependencies] +tokio = { workspace = true, features = [ "macros" ] } +flate2 = { workspace = true } [features] default = [] @@ -42,12 +47,14 @@ client = [ "dep:futures", "dep:hyper", "dep:openssl", + "dep:proxmox-compression", "dep:tokio", "dep:tokio-openssl", "http-helpers", "hyper?/client", "hyper?/http1", "hyper?/http2", + "hyper?/stream", "hyper?/tcp", "rate-limited-stream", "tokio?/io-util", diff --git a/proxmox-http/src/client/simple.rs b/proxmox-http/src/client/simple.rs index e9910802..062889ac 100644 --- a/proxmox-http/src/client/simple.rs +++ b/proxmox-http/src/client/simple.rs @@ -78,7 +78,8 @@ impl Client { self.add_proxy_headers(&mut request)?; - self.client.request(request).map_err(Error::from).await + let encoded_response = self.client.request(request).map_err(Error::from).await?; + decode_response(encoded_response).await } pub async fn post( @@ -245,3 +246,65 @@ impl crate::HttpClient for Client { }) } } + +/// Wraps the `Body` stream in a DeflateDecoder stream if the `Content-Encoding` +/// header of the response is `deflate`, otherwise returns the original +/// response. +async fn decode_response(mut res: Response) -> Result, Error> { + let Some(content_encoding) = res.headers_mut().remove(&hyper::header::CONTENT_ENCODING) else { + return Ok(res); + }; + + let encodings = content_encoding.to_str()?; + if encodings == "deflate" { + let (parts, body) = res.into_parts(); + let decoder = proxmox_compression::DeflateDecoder::builder(body) + .zlib(true) + .build(); + let decoded_body = Body::wrap_stream(decoder); + Ok(Response::from_parts(parts, decoded_body)) + } else { + bail!("Unknown encoding format: {encodings}"); + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::io::Write; + + const BODY: &str = r#"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do +eiusmod tempor incididunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut +enim aeque doleamus animo, cum corpore dolemus, fieri tamen permagna accessio potest, +si aliquod aeternum et infinitum impendere."#; + + #[tokio::test] + async fn test_parse_response_deflate() { + let encoded = encode_deflate(BODY.as_bytes()).unwrap(); + let encoded_body = Body::from(encoded); + let encoded_response = Response::builder() + .header(hyper::header::CONTENT_ENCODING, "deflate") + .body(encoded_body) + .unwrap(); + + let decoded_response = decode_response(encoded_response).await.unwrap(); + + assert_eq!( + Client::response_body_string(decoded_response) + .await + .unwrap(), + BODY + ); + } + + fn encode_deflate(bytes: &[u8]) -> Result, std::io::Error> { + use flate2::write::ZlibEncoder; + use flate2::Compression; + + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(bytes).unwrap(); + + e.finish() + } +}