[go: up one dir, main page]

ureq/body/
mod.rs

1use std::fmt;
2use std::io;
3use std::sync::Arc;
4
5pub use build::BodyBuilder;
6use ureq_proto::http::header;
7use ureq_proto::BodyMode;
8
9use crate::http;
10use crate::run::BodyHandler;
11use crate::Error;
12
13use self::limit::LimitReader;
14use self::lossy::LossyUtf8Reader;
15
16mod build;
17mod limit;
18mod lossy;
19
20#[cfg(feature = "charset")]
21mod charset;
22
23#[cfg(feature = "gzip")]
24mod gzip;
25
26#[cfg(feature = "brotli")]
27mod brotli;
28
29/// Default max body size for read_to_string() and read_to_vec().
30const MAX_BODY_SIZE: u64 = 10 * 1024 * 1024;
31
32/// A response body returned as [`http::Response<Body>`].
33///
34/// # Body lengths
35///
36/// HTTP/1.1 has two major modes of transfering body data. Either a `Content-Length`
37/// header defines exactly how many bytes to transfer, or `Transfer-Encoding: chunked`
38/// facilitates a streaming style when the size is not known up front.
39///
40/// To protect against a problem called [request smuggling], ureq has heuristics for
41/// how to interpret a server sending both `Transfer-Encoding` and `Content-Length` headers.
42///
43/// 1. `chunked` takes precedence if there both headers are present (not for HTTP/1.0)
44/// 2. `content-length` is used if there is no chunked
45/// 3. If there are no headers, fall back on "close delimited" meaning the socket
46///    must close to end the body
47///
48/// When a `Content-Length` header is used, ureq will ensure the received body is _EXACTLY_
49/// as many bytes as declared (it cannot be less). This mechanic is in `ureq-proto`
50/// and is different to the [`BodyWithConfig::limit()`] below.
51///
52/// # Pool reuse
53///
54/// To return a connection (aka [`Transport`][crate::unversioned::transport::Transport])
55/// to the Agent's pool, the body must be read to end. If [`BodyWithConfig::limit()`] is set
56/// shorter size than the actual response body, the connection will not be reused.
57///
58/// # Example
59///
60/// ```
61/// use std::io::Read;
62/// let mut res = ureq::get("http://httpbin.org/bytes/100")
63///     .call()?;
64///
65/// assert!(res.headers().contains_key("Content-Length"));
66/// let len: usize = res.headers().get("Content-Length")
67///     .unwrap().to_str().unwrap().parse().unwrap();
68///
69/// let mut bytes: Vec<u8> = Vec::with_capacity(len);
70/// res.body_mut().as_reader()
71///     .read_to_end(&mut bytes)?;
72///
73/// assert_eq!(bytes.len(), len);
74/// # Ok::<_, ureq::Error>(())
75/// ```
76///
77/// [request smuggling]: https://en.wikipedia.org/wiki/HTTP_request_smuggling
78pub struct Body {
79    source: BodyDataSource,
80    info: Arc<ResponseInfo>,
81}
82
83enum BodyDataSource {
84    Handler(BodyHandler),
85    Reader(Box<dyn io::Read + Send + Sync>),
86}
87
88#[derive(Clone)]
89pub(crate) struct ResponseInfo {
90    content_encoding: ContentEncoding,
91    mime_type: Option<String>,
92    charset: Option<String>,
93    body_mode: BodyMode,
94}
95
96impl Body {
97    /// Builder for creating a body
98    ///
99    /// This is useful for testing, or for [`Middleware`][crate::middleware::Middleware] that
100    /// returns another body than the requested one.
101    pub fn builder() -> BodyBuilder {
102        BodyBuilder::new()
103    }
104
105    pub(crate) fn new(handler: BodyHandler, info: ResponseInfo) -> Self {
106        Body {
107            source: BodyDataSource::Handler(handler),
108            info: Arc::new(info),
109        }
110    }
111
112    /// The mime-type of the `content-type` header.
113    ///
114    /// For the below header, we would get `Some("text/plain")`:
115    ///
116    /// ```text
117    ///     Content-Type: text/plain; charset=iso-8859-1
118    /// ```
119    ///
120    /// *Caution:* A bad server might set `Content-Type` to one thing and send
121    /// something else. There is no way ureq can verify this.
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// let res = ureq::get("https://www.google.com/")
127    ///     .call()?;
128    ///
129    /// assert_eq!(res.body().mime_type(), Some("text/html"));
130    /// # Ok::<_, ureq::Error>(())
131    /// ```
132    pub fn mime_type(&self) -> Option<&str> {
133        self.info.mime_type.as_deref()
134    }
135
136    /// The charset of the `content-type` header.
137    ///
138    /// For the below header, we would get `Some("iso-8859-1")`:
139    ///
140    /// ```text
141    ///     Content-Type: text/plain; charset=iso-8859-1
142    /// ```
143    ///
144    /// *Caution:* A bad server might set `Content-Type` to one thing and send
145    /// something else. There is no way ureq can verify this.
146    ///
147    /// # Example
148    ///
149    /// ```
150    /// let res = ureq::get("https://www.google.com/")
151    ///     .call()?;
152    ///
153    /// assert_eq!(res.body().charset(), Some("ISO-8859-1"));
154    /// # Ok::<_, ureq::Error>(())
155    /// ```
156    pub fn charset(&self) -> Option<&str> {
157        self.info.charset.as_deref()
158    }
159
160    /// The content length of the body.
161    ///
162    /// This is the value of the `Content-Length` header, if there is one. For chunked
163    /// responses (`Transfer-Encoding: chunked`) , this will be `None`. Similarly for
164    /// HTTP/1.0 without a `Content-Length` header, the response is close delimited,
165    /// which means the length is unknown.
166    ///
167    /// A bad server might set `Content-Length` to one thing and send something else.
168    /// ureq will double check this, see section on body length heuristics.
169    ///
170    /// # Example
171    ///
172    /// ```
173    /// let res = ureq::get("https://httpbin.org/bytes/100")
174    ///     .call()?;
175    ///
176    /// assert_eq!(res.body().content_length(), Some(100));
177    /// # Ok::<_, ureq::Error>(())
178    /// ```
179    pub fn content_length(&self) -> Option<u64> {
180        match self.info.body_mode {
181            BodyMode::NoBody => None,
182            BodyMode::LengthDelimited(v) => Some(v),
183            BodyMode::Chunked => None,
184            BodyMode::CloseDelimited => None,
185        }
186    }
187
188    /// Handle this body as a shared `impl Read` of the body.
189    ///
190    /// This is the regular API which goes via [`http::Response::body_mut()`] to get a
191    /// mut reference to the `Body`, and then use `as_reader()`. It is also possible to
192    /// get a non-shared, owned reader via [`Body::into_reader()`].
193    ///
194    /// * Reader is not limited. To set a limit use [`Body::with_config()`].
195    ///
196    /// # Example
197    ///
198    /// ```
199    /// use std::io::Read;
200    ///
201    /// let mut res = ureq::get("http://httpbin.org/bytes/100")
202    ///     .call()?;
203    ///
204    /// let mut bytes: Vec<u8> = Vec::with_capacity(1000);
205    /// res.body_mut().as_reader()
206    ///     .read_to_end(&mut bytes)?;
207    /// # Ok::<_, ureq::Error>(())
208    /// ```
209    pub fn as_reader(&mut self) -> BodyReader {
210        self.with_config().reader()
211    }
212
213    /// Turn this response into an owned `impl Read` of the body.
214    ///
215    /// Sometimes it might be useful to disconnect the body reader from the body.
216    /// The reader returned by [`Body::as_reader()`] borrows the body, while this
217    /// variant consumes the body and turns it into a reader with lifetime `'static`.
218    /// The reader can for instance be sent to another thread.
219    ///
220    /// * Reader is not limited. To set a limit use [`Body::into_with_config()`].
221    ///
222    /// ```
223    /// use std::io::Read;
224    ///
225    /// let res = ureq::get("http://httpbin.org/bytes/100")
226    ///     .call()?;
227    ///
228    /// let (_, body) = res.into_parts();
229    ///
230    /// let mut bytes: Vec<u8> = Vec::with_capacity(1000);
231    /// body.into_reader()
232    ///     .read_to_end(&mut bytes)?;
233    /// # Ok::<_, ureq::Error>(())
234    /// ```
235    pub fn into_reader(self) -> BodyReader<'static> {
236        self.into_with_config().reader()
237    }
238
239    /// Read the response as a string.
240    ///
241    /// * Response is limited to 10MB
242    /// * Replaces incorrect utf-8 chars to `?`
243    ///
244    /// To change these defaults use [`Body::with_config()`].
245    ///
246    /// ```
247    /// let mut res = ureq::get("http://httpbin.org/robots.txt")
248    ///     .call()?;
249    ///
250    /// let s = res.body_mut().read_to_string()?;
251    /// assert_eq!(s, "User-agent: *\nDisallow: /deny\n");
252    /// # Ok::<_, ureq::Error>(())
253    /// ```
254    pub fn read_to_string(&mut self) -> Result<String, Error> {
255        self.with_config()
256            .limit(MAX_BODY_SIZE)
257            .lossy_utf8(true)
258            .read_to_string()
259    }
260
261    /// Read the response to a vec.
262    ///
263    /// * Response is limited to 10MB.
264    ///
265    /// To change this default use [`Body::with_config()`].
266    /// ```
267    /// let mut res = ureq::get("http://httpbin.org/bytes/100")
268    ///     .call()?;
269    ///
270    /// let bytes = res.body_mut().read_to_vec()?;
271    /// assert_eq!(bytes.len(), 100);
272    /// # Ok::<_, ureq::Error>(())
273    /// ```
274    pub fn read_to_vec(&mut self) -> Result<Vec<u8>, Error> {
275        self.with_config()
276            //
277            .limit(MAX_BODY_SIZE)
278            .read_to_vec()
279    }
280
281    /// Read the response from JSON.
282    ///
283    /// * Response is limited to 10MB.
284    ///
285    /// To change this default use [`Body::with_config()`].
286    ///
287    /// The returned value is something that derives [`Deserialize`](serde::Deserialize).
288    /// You might need to be explicit with which type you want. See example below.
289    ///
290    /// ```
291    /// use serde::Deserialize;
292    ///
293    /// #[derive(Deserialize)]
294    /// struct BodyType {
295    ///   slideshow: BodyTypeInner,
296    /// }
297    ///
298    /// #[derive(Deserialize)]
299    /// struct BodyTypeInner {
300    ///   author: String,
301    /// }
302    ///
303    /// let body = ureq::get("https://httpbin.org/json")
304    ///     .call()?
305    ///     .body_mut()
306    ///     .read_json::<BodyType>()?;
307    ///
308    /// assert_eq!(body.slideshow.author, "Yours Truly");
309    /// # Ok::<_, ureq::Error>(())
310    /// ```
311    #[cfg(feature = "json")]
312    pub fn read_json<T: serde::de::DeserializeOwned>(&mut self) -> Result<T, Error> {
313        let reader = self.with_config().limit(MAX_BODY_SIZE).reader();
314        let value: T = serde_json::from_reader(reader)?;
315        Ok(value)
316    }
317
318    /// Read the body data with configuration.
319    ///
320    /// This borrows the body which gives easier use with [`http::Response::body_mut()`].
321    /// To get a non-borrowed reader use [`Body::into_with_config()`].
322    ///
323    /// # Example
324    ///
325    /// ```
326    /// let reader = ureq::get("http://httpbin.org/bytes/100")
327    ///     .call()?
328    ///     .body_mut()
329    ///     .with_config()
330    ///     // Reader will only read 50 bytes
331    ///     .limit(50)
332    ///     .reader();
333    /// # Ok::<_, ureq::Error>(())
334    /// ```
335    pub fn with_config(&mut self) -> BodyWithConfig {
336        let handler = (&mut self.source).into();
337        BodyWithConfig::new(handler, self.info.clone())
338    }
339
340    /// Consume self and read the body with configuration.
341    ///
342    /// This consumes self and returns a reader with `'static` lifetime.
343    ///
344    /// # Example
345    ///
346    /// ```
347    /// // Get the body out of http::Response
348    /// let (_, body) = ureq::get("http://httpbin.org/bytes/100")
349    ///     .call()?
350    ///     .into_parts();
351    ///
352    /// let reader = body
353    ///     .into_with_config()
354    ///     // Reader will only read 50 bytes
355    ///     .limit(50)
356    ///     .reader();
357    /// # Ok::<_, ureq::Error>(())
358    /// ```
359    pub fn into_with_config(self) -> BodyWithConfig<'static> {
360        let handler = self.source.into();
361        BodyWithConfig::new(handler, self.info.clone())
362    }
363}
364
365/// Configuration of how to read the body.
366///
367/// Obtained via one of:
368///
369/// * [Body::with_config()]
370/// * [Body::into_with_config()]
371///
372pub struct BodyWithConfig<'a> {
373    handler: BodySourceRef<'a>,
374    info: Arc<ResponseInfo>,
375    limit: u64,
376    lossy_utf8: bool,
377}
378
379impl<'a> BodyWithConfig<'a> {
380    fn new(handler: BodySourceRef<'a>, info: Arc<ResponseInfo>) -> Self {
381        BodyWithConfig {
382            handler,
383            info,
384            limit: u64::MAX,
385            lossy_utf8: false,
386        }
387    }
388
389    /// Limit the response body.
390    ///
391    /// Controls how many bytes we should read before throwing an error. This is used
392    /// to ensure RAM isn't exhausted by a server sending a very large response body.
393    ///
394    /// The default limit is `u64::MAX` (unlimited).
395    pub fn limit(mut self, value: u64) -> Self {
396        self.limit = value;
397        self
398    }
399
400    /// Replace invalid utf-8 chars.
401    ///
402    /// `true` means that broken utf-8 characters are replaced by a question mark `?`
403    /// (not utf-8 replacement char). This happens after charset conversion regardless of
404    /// whether the **charset** feature is enabled or not.
405    ///
406    /// The default is `false`.
407    pub fn lossy_utf8(mut self, value: bool) -> Self {
408        self.lossy_utf8 = value;
409        self
410    }
411
412    fn do_build(self) -> BodyReader<'a> {
413        BodyReader::new(
414            LimitReader::new(self.handler, self.limit),
415            &self.info,
416            self.info.body_mode,
417            self.lossy_utf8,
418        )
419    }
420
421    /// Creates a reader.
422    ///
423    /// The reader is either shared or owned, depending on `with_config` or `into_with_config`.
424    ///
425    /// # Example of owned vs shared
426    ///
427    /// ```
428    /// // Creates an owned reader.
429    /// let reader = ureq::get("https://httpbin.org/get")
430    ///     .call()?
431    ///     .into_body()
432    ///     // takes ownership of Body
433    ///     .into_with_config()
434    ///     .limit(10)
435    ///     .reader();
436    /// # Ok::<_, ureq::Error>(())
437    /// ```
438    ///
439    /// ```
440    /// // Creates a shared reader.
441    /// let reader = ureq::get("https://httpbin.org/get")
442    ///     .call()?
443    ///     .body_mut()
444    ///     // borrows Body
445    ///     .with_config()
446    ///     .limit(10)
447    ///     .reader();
448    /// # Ok::<_, ureq::Error>(())
449    /// ```
450    pub fn reader(self) -> BodyReader<'a> {
451        self.do_build()
452    }
453
454    /// Read into string.
455    ///
456    /// *Caution:* without a preceeding [`limit()`][BodyWithConfig::limit], this
457    /// becomes an unbounded sized `String`. A bad server could exhaust your memory.
458    ///
459    /// # Example
460    ///
461    /// ```
462    /// // Reads max 10k to a String.
463    /// let string = ureq::get("https://httpbin.org/get")
464    ///     .call()?
465    ///     .body_mut()
466    ///     .with_config()
467    ///     // Important. Limits body to 10k
468    ///     .limit(10_000)
469    ///     .read_to_string()?;
470    /// # Ok::<_, ureq::Error>(())
471    /// ```
472    pub fn read_to_string(self) -> Result<String, Error> {
473        use std::io::Read;
474        let mut reader = self.do_build();
475        let mut buf = String::new();
476        reader.read_to_string(&mut buf)?;
477        Ok(buf)
478    }
479
480    /// Read into vector.
481    ///
482    /// *Caution:* without a preceeding [`limit()`][BodyWithConfig::limit], this
483    /// becomes an unbounded sized `Vec`. A bad server could exhaust your memory.
484    ///
485    /// # Example
486    ///
487    /// ```
488    /// // Reads max 10k to a Vec.
489    /// let myvec = ureq::get("https://httpbin.org/get")
490    ///     .call()?
491    ///     .body_mut()
492    ///     .with_config()
493    ///     // Important. Limits body to 10k
494    ///     .limit(10_000)
495    ///     .read_to_vec()?;
496    /// # Ok::<_, ureq::Error>(())
497    /// ```
498    pub fn read_to_vec(self) -> Result<Vec<u8>, Error> {
499        use std::io::Read;
500        let mut reader = self.do_build();
501        let mut buf = Vec::new();
502        reader.read_to_end(&mut buf)?;
503        Ok(buf)
504    }
505
506    /// Read JSON body.
507    ///
508    /// *Caution:* without a preceeding [`limit()`][BodyWithConfig::limit], this
509    /// becomes an unbounded sized `String`. A bad server could exhaust your memory.
510    ///
511    /// # Example
512    ///
513    /// ```
514    /// use serde_json::Value;
515    ///
516    /// // Reads max 10k as a JSON value.
517    /// let json: Value  = ureq::get("https://httpbin.org/get")
518    ///     .call()?
519    ///     .body_mut()
520    ///     .with_config()
521    ///     // Important. Limits body to 10k
522    ///     .limit(10_000)
523    ///     .read_json()?;
524    /// # Ok::<_, ureq::Error>(())
525    /// ```
526    #[cfg(feature = "json")]
527    pub fn read_json<T: serde::de::DeserializeOwned>(self) -> Result<T, Error> {
528        let reader = self.do_build();
529        let value: T = serde_json::from_reader(reader)?;
530        Ok(value)
531    }
532}
533
534#[derive(Debug, Clone, Copy)]
535enum ContentEncoding {
536    None,
537    Gzip,
538    Brotli,
539    Unknown,
540}
541
542impl ResponseInfo {
543    pub fn new(headers: &http::HeaderMap, body_mode: BodyMode) -> Self {
544        let content_encoding = headers
545            .get(header::CONTENT_ENCODING)
546            .and_then(|v| v.to_str().ok())
547            .map(ContentEncoding::from)
548            .unwrap_or(ContentEncoding::None);
549
550        let (mime_type, charset) = headers
551            .get(header::CONTENT_TYPE)
552            .and_then(|v| v.to_str().ok())
553            .map(split_content_type)
554            .unwrap_or((None, None));
555
556        ResponseInfo {
557            content_encoding,
558            mime_type,
559            charset,
560            body_mode,
561        }
562    }
563
564    /// Whether the mime type indicats text.
565    fn is_text(&self) -> bool {
566        self.mime_type
567            .as_deref()
568            .map(|s| s.starts_with("text/"))
569            .unwrap_or(false)
570    }
571}
572
573fn split_content_type(content_type: &str) -> (Option<String>, Option<String>) {
574    // Content-Type: text/plain; charset=iso-8859-1
575    let mut split = content_type.split(';');
576
577    let Some(mime_type) = split.next() else {
578        return (None, None);
579    };
580
581    let mut charset = None;
582
583    for maybe_charset in split {
584        let maybe_charset = maybe_charset.trim();
585        if let Some(s) = maybe_charset.strip_prefix("charset=") {
586            charset = Some(s.to_string());
587        }
588    }
589
590    (Some(mime_type.to_string()), charset)
591}
592
593/// A reader of the response data.
594///
595/// 1. If `Transfer-Encoding: chunked`, the returned reader will unchunk it
596///    and any `Content-Length` header is ignored.
597/// 2. If `Content-Encoding: gzip` (or `br`) and the corresponding feature
598///    flag is enabled (**gzip** and **brotli**), decompresses the body data.
599/// 3. Given a header like `Content-Type: text/plain; charset=ISO-8859-1`
600///    and the **charset** feature enabled, will translate the body to utf-8.
601///    This mechanic need two components a mime-type starting `text/` and
602///    a non-utf8 charset indication.
603/// 4. If `Content-Length` is set, the returned reader is limited to this byte
604///    length regardless of how many bytes the server sends.
605/// 5. If no length header, the reader is until server stream end.
606/// 6. The limit in the body method used to obtain the reader.
607///
608/// Note: The reader is also limited by the [`Body::as_reader`] and
609/// [`Body::into_reader`] calls. If that limit is set very high, a malicious
610/// server might return enough bytes to exhaust available memory. If you're
611/// making requests to untrusted servers, you should use set that
612/// limit accordingly.
613///
614/// # Example
615///
616/// ```
617/// use std::io::Read;
618/// let mut res = ureq::get("http://httpbin.org/bytes/100")
619///     .call()?;
620///
621/// assert!(res.headers().contains_key("Content-Length"));
622/// let len: usize = res.headers().get("Content-Length")
623///     .unwrap().to_str().unwrap().parse().unwrap();
624///
625/// let mut bytes: Vec<u8> = Vec::with_capacity(len);
626/// res.body_mut().as_reader()
627///     .read_to_end(&mut bytes)?;
628///
629/// assert_eq!(bytes.len(), len);
630/// # Ok::<_, ureq::Error>(())
631/// ```
632pub struct BodyReader<'a> {
633    reader: MaybeLossyDecoder<CharsetDecoder<ContentDecoder<LimitReader<BodySourceRef<'a>>>>>,
634    // If this reader is used as SendBody for another request, this
635    // body mode can indiciate the content-length. Gzip, charset etc
636    // would mean input is not same as output.
637    outgoing_body_mode: BodyMode,
638}
639
640impl<'a> BodyReader<'a> {
641    fn new(
642        reader: LimitReader<BodySourceRef<'a>>,
643        info: &ResponseInfo,
644        incoming_body_mode: BodyMode,
645        lossy_utf8: bool,
646    ) -> BodyReader<'a> {
647        // This is outgoing body_mode in case we are using the BodyReader as a send body
648        // in a proxy situation.
649        let mut outgoing_body_mode = incoming_body_mode;
650
651        let reader = match info.content_encoding {
652            ContentEncoding::None | ContentEncoding::Unknown => ContentDecoder::PassThrough(reader),
653            #[cfg(feature = "gzip")]
654            ContentEncoding::Gzip => {
655                debug!("Decoding gzip");
656                outgoing_body_mode = BodyMode::Chunked;
657                ContentDecoder::Gzip(Box::new(gzip::GzipDecoder::new(reader)))
658            }
659            #[cfg(not(feature = "gzip"))]
660            ContentEncoding::Gzip => ContentDecoder::PassThrough(reader),
661            #[cfg(feature = "brotli")]
662            ContentEncoding::Brotli => {
663                debug!("Decoding brotli");
664                outgoing_body_mode = BodyMode::Chunked;
665                ContentDecoder::Brotli(Box::new(brotli::BrotliDecoder::new(reader)))
666            }
667            #[cfg(not(feature = "brotli"))]
668            ContentEncoding::Brotli => ContentDecoder::PassThrough(reader),
669        };
670
671        let reader = if info.is_text() {
672            charset_decoder(
673                reader,
674                info.mime_type.as_deref(),
675                info.charset.as_deref(),
676                &mut outgoing_body_mode,
677            )
678        } else {
679            CharsetDecoder::PassThrough(reader)
680        };
681
682        let reader = if info.is_text() && lossy_utf8 {
683            MaybeLossyDecoder::Lossy(LossyUtf8Reader::new(reader))
684        } else {
685            MaybeLossyDecoder::PassThrough(reader)
686        };
687
688        BodyReader {
689            outgoing_body_mode,
690            reader,
691        }
692    }
693
694    pub(crate) fn body_mode(&self) -> BodyMode {
695        self.outgoing_body_mode
696    }
697}
698
699#[allow(unused)]
700fn charset_decoder<R: io::Read>(
701    reader: R,
702    mime_type: Option<&str>,
703    charset: Option<&str>,
704    body_mode: &mut BodyMode,
705) -> CharsetDecoder<R> {
706    #[cfg(feature = "charset")]
707    {
708        use encoding_rs::{Encoding, UTF_8};
709
710        let from = charset
711            .and_then(|c| Encoding::for_label(c.as_bytes()))
712            .unwrap_or(UTF_8);
713
714        if from == UTF_8 {
715            // Do nothing
716            CharsetDecoder::PassThrough(reader)
717        } else {
718            debug!("Decoding charset {}", from.name());
719            *body_mode = BodyMode::Chunked;
720            CharsetDecoder::Decoder(self::charset::CharCodec::new(reader, from, UTF_8))
721        }
722    }
723
724    #[cfg(not(feature = "charset"))]
725    {
726        CharsetDecoder::PassThrough(reader)
727    }
728}
729
730enum MaybeLossyDecoder<R> {
731    Lossy(LossyUtf8Reader<R>),
732    PassThrough(R),
733}
734
735impl<R: io::Read> io::Read for MaybeLossyDecoder<R> {
736    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
737        match self {
738            MaybeLossyDecoder::Lossy(r) => r.read(buf),
739            MaybeLossyDecoder::PassThrough(r) => r.read(buf),
740        }
741    }
742}
743
744impl<'a> io::Read for BodyReader<'a> {
745    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
746        self.reader.read(buf)
747    }
748}
749
750enum CharsetDecoder<R> {
751    #[cfg(feature = "charset")]
752    Decoder(charset::CharCodec<R>),
753    PassThrough(R),
754}
755
756impl<R: io::Read> io::Read for CharsetDecoder<R> {
757    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
758        match self {
759            #[cfg(feature = "charset")]
760            CharsetDecoder::Decoder(v) => v.read(buf),
761            CharsetDecoder::PassThrough(v) => v.read(buf),
762        }
763    }
764}
765
766enum ContentDecoder<R: io::Read> {
767    #[cfg(feature = "gzip")]
768    Gzip(Box<gzip::GzipDecoder<R>>),
769    #[cfg(feature = "brotli")]
770    Brotli(Box<brotli::BrotliDecoder<R>>),
771    PassThrough(R),
772}
773
774impl<R: io::Read> io::Read for ContentDecoder<R> {
775    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
776        match self {
777            #[cfg(feature = "gzip")]
778            ContentDecoder::Gzip(v) => v.read(buf),
779            #[cfg(feature = "brotli")]
780            ContentDecoder::Brotli(v) => v.read(buf),
781            ContentDecoder::PassThrough(v) => v.read(buf),
782        }
783    }
784}
785
786impl fmt::Debug for Body {
787    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788        f.debug_struct("Body").finish()
789    }
790}
791
792impl From<&str> for ContentEncoding {
793    fn from(s: &str) -> Self {
794        match s {
795            "gzip" => ContentEncoding::Gzip,
796            "br" => ContentEncoding::Brotli,
797            _ => {
798                debug!("Unknown content-encoding: {}", s);
799                ContentEncoding::Unknown
800            }
801        }
802    }
803}
804
805impl<'a> From<&'a mut BodyDataSource> for BodySourceRef<'a> {
806    fn from(value: &'a mut BodyDataSource) -> Self {
807        match value {
808            BodyDataSource::Handler(v) => Self::HandlerShared(v),
809            BodyDataSource::Reader(v) => Self::ReaderShared(v),
810        }
811    }
812}
813
814impl From<BodyDataSource> for BodySourceRef<'static> {
815    fn from(value: BodyDataSource) -> Self {
816        match value {
817            BodyDataSource::Handler(v) => Self::HandlerOwned(v),
818            BodyDataSource::Reader(v) => Self::ReaderOwned(v),
819        }
820    }
821}
822
823pub(crate) enum BodySourceRef<'a> {
824    HandlerShared(&'a mut BodyHandler),
825    HandlerOwned(BodyHandler),
826    ReaderShared(&'a mut (dyn io::Read + Send + Sync)),
827    ReaderOwned(Box<dyn io::Read + Send + Sync>),
828}
829
830impl<'a> io::Read for BodySourceRef<'a> {
831    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
832        match self {
833            BodySourceRef::HandlerShared(v) => v.read(buf),
834            BodySourceRef::HandlerOwned(v) => v.read(buf),
835            BodySourceRef::ReaderShared(v) => v.read(buf),
836            BodySourceRef::ReaderOwned(v) => v.read(buf),
837        }
838    }
839}
840
841#[cfg(all(test, feature = "_test"))]
842mod test {
843    use std::iter;
844
845    use crate::test::init_test_log;
846    use crate::transport::set_handler;
847    use crate::Error;
848
849    #[test]
850    fn content_type_without_charset() {
851        init_test_log();
852        set_handler("/get", 200, &[("content-type", "application/json")], b"{}");
853
854        let res = crate::get("https://my.test/get").call().unwrap();
855        assert_eq!(res.body().mime_type(), Some("application/json"));
856        assert!(res.body().charset().is_none());
857    }
858
859    #[test]
860    fn content_type_with_charset() {
861        init_test_log();
862        set_handler(
863            "/get",
864            200,
865            &[("content-type", "application/json; charset=iso-8859-4")],
866            b"{}",
867        );
868
869        let res = crate::get("https://my.test/get").call().unwrap();
870        assert_eq!(res.body().mime_type(), Some("application/json"));
871        assert_eq!(res.body().charset(), Some("iso-8859-4"));
872    }
873
874    #[test]
875    fn chunked_transfer() {
876        init_test_log();
877
878        let s = "3\r\n\
879            hel\r\n\
880            b\r\n\
881            lo world!!!\r\n\
882            0\r\n\
883            \r\n";
884
885        set_handler(
886            "/get",
887            200,
888            &[("transfer-encoding", "chunked")],
889            s.as_bytes(),
890        );
891
892        let mut res = crate::get("https://my.test/get").call().unwrap();
893        let b = res.body_mut().read_to_string().unwrap();
894        assert_eq!(b, "hello world!!!");
895    }
896
897    #[test]
898    fn large_response_header() {
899        init_test_log();
900        set_handler(
901            "/get",
902            200,
903            &[(
904                "content-type",
905                &iter::repeat('b').take(64 * 1024).collect::<String>(),
906            )],
907            b"{}",
908        );
909
910        let err = crate::get("https://my.test/get").call().unwrap_err();
911        assert!(matches!(err, Error::LargeResponseHeader(_, _)));
912    }
913}