minreq/
response.rs

1use crate::{Error, connection::HttpStream};
2use std::collections::HashMap;
3use std::{fmt::Display, str};
4use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
5const BACKING_READ_BUFFER_LENGTH: usize = 16 * 1024;
6
7/// An HTTP response.
8///
9/// Returned by [`Request::send`](struct.Request.html#method.send).
10///
11/// # Example
12///
13/// ```no_run
14/// # fn main() -> Result<(), minreq::Error> {
15/// let response = minreq::get("http://example.com").send()?;
16/// println!("{}", response.as_str()?);
17/// # Ok(()) }
18/// ```
19#[derive(Clone, PartialEq, Eq, Debug)]
20pub struct Response {
21    /// The status code of the response, eg. 404.
22    pub status_code: i32,
23    /// The reason phrase of the response, eg. "Not Found".
24    pub reason_phrase: String,
25    /// The headers of the response. The header field names (the
26    /// keys) are all lowercase.
27    pub headers: HashMap<String, String>,
28    /// The URL of the resource returned in this response. May differ from the
29    /// request URL if it was redirected or typo corrections were applied (e.g.
30    /// <http://example.com?foo=bar> would be corrected to
31    /// <http://example.com/?foo=bar>).
32    pub url: String,
33    pub download_size: u64,
34    body: Vec<u8>,
35}
36impl Display for Response {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        let len =  self.body_len();
39        let len = match len {
40            0 => self.download_size,
41            _ => len
42        };
43        writeln!(
44            f,
45            "Response {{ Status: {} {}, body len: {} }}",
46            self.status_code,
47            self.reason_phrase,
48            len
49        )
50    }
51}
52impl Response {
53    pub(crate) async fn create(mut parent: ResponseLazy, is_head: bool) -> Result<Response, Error> {
54        let mut body = Vec::new();
55        if !is_head && parent.status_code != 204 && parent.status_code != 304 {
56            loop {
57                let d = parent.next().await;
58                match d {
59                    Some(byte) => {
60                        body.push(byte);
61                    }
62                    None => break,
63                }
64            }
65        }
66
67        let ResponseLazy {
68            status_code,
69            reason_phrase,
70            headers,
71            url,
72            ..
73        } = parent;
74
75        Ok(Response {
76            status_code,
77            reason_phrase,
78            headers,
79            url,
80            body,
81            download_size: 0
82        })
83    }
84
85    /// Returns the body as an `&str`.
86    ///
87    /// # Errors
88    ///
89    /// Returns
90    /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody)
91    /// if the body is not UTF-8, with a description as to why the
92    /// provided slice is not UTF-8.
93    ///
94    /// # Example
95    ///
96    /// ```no_run
97    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
98    /// # let url = "http://example.org/";
99    /// let response = minreq::get(url).send()?;
100    /// println!("{}", response.as_str()?);
101    /// # Ok(())
102    /// # }
103    /// ```
104    pub fn as_str(&self) -> Result<&str, Error> {
105        match str::from_utf8(&self.body) {
106            Ok(s) => Ok(s),
107            Err(err) => Err(Error::InvalidUtf8InBody(err)),
108        }
109    }
110
111    /// Returns a reference to the contained bytes of the body. If you
112    /// want the `Vec<u8>` itself, use
113    /// [`into_bytes()`](#method.into_bytes) instead.
114    ///
115    /// # Example
116    ///
117    /// ```no_run
118    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
119    /// # let url = "http://example.org/";
120    /// let response = minreq::get(url).send()?;
121    /// println!("{:?}", response.as_bytes());
122    /// # Ok(())
123    /// # }
124    /// ```
125    pub fn as_bytes(&self) -> &[u8] {
126        &self.body
127    }
128
129    /// Turns the `Response` into the inner `Vec<u8>`, the bytes that
130    /// make up the response's body. If you just need a `&[u8]`, use
131    /// [`as_bytes()`](#method.as_bytes) instead.
132    ///
133    /// # Example
134    ///
135    /// ```no_run
136    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
137    /// # let url = "http://example.org/";
138    /// let response = minreq::get(url).send()?;
139    /// println!("{:?}", response.into_bytes());
140    /// // This would error, as into_bytes consumes the Response:
141    /// // let x = response.status_code;
142    /// # Ok(())
143    /// # }
144    /// ```
145    pub fn into_bytes(self) -> Vec<u8> {
146        self.body
147    }
148    pub fn body_len(&self) -> u64{
149        let default_content_lenth = String::from("0");
150        let content_lenth = self.headers.get("content-length").unwrap_or(&default_content_lenth);
151        u64::from_str_radix(content_lenth.trim(), 10).unwrap_or(0)
152    }
153}
154
155/// An HTTP response, which is loaded lazily.
156///
157/// In comparison to [`Response`](struct.Response.html), this is
158/// returned from
159/// [`send_lazy()`](struct.Request.html#method.send_lazy), where as
160/// [`Response`](struct.Response.html) is returned from
161/// [`send()`](struct.Request.html#method.send).
162///
163/// In practice, "lazy loading" means that the bytes are only loaded
164/// as you iterate through them. The bytes are provided in the form of
165/// a `Result<(u8, usize), minreq::Error>`, as the reading operation
166/// can fail in various ways. The `u8` is the actual byte that was
167/// read, and `usize` is how many bytes we are expecting to read in
168/// the future (including this byte). Note, however, that the `usize`
169/// can change, particularly when the `Transfer-Encoding` is
170/// `chunked`: then it will reflect how many bytes are left of the
171/// current chunk. The expected size is capped at 16 KiB to avoid
172/// server-side DoS attacks targeted at clients accidentally reserving
173/// too much memory.
174///
175/// # Example
176/// ```no_run
177/// // This is how the normal Response works behind the scenes, and
178/// // how you might use ResponseLazy.
179/// # fn main() -> Result<(), minreq::Error> {
180/// let response = minreq::get("http://example.com").send_lazy()?;
181/// let mut vec = Vec::new();
182/// for result in response {
183///     let (byte, length) = result?;
184///     vec.reserve(length);
185///     vec.push(byte);
186/// }
187/// # Ok(())
188/// # }
189///
190/// ```
191pub struct ResponseLazy {
192    /// The status code of the response, eg. 404.
193    pub status_code: i32,
194    /// The reason phrase of the response, eg. "Not Found".
195    pub reason_phrase: String,
196    /// The headers of the response. The header field names (the
197    /// keys) are all lowercase.
198    pub headers: HashMap<String, String>,
199    /// The URL of the resource returned in this response. May differ from the
200    /// request URL if it was redirected or typo corrections were applied (e.g.
201    /// <http://example.com?foo=bar> would be corrected to
202    /// <http://example.com/?foo=bar>).
203    pub url: String,
204
205    pub stream: HttpStreamBytes,
206    state: HttpStreamState,
207    max_trailing_headers_size: Option<usize>,
208}
209
210pub type HttpStreamBytes = BufReader<HttpStream>;
211
212impl ResponseLazy {
213    pub(crate) async fn from_stream(
214        stream: HttpStream,
215        max_headers_size: Option<usize>,
216        max_status_line_len: Option<usize>,
217    ) -> Result<ResponseLazy, Error> {
218        let mut stream = BufReader::with_capacity(BACKING_READ_BUFFER_LENGTH, stream);
219        let ResponseMetadata {
220            status_code,
221            reason_phrase,
222            headers,
223            state,
224            max_trailing_headers_size,
225        } = read_metadata(&mut stream, max_headers_size, max_status_line_len).await?;
226
227        Ok(ResponseLazy {
228            status_code,
229            reason_phrase,
230            headers,
231            url: String::new(),
232            stream,
233            state,
234            max_trailing_headers_size,
235        })
236    }
237    async fn next(&mut self) -> Option<u8> {
238        use HttpStreamState::*;
239        match self.state {
240            EndOnClose => read_until_closed(&mut self.stream).await,
241            ContentLength(ref mut length) => {
242                read_with_content_length(&mut self.stream, length).await
243            }
244            Chunked(ref mut expecting_chunks, ref mut length, ref mut content_length) => {
245                read_chunked(
246                    &mut self.stream,
247                    &mut self.headers,
248                    expecting_chunks,
249                    length,
250                    content_length,
251                    self.max_trailing_headers_size,
252                )
253                .await
254            }
255        }
256    }
257}
258
259async fn read_until_closed(bytes: &mut HttpStreamBytes) -> Option<u8> {
260    let mut buf = [0u8];
261    if let Ok(len) = bytes.read(&mut buf).await {
262        if len != 0 {
263            return Some(buf[0]);
264        }
265    }
266    None
267}
268
269async fn read_with_content_length(
270    bytes: &mut HttpStreamBytes,
271    content_length: &mut usize,
272) -> Option<u8> {
273    if *content_length > 0 {
274        *content_length -= 1;
275        let mut buf = [0u8];
276        if let Ok(len) = bytes.read(&mut buf).await {
277            if len != 0 {
278                return Some(buf[0]);
279            }
280        }
281    }
282    None
283}
284
285async fn read_trailers(
286    bytes: &mut HttpStreamBytes,
287    headers: &mut HashMap<String, String>,
288    mut max_headers_size: Option<usize>,
289) -> Result<(), Error> {
290    loop {
291        let trailer_line = read_line(bytes).await?;
292        if let Some(ref mut max_headers_size) = max_headers_size {
293            *max_headers_size -= trailer_line.len() + 2;
294        }
295        if let Some((header, value)) = parse_header(trailer_line) {
296            headers.insert(header, value);
297        } else {
298            break;
299        }
300    }
301    Ok(())
302}
303
304async fn read_chunked(
305    bytes: &mut HttpStreamBytes,
306    headers: &mut HashMap<String, String>,
307    expecting_more_chunks: &mut bool,
308    chunk_length: &mut usize,
309    content_length: &mut usize,
310    max_trailing_headers_size: Option<usize>,
311) -> Option<u8> {
312    if !*expecting_more_chunks && *chunk_length == 0 {
313        return None;
314    }
315
316    if *chunk_length == 0 {
317        // Max length of the chunk length line is 1KB: not too long to
318        // take up much memory, long enough to tolerate some chunk
319        // extensions (which are ignored).
320
321        // Get the size of the next chunk
322        let length_line = match read_line(bytes).await {
323            Ok(line) => line,
324            Err(_) => return None,
325        };
326
327        // Note: the trim() and check for empty lines shouldn't be
328        // needed according to the RFC, but we might as well, it's a
329        // small change and it fixes a few servers.
330        let incoming_length = if length_line.is_empty() {
331            0
332        } else {
333            let length = if let Some(i) = length_line.find(';') {
334                length_line[..i].trim()
335            } else {
336                length_line.trim()
337            };
338            match usize::from_str_radix(length, 16) {
339                Ok(length) => length,
340                Err(_) => return None,
341            }
342        };
343
344        if incoming_length == 0 {
345            if let Err(_) = read_trailers(bytes, headers, max_trailing_headers_size).await {
346                return None;
347            }
348
349            *expecting_more_chunks = false;
350            headers.insert("content-length".to_string(), (*content_length).to_string());
351            headers.remove("transfer-encoding");
352            return None;
353        }
354        *chunk_length = incoming_length;
355        *content_length += incoming_length;
356    }
357
358    if *chunk_length > 0 {
359        *chunk_length -= 1;
360        let mut buf = [0u8];
361        if let Ok(len) = bytes.read(&mut buf).await {
362            if len == 1 {
363                // If we're at the end of the chunk...
364                if *chunk_length == 0 {
365                    //...read the trailing \r\n of the chunk, and
366                    // possibly return an error instead.
367
368                    // TODO: Maybe this could be written in a way
369                    // that doesn't discard the last ok byte if
370                    // the \r\n reading fails?
371                    if let Err(_) = read_line(bytes).await {
372                        return None;
373                    }
374                }
375
376                return Some(buf[0]);
377            } else {
378                return None;
379            }
380        }
381    }
382
383    None
384}
385
386enum HttpStreamState {
387    // No Content-Length, and Transfer-Encoding != chunked, so we just
388    // read unti lthe server closes the connection (this should be the
389    // fallback, if I read the rfc right).
390    EndOnClose,
391    // Content-Length was specified, read that amount of bytes
392    ContentLength(usize),
393    // Transfer-Encoding == chunked, so we need to save two pieces of
394    // information: are we expecting more chunks, how much is there
395    // left of the current chunk, and how much have we read? The last
396    // number is needed in order to provide an accurate Content-Length
397    // header after loading all the bytes.
398    Chunked(bool, usize, usize),
399}
400
401// This struct is just used in the Response and ResponseLazy
402// constructors, but not in their structs, for api-cleanliness
403// reasons. (Eg. response.status_code is much cleaner than
404// response.meta.status_code or similar.)
405struct ResponseMetadata {
406    status_code: i32,
407    reason_phrase: String,
408    headers: HashMap<String, String>,
409    state: HttpStreamState,
410    max_trailing_headers_size: Option<usize>,
411}
412
413async fn read_metadata(
414    stream: &mut HttpStreamBytes,
415    mut max_headers_size: Option<usize>,
416    _max_status_line_len: Option<usize>,
417) -> Result<ResponseMetadata, Error> {
418    let line = read_line(stream).await?;
419    let (status_code, reason_phrase) = parse_status_line(&line);
420
421    let mut headers = HashMap::new();
422    loop {
423        let line = read_line(stream).await?;
424        if line.is_empty() || line == "\r\n" {
425            // Body starts here
426            break;
427        }
428        let line = line.trim().to_string();
429        if let Some(ref mut max_headers_size) = max_headers_size {
430            *max_headers_size -= line.len() + 2;
431        }
432        if let Some(header) = parse_header(line) {
433            headers.insert(header.0.to_lowercase(), header.1);
434        }
435    }
436
437    let mut chunked = false;
438    let mut content_length = None;
439    for (header, value) in &headers {
440        // Handle the Transfer-Encoding header
441        if header.to_lowercase().trim() == "transfer-encoding"
442            && value.to_lowercase().trim() == "chunked"
443        {
444            chunked = true;
445        }
446
447        // Handle the Content-Length header
448        if header.to_lowercase().trim() == "content-length" {
449            match str::parse::<usize>(value.trim()) {
450                Ok(length) => content_length = Some(length),
451                Err(_) => return Err(Error::MalformedContentLength),
452            }
453        }
454    }
455
456    let state = if chunked {
457        HttpStreamState::Chunked(true, 0, 0)
458    } else if let Some(length) = content_length {
459        HttpStreamState::ContentLength(length)
460    } else {
461        HttpStreamState::EndOnClose
462    };
463
464    Ok(ResponseMetadata {
465        status_code,
466        reason_phrase,
467        headers,
468        state,
469        max_trailing_headers_size: max_headers_size,
470    })
471}
472
473pub async fn read_line<R: AsyncBufReadExt + Unpin>(reader: &mut R) -> Result<String, Error> {
474    let mut buffer = String::new();
475    match reader
476        .read_line(&mut buffer)
477        .await
478        .map_err(|_| Error::HeadersOverflow)
479    {
480        Ok(_) => Ok(buffer),
481        Err(_) => Err(Error::HeadersOverflow),
482    }
483}
484
485fn parse_status_line(line: &str) -> (i32, String) {
486    // sample status line format
487    // HTTP/1.1 200 OK
488    let mut status_code = String::with_capacity(3);
489    let mut reason_phrase = String::with_capacity(2);
490
491    let mut spaces = 0;
492
493    for c in line.chars() {
494        if spaces >= 2 {
495            reason_phrase.push(c);
496        }
497
498        if c == ' ' {
499            spaces += 1;
500        } else if spaces == 1 {
501            status_code.push(c);
502        }
503    }
504
505    if let Ok(status_code) = status_code.parse::<i32>() {
506        return (status_code, reason_phrase.trim().to_string());
507    }
508
509    (503, "Server did not provide a status line".to_string())
510}
511
512fn parse_header(mut line: String) -> Option<(String, String)> {
513    if let Some(location) = line.find(':') {
514        // Trim the first character of the header if it is a space,
515        // otherwise return everything after the ':'. This should
516        // preserve the behavior in versions <=2.0.1 in most cases
517        // (namely, ones where it was valid), where the first
518        // character after ':' was always cut off.
519        let value = if let Some(sp) = line.get(location + 1..location + 2) {
520            if sp == " " {
521                line[location + 2..].to_string()
522            } else {
523                line[location + 1..].to_string()
524            }
525        } else {
526            line[location + 1..].to_string()
527        };
528
529        line.truncate(location);
530        // Headers should be ascii, I'm pretty sure. If not, please open an issue.
531        line.make_ascii_lowercase();
532        return Some((line, value));
533    }
534    None
535}