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 crate::test::init_test_log;
844 use crate::transport::set_handler;
845 use crate::Error;
846
847 #[test]
848 fn content_type_without_charset() {
849 init_test_log();
850 set_handler("/get", 200, &[("content-type", "application/json")], b"{}");
851
852 let res = crate::get("https://my.test/get").call().unwrap();
853 assert_eq!(res.body().mime_type(), Some("application/json"));
854 assert!(res.body().charset().is_none());
855 }
856
857 #[test]
858 fn content_type_with_charset() {
859 init_test_log();
860 set_handler(
861 "/get",
862 200,
863 &[("content-type", "application/json; charset=iso-8859-4")],
864 b"{}",
865 );
866
867 let res = crate::get("https://my.test/get").call().unwrap();
868 assert_eq!(res.body().mime_type(), Some("application/json"));
869 assert_eq!(res.body().charset(), Some("iso-8859-4"));
870 }
871
872 #[test]
873 fn chunked_transfer() {
874 init_test_log();
875
876 let s = "3\r\n\
877 hel\r\n\
878 b\r\n\
879 lo world!!!\r\n\
880 0\r\n\
881 \r\n";
882
883 set_handler(
884 "/get",
885 200,
886 &[("transfer-encoding", "chunked")],
887 s.as_bytes(),
888 );
889
890 let mut res = crate::get("https://my.test/get").call().unwrap();
891 let b = res.body_mut().read_to_string().unwrap();
892 assert_eq!(b, "hello world!!!");
893 }
894
895 #[test]
896 fn large_response_header() {
897 init_test_log();
898 set_handler(
899 "/get",
900 200,
901 &[("content-type", &"b".repeat(64 * 1024))],
902 b"{}",
903 );
904
905 let err = crate::get("https://my.test/get").call().unwrap_err();
906 assert!(matches!(err, Error::LargeResponseHeader(_, _)));
907 }
908}