Repository: ducaale/xh (rust)

Issue:
HEAD fails on compressed response
```
$ xh head httpbin.org/gzip
[...]
xh: error: error decoding gzip response body: failed to fill whole buffer
```
This didn't happen before #241, so reqwest deals with it correctly. I don't know how.

Pull request:
Handle empty gzip responses with content-length > 0
Resolves #253 

Patch:
diff --git a/src/decoder.rs b/src/decoder.rs
index e257ef1..8b91aa0 100644
--- a/src/decoder.rs
+++ b/src/decoder.rs
@@ -51,6 +51,7 @@ pub fn get_compression_type(headers: &HeaderMap) -> Option<CompressionType> {
 
 struct InnerReader<R: Read> {
     reader: R,
+    has_read_data: bool,
     has_errored: bool,
 }
 
@@ -58,6 +59,7 @@ impl<R: Read> InnerReader<R> {
     fn new(reader: R) -> Self {
         InnerReader {
             reader,
+            has_read_data: false,
             has_errored: false,
         }
     }
@@ -66,7 +68,11 @@ impl<R: Read> InnerReader<R> {
 impl<R: Read> Read for InnerReader<R> {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         match self.reader.read(buf) {
-            Ok(len) => Ok(len),
+            Ok(0) => Ok(0),
+            Ok(len) => {
+                self.has_read_data = true;
+                Ok(len)
+            }
             Err(e) => {
                 self.has_errored = true;
                 Err(e)
@@ -86,36 +92,33 @@ impl<R: Read> Read for Decoder<R> {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         match self {
             Decoder::PlainText(decoder) => decoder.read(buf),
-            Decoder::Gzip(decoder) => decoder.read(buf).map_err(|e| {
-                if decoder.get_ref().has_errored {
-                    e
-                } else {
-                    io::Error::new(
-                        e.kind(),
-                        format!("error decoding gzip response body: {}", e),
-                    )
-                }
-            }),
-            Decoder::Deflate(decoder) => decoder.read(buf).map_err(|e| {
-                if decoder.get_ref().has_errored {
-                    e
-                } else {
-                    io::Error::new(
-                        e.kind(),
-                        format!("error decoding deflate response body: {}", e),
-                    )
-                }
-            }),
-            Decoder::Brotli(decoder) => decoder.read(buf).map_err(|e| {
-                if decoder.get_ref().has_errored {
-                    e
-                } else {
-                    io::Error::new(
-                        e.kind(),
-                        format!("error decoding brotli response body: {}", e),
-                    )
-                }
-            }),
+            Decoder::Gzip(decoder) => match decoder.read(buf) {
+                Ok(n) => Ok(n),
+                Err(e) if decoder.get_ref().has_errored => Err(e),
+                Err(_) if !decoder.get_ref().has_read_data => Ok(0),
+                Err(e) => Err(io::Error::new(
+                    e.kind(),
+                    format!("error decoding gzip response body: {}", e),
+                )),
+            },
+            Decoder::Deflate(decoder) => match decoder.read(buf) {
+                Ok(n) => Ok(n),
+                Err(e) if decoder.get_ref().has_errored => Err(e),
+                Err(_) if !decoder.get_ref().has_read_data => Ok(0),
+                Err(e) => Err(io::Error::new(
+                    e.kind(),
+                    format!("error decoding deflate response body: {}", e),
+                )),
+            },
+            Decoder::Brotli(decoder) => match decoder.read(buf) {
+                Ok(n) => Ok(n),
+                Err(e) if decoder.get_ref().has_errored => Err(e),
+                Err(_) if !decoder.get_ref().has_read_data => Ok(0),
+                Err(e) => Err(io::Error::new(
+                    e.kind(),
+                    format!("error decoding brotli response body: {}", e),
+                )),
+            },
         }
     }
 }


Test patch:
diff --git a/tests/cli.rs b/tests/cli.rs
index 3f5224e..4a7d88d 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -2869,3 +2869,28 @@ fn empty_response_with_content_encoding() {
 
         "#});
 }
+
+#[test]
+fn empty_response_with_content_encoding_and_content_length() {
+    let server = server::http(|_req| async move {
+        hyper::Response::builder()
+            .header("date", "N/A")
+            .header("content-encoding", "gzip")
+            .header("content-length", "100")
+            .body("".into())
+            .unwrap()
+    });
+
+    get_command()
+        .arg("head")
+        .arg(server.base_url())
+        .assert()
+        .stdout(indoc! {r#"
+            HTTP/1.1 200 OK
+            Content-Encoding: gzip
+            Content-Length: 100
+            Date: N/A
+
+
+        "#});
+}