Skip to main content

specter/
response.rs

1//! HTTP response handling with explicit decompression.
2
3use crate::error::{Error, Result};
4use crate::headers::Headers;
5use bytes::Bytes;
6use http::StatusCode;
7use std::io::Read;
8use url::Url;
9
10/// HTTP response with explicit decompression.
11#[derive(Debug, Clone)]
12pub struct Response {
13    pub(crate) status: u16,
14    headers: Headers,
15    body: Bytes,
16    http_version: String,
17    effective_url: Option<Url>,
18}
19
20impl Response {
21    pub fn new(status: u16, headers: Headers, body: Bytes, http_version: String) -> Self {
22        Self {
23            status,
24            headers,
25            body,
26            http_version,
27            effective_url: None,
28        }
29    }
30
31    /// Set the effective URL (the URL that was actually requested).
32    /// This is used by the redirect engine to track the current URL.
33    pub fn with_url(mut self, url: Url) -> Self {
34        self.effective_url = Some(url);
35        self
36    }
37
38    pub fn http_version(&self) -> &str {
39        &self.http_version
40    }
41
42    pub fn status(&self) -> StatusCode {
43        StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
44    }
45
46    pub fn status_code(&self) -> u16 {
47        self.status
48    }
49
50    pub fn headers(&self) -> &Headers {
51        &self.headers
52    }
53
54    pub fn url(&self) -> Option<&Url> {
55        self.effective_url.as_ref()
56    }
57
58    pub fn body(&self) -> &Bytes {
59        &self.body
60    }
61
62    pub fn bytes_raw(&self) -> Bytes {
63        self.body.clone()
64    }
65
66    pub fn into_body(self) -> Bytes {
67        self.body
68    }
69
70    pub fn bytes(&self) -> Result<Bytes> {
71        self.decoded_body()
72    }
73
74    pub fn is_success(&self) -> bool {
75        (200..300).contains(&self.status)
76    }
77    pub fn is_redirect(&self) -> bool {
78        (300..400).contains(&self.status)
79    }
80    pub fn redirect_url(&self) -> Option<&str> {
81        self.get_header("Location")
82    }
83
84    pub fn get_header(&self, name: &str) -> Option<&str> {
85        self.headers.get(name)
86    }
87
88    pub fn get_headers(&self, name: &str) -> Vec<&str> {
89        self.headers.get_all(name)
90    }
91
92    pub fn content_type(&self) -> Option<&str> {
93        self.get_header("Content-Type")
94    }
95    pub fn content_encoding(&self) -> Option<&str> {
96        self.get_header("Content-Encoding")
97    }
98
99    /// Decode body based on Content-Encoding (gzip, deflate, br, zstd).
100    /// Supports chained encodings (e.g., "gzip, deflate") by applying decodings in reverse order.
101    pub fn decoded_body(&self) -> Result<Bytes> {
102        let encodings: Vec<&str> = self
103            .content_encoding()
104            .map(|s| s.split(',').map(str::trim).collect())
105            .unwrap_or_default();
106
107        // If Content-Encoding header is present, process encodings in reverse order
108        // (last encoding applied first during decode)
109        if !encodings.is_empty() {
110            let mut data = self.body.clone();
111            for encoding in encodings.iter().rev() {
112                data = match encoding.to_lowercase().as_str() {
113                    "gzip" | "x-gzip" => decode_gzip(&data)?,
114                    "deflate" => decode_deflate(&data)?,
115                    "br" => decode_brotli(&data)?,
116                    "zstd" => decode_zstd(&data)?,
117                    "identity" => data,
118                    _ => {
119                        // Unknown encoding, pass through
120                        data
121                    }
122                };
123            }
124            return Ok(data);
125        }
126
127        // No Content-Encoding header: check magic bytes
128        if self.body.len() >= 4 {
129            // zstd magic: 0x28 0xB5 0x2F 0xFD
130            if self.body[0] == 0x28
131                && self.body[1] == 0xB5
132                && self.body[2] == 0x2F
133                && self.body[3] == 0xFD
134            {
135                return decode_zstd(&self.body);
136            }
137        }
138        if self.body.len() >= 2 {
139            // gzip magic: 0x1f 0x8b
140            if self.body[0] == 0x1f && self.body[1] == 0x8b {
141                return decode_gzip(&self.body);
142            }
143        }
144
145        Ok(self.body.clone())
146    }
147
148    pub fn text(&self) -> Result<String> {
149        let decoded = self.decoded_body()?;
150        String::from_utf8(decoded.to_vec())
151            .map_err(|e| Error::Decompression(format!("UTF-8 decode error: {}", e)))
152    }
153
154    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
155        let text = self.text()?;
156        serde_json::from_str(&text).map_err(Error::from)
157    }
158
159    pub fn error_for_status(self) -> Result<Self> {
160        if self.status().is_client_error() || self.status().is_server_error() {
161            let message = self
162                .status()
163                .canonical_reason()
164                .unwrap_or("HTTP error")
165                .to_string();
166            Err(Error::http_status(self.status, message))
167        } else {
168            Ok(self)
169        }
170    }
171
172    pub fn error_for_status_ref(&self) -> Result<&Self> {
173        if self.status().is_client_error() || self.status().is_server_error() {
174            let message = self
175                .status()
176                .canonical_reason()
177                .unwrap_or("HTTP error")
178                .to_string();
179            Err(Error::http_status(self.status, message))
180        } else {
181            Ok(self)
182        }
183    }
184}
185
186fn decode_gzip(data: &[u8]) -> Result<Bytes> {
187    let mut decoder = flate2::read::GzDecoder::new(data);
188    let mut decoded = Vec::new();
189    decoder
190        .read_to_end(&mut decoded)
191        .map_err(|e| Error::Decompression(format!("gzip: {}", e)))?;
192    Ok(Bytes::from(decoded))
193}
194
195fn decode_deflate(data: &[u8]) -> Result<Bytes> {
196    let mut decoded = Vec::new();
197    if flate2::read::ZlibDecoder::new(data)
198        .read_to_end(&mut decoded)
199        .is_ok()
200    {
201        return Ok(Bytes::from(decoded));
202    }
203    decoded.clear();
204    flate2::read::DeflateDecoder::new(data)
205        .read_to_end(&mut decoded)
206        .map_err(|e| Error::Decompression(format!("deflate: {}", e)))?;
207    Ok(Bytes::from(decoded))
208}
209
210fn decode_brotli(data: &[u8]) -> Result<Bytes> {
211    let mut decoder = brotli::Decompressor::new(data, 4096);
212    let mut decoded = Vec::new();
213    decoder
214        .read_to_end(&mut decoded)
215        .map_err(|e| Error::Decompression(format!("brotli: {}", e)))?;
216    Ok(Bytes::from(decoded))
217}
218
219fn decode_zstd(data: &[u8]) -> Result<Bytes> {
220    zstd::stream::decode_all(data)
221        .map(Bytes::from)
222        .map_err(|e| Error::Decompression(format!("zstd: {}", e)))
223}