ureq_proto/
body.rs

1use std::fmt;
2use std::io::Write;
3
4use http::{header, HeaderName, HeaderValue, Method};
5
6use crate::chunk::Dechunker;
7use crate::util::{compare_lowercase_ascii, log_data, Writer};
8use crate::Error;
9
10#[derive(Debug, Clone, Copy, Default)]
11pub(crate) struct BodyWriter {
12    mode: SenderMode,
13    ended: bool,
14}
15
16#[derive(Debug, Clone, Copy, Default)]
17enum SenderMode {
18    #[default]
19    None,
20    Sized(u64),
21    Chunked,
22}
23
24// This is 0x2800 in hex.
25pub(crate) const DEFAULT_CHUNK_SIZE: usize = 10 * 1024;
26// 4 is 0x2800 and the other + 4 is for the \r\n\r\n overhead.
27pub(crate) const DEFAULT_CHUNK_OVERHEAD: usize = 4 + 4;
28pub(crate) const DEFAULT_CHUNK_AND_OVERHEAD: usize = DEFAULT_CHUNK_SIZE + DEFAULT_CHUNK_OVERHEAD;
29
30impl BodyWriter {
31    pub fn new_none() -> Self {
32        BodyWriter {
33            mode: SenderMode::None,
34            ended: true,
35        }
36    }
37
38    pub fn new_chunked() -> Self {
39        BodyWriter {
40            mode: SenderMode::Chunked,
41            ended: false,
42        }
43    }
44
45    pub fn new_sized(size: u64) -> Self {
46        BodyWriter {
47            mode: SenderMode::Sized(size),
48            ended: false,
49        }
50    }
51
52    #[cfg(feature = "server")]
53    pub fn body_mode(&self) -> BodyMode {
54        match self.mode {
55            SenderMode::None => BodyMode::NoBody,
56            SenderMode::Sized(n) => BodyMode::LengthDelimited(n),
57            SenderMode::Chunked => BodyMode::Chunked,
58        }
59    }
60
61    pub fn has_body(&self) -> bool {
62        match self.mode {
63            SenderMode::Sized(n) => n > 0,
64            SenderMode::Chunked => true,
65            SenderMode::None => false,
66        }
67    }
68
69    pub fn is_chunked(&self) -> bool {
70        matches!(self.mode, SenderMode::Chunked)
71    }
72
73    pub fn write(&mut self, input: &[u8], w: &mut Writer) -> usize {
74        match &mut self.mode {
75            SenderMode::None => unreachable!(),
76            SenderMode::Sized(left) => {
77                let left_usize = (*left).min(usize::MAX as u64) as usize;
78                let to_write = w.available().min(input.len()).min(left_usize);
79
80                let success = w.try_write(|w| w.write_all(&input[..to_write]));
81                assert!(success);
82
83                *left -= to_write as u64;
84
85                if *left == 0 {
86                    self.ended = true;
87                }
88
89                to_write
90            }
91            SenderMode::Chunked => {
92                let mut input_used = 0;
93
94                if input.is_empty() {
95                    self.finish(w);
96                    self.ended = true;
97                } else {
98                    // The chunk size might be smaller than the entire input, in which case
99                    // we continue to send chunks frome the same input.
100                    while write_chunk(
101                        //
102                        &input[input_used..],
103                        &mut input_used,
104                        w,
105                        DEFAULT_CHUNK_SIZE,
106                    ) {}
107                }
108
109                input_used
110            }
111        }
112    }
113
114    fn finish(&self, w: &mut Writer) -> bool {
115        if self.is_chunked() {
116            let success = w.try_write(|w| w.write_all(b"0\r\n\r\n"));
117            if !success {
118                return false;
119            }
120        }
121        true
122    }
123
124    pub(crate) fn body_header(&self) -> (HeaderName, HeaderValue) {
125        match self.mode {
126            SenderMode::None => unreachable!(),
127            SenderMode::Sized(size) => (
128                header::CONTENT_LENGTH,
129                // TODO(martin): avoid allocation here
130                HeaderValue::from_str(&size.to_string()).unwrap(),
131            ),
132            SenderMode::Chunked => (
133                header::TRANSFER_ENCODING,
134                HeaderValue::from_static("chunked"),
135            ),
136        }
137    }
138
139    pub(crate) fn is_ended(&self) -> bool {
140        self.ended
141    }
142
143    pub(crate) fn left_to_send(&self) -> Option<u64> {
144        match self.mode {
145            SenderMode::Sized(v) => Some(v),
146            _ => None,
147        }
148    }
149
150    pub(crate) fn consume_direct_write(&mut self, amount: usize) {
151        match &mut self.mode {
152            SenderMode::None => unreachable!(),
153            SenderMode::Sized(left) => {
154                *left -= amount as u64;
155
156                if *left == 0 {
157                    self.ended = true;
158                }
159            }
160            SenderMode::Chunked => unreachable!(),
161        }
162    }
163}
164
165#[allow(unused)]
166pub(crate) fn calculate_chunk_overhead(output_len: usize) -> usize {
167    // The + 1 and floor() is to make even powers of 16 right.
168    // The + 4 is for the \r\n overhead.
169    //
170    // A chunk is with length is:
171    // <digits_in_hex>\r\n
172    // <chunk>\r\n
173    //
174    // And an end/0-sized chunk is:
175    // 0\r\n
176    // \r\n
177    ((output_len as f64).log(16.0) + 1.0).floor() as usize + 4
178}
179
180pub(crate) fn calculate_max_input(output_len: usize) -> usize {
181    let chunks = output_len / DEFAULT_CHUNK_AND_OVERHEAD;
182    let remaining = output_len % DEFAULT_CHUNK_AND_OVERHEAD;
183
184    // We can safely assume remaining is < DEFAULT_CHUNK_AND_OVERHEAD which requires
185    // DEFAULT_CHUNK_HEX number of chars to write. Thus whatever the remaining length is,
186    // it will fit into DEFAULT_CHUNK_HEX + 4 (for the \r\n overhead)
187    let tail = remaining.saturating_sub(DEFAULT_CHUNK_OVERHEAD);
188
189    chunks * DEFAULT_CHUNK_SIZE + tail
190}
191
192fn write_chunk(input: &[u8], input_used: &mut usize, w: &mut Writer, max_chunk: usize) -> bool {
193    // TODO(martin): Redo this to  try and calculate a perfect fit of the
194    // input into the output.
195
196    // 5 is the smallest possible overhead
197    let available = w.available().saturating_sub(5);
198
199    let to_write = input.len().min(max_chunk).min(available);
200
201    let success = w.try_write(|w| {
202        // chunk length
203        write!(w, "{:0x?}\r\n", to_write)?;
204
205        // chunk
206        w.write_all(&input[..to_write])?;
207
208        // chunk end
209        write!(w, "\r\n")
210    });
211
212    if success {
213        *input_used += to_write;
214    }
215
216    // write another chunk?
217    success && input.len() > to_write
218}
219
220#[derive(Clone, Copy, PartialEq, Eq)]
221pub(crate) enum BodyReader {
222    /// No body is expected either due to the status or method.
223    NoBody,
224    /// Delimited by content-length.
225    /// The value is what's left to receive.
226    LengthDelimited(u64),
227    /// Chunked transfer encoding
228    Chunked(Dechunker),
229    /// Expect remote to close at end of body.
230    #[cfg(feature = "client")]
231    CloseDelimited,
232}
233
234/// Kind of body
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum BodyMode {
237    /// No body is expected either due to the status or method.
238    NoBody,
239    /// Delimited by content-length.
240    /// The value is what's left to receive.
241    LengthDelimited(u64),
242    /// Chunked transfer encoding
243    Chunked,
244    /// Expect remote to close at end of body.
245    CloseDelimited,
246}
247
248impl BodyReader {
249    pub fn body_mode(&self) -> BodyMode {
250        match self {
251            BodyReader::NoBody => BodyMode::NoBody,
252            // TODO(martin): if we read body_mode at the wrong time, this v is
253            // not the total length, but the the remaining.
254            BodyReader::LengthDelimited(v) => BodyMode::LengthDelimited(*v),
255            BodyReader::Chunked(_) => BodyMode::Chunked,
256            #[cfg(feature = "client")]
257            BodyReader::CloseDelimited => BodyMode::CloseDelimited,
258        }
259    }
260
261    #[cfg(feature = "server")]
262    pub fn has_body(&self) -> bool {
263        match self {
264            BodyReader::NoBody => false,
265            BodyReader::LengthDelimited(v) if *v == 0 => false,
266            _ => true,
267        }
268    }
269
270    #[cfg(feature = "server")]
271    pub fn for_request<'a>(
272        http10: bool,
273        method: &Method,
274        force_send: bool,
275        header_lookup: &'a dyn Fn(http::HeaderName) -> Option<&'a str>,
276    ) -> Result<Self, Error> {
277        use crate::ext::MethodExt;
278
279        if !method.allow_request_body() && !force_send {
280            return Ok(Self::NoBody);
281        }
282
283        let ret = Self::header_defined(http10, header_lookup)?.unwrap_or_else(|| {
284            if !http10 && method.need_request_body() {
285                Self::Chunked(Dechunker::new())
286            } else {
287                Self::NoBody
288            }
289        });
290
291        Ok(ret)
292    }
293
294    #[cfg(feature = "client")]
295    // https://datatracker.ietf.org/doc/html/rfc2616#section-4.3
296    pub fn for_response<'a>(
297        http10: bool,
298        method: &Method,
299        status_code: u16,
300        force_recv: bool,
301        header_lookup: &'a dyn Fn(HeaderName) -> Option<&'a str>,
302    ) -> Result<Self, Error> {
303        let header_defined =
304            Self::header_defined(http10, header_lookup)?.unwrap_or(Self::CloseDelimited);
305
306        // Is body mode being defined in headers?
307        let body_mode_defined = header_defined.body_mode() != BodyMode::CloseDelimited;
308
309        // Are we allowed to receive a body?
310        let body_allowed = response_body_allowed(method, status_code, header_defined.body_mode());
311
312        if body_mode_defined && (body_allowed || force_recv) {
313            // Response contains a body header (even Content-Length: 0)
314            // and expects a body or is forcing a body.
315            Ok(header_defined)
316        } else if !body_mode_defined && body_allowed {
317            // Response has no body header but a body might follow.
318            // Assume close-delimited body.
319            Ok(header_defined)
320        } else {
321            Ok(Self::NoBody)
322        }
323    }
324
325    fn header_defined<'a>(
326        http10: bool,
327        header_lookup: &'a dyn Fn(HeaderName) -> Option<&'a str>,
328    ) -> Result<Option<Self>, Error> {
329        let mut content_length: Option<u64> = None;
330        let mut chunked = false;
331
332        // for head in headers {
333        if let Some(value) = header_lookup(header::CONTENT_LENGTH) {
334            let v = value
335                .parse::<u64>()
336                .map_err(|_| Error::BadContentLengthHeader)?;
337            if content_length.is_some() {
338                return Err(Error::TooManyContentLengthHeaders);
339            }
340            content_length = Some(v);
341        }
342
343        if let Some(value) = header_lookup(header::TRANSFER_ENCODING) {
344            // Header can repeat, stop looking if we found "chunked"
345            chunked = value
346                .split(',')
347                .map(|v| v.trim())
348                .any(|v| compare_lowercase_ascii(v, "chunked"));
349        }
350
351        if chunked && !http10 {
352            // https://datatracker.ietf.org/doc/html/rfc2616#section-4.4
353            // Messages MUST NOT include both a Content-Length header field and a
354            // non-identity transfer-coding. If the message does include a non-
355            // identity transfer-coding, the Content-Length MUST be ignored.
356            return Ok(Some(Self::Chunked(Dechunker::new())));
357        }
358
359        if let Some(len) = content_length {
360            return Ok(Some(Self::LengthDelimited(len)));
361        }
362
363        Ok(None)
364    }
365
366    /// A request is allowed to have a body based solely upon it's method. A response,
367    /// however, requires a number of factors to be examined. This function checks these
368    /// factors.
369    pub fn read(
370        &mut self,
371        src: &[u8],
372        dst: &mut [u8],
373        stop_on_chunk_boundary: bool,
374    ) -> Result<(usize, usize), Error> {
375        // unwrap is ok because we can't be in state RECV_BODY without setting it.
376        let part = match self {
377            BodyReader::LengthDelimited(_) => self.read_limit(src, dst),
378            BodyReader::Chunked(_) => self.read_chunked(src, dst, stop_on_chunk_boundary),
379            BodyReader::NoBody => return Ok((0, 0)),
380            #[cfg(feature = "client")]
381            BodyReader::CloseDelimited => self.read_unlimit(src, dst),
382        }?;
383
384        log_data(&src[..part.0]);
385
386        Ok(part)
387    }
388
389    fn read_limit(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize), Error> {
390        let left = match self {
391            BodyReader::LengthDelimited(v) => v,
392            _ => unreachable!(),
393        };
394        let left_usize = (*left).min(usize::MAX as u64) as usize;
395
396        let to_read = src.len().min(dst.len()).min(left_usize);
397
398        dst[..to_read].copy_from_slice(&src[..to_read]);
399
400        *left -= to_read as u64;
401
402        Ok((to_read, to_read))
403    }
404
405    fn read_chunked(
406        &mut self,
407        src: &[u8],
408        dst: &mut [u8],
409        stop_on_chunk_boundary: bool,
410    ) -> Result<(usize, usize), Error> {
411        let dechunker = match self {
412            BodyReader::Chunked(v) => v,
413            _ => unreachable!(),
414        };
415
416        let mut input_used = 0;
417        let mut output_used = 0;
418
419        loop {
420            let (i, o) = dechunker.parse_input(&src[input_used..], &mut dst[output_used..])?;
421
422            input_used += i;
423            output_used += o;
424
425            if i == 0 || input_used == src.len() || output_used == dst.len() {
426                break;
427            }
428
429            if dechunker.is_ended() {
430                break;
431            }
432
433            if stop_on_chunk_boundary && dechunker.is_on_chunk_boundary() {
434                break;
435            }
436        }
437
438        Ok((input_used, output_used))
439    }
440
441    #[cfg(feature = "client")]
442    fn read_unlimit(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize), Error> {
443        let to_read = src.len().min(dst.len());
444
445        dst[..to_read].copy_from_slice(&src[..to_read]);
446
447        Ok((to_read, to_read))
448    }
449
450    pub fn is_ended(&self) -> bool {
451        match self {
452            BodyReader::NoBody => true,
453            BodyReader::LengthDelimited(v) => *v == 0,
454            BodyReader::Chunked(v) => v.is_ended(),
455            #[cfg(feature = "client")]
456            BodyReader::CloseDelimited => false,
457        }
458    }
459
460    #[cfg(feature = "client")]
461    pub fn is_ended_chunked(&self) -> bool {
462        match self {
463            BodyReader::Chunked(v) => v.is_ending() || v.is_ended(),
464            _ => false,
465        }
466    }
467
468    pub(crate) fn is_on_chunk_boundary(&self) -> bool {
469        match self {
470            BodyReader::NoBody => false,
471            BodyReader::LengthDelimited(_) => false,
472            BodyReader::Chunked(v) => v.is_on_chunk_boundary(),
473            #[cfg(feature = "client")]
474            BodyReader::CloseDelimited => false,
475        }
476    }
477}
478
479/// A request is allowed to have a body based solely upon it's method. A response,
480/// however, requires a number of factors to be examined. This function checks these
481/// factors.
482pub fn response_body_allowed(method: &Method, status_code: u16, body_mode: BodyMode) -> bool {
483    let is_success = (200..=299).contains(&status_code);
484    let is_informational = (100..=199).contains(&status_code);
485    let is_redirect = (300..=399).contains(&status_code) && status_code != 304;
486
487    // Implicitly we know that CloseDelimited means no header indicated that
488    // there was a body.
489    let has_body_header = body_mode != BodyMode::CloseDelimited;
490
491    // https://datatracker.ietf.org/doc/html/rfc2616#section-4.3
492    // All responses to the HEAD request method
493    // MUST NOT include a message-body, even though the presence of entity-
494    // header fields might lead one to believe they do.
495    let body_not_allowed = method == Method::HEAD ||
496            // A client MUST ignore any Content-Length or Transfer-Encoding
497            // header fields received in a successful response to CONNECT.
498            is_success && method == Method::CONNECT ||
499            // All 1xx (informational), 204 (no content), and 304 (not modified) responses
500            // MUST NOT include a message-body.
501            is_informational ||
502            matches!(status_code, 204 | 304) ||
503            // Surprisingly, redirects may have a body. Whether they do we need to
504            // check the existence of content-length or transfer-encoding headers.
505            is_redirect && !has_body_header;
506
507    !body_not_allowed
508}
509
510impl fmt::Debug for BodyReader {
511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512        match self {
513            Self::NoBody => write!(f, "NoBody"),
514            Self::LengthDelimited(arg0) => f.debug_tuple("LengthDelimited").field(arg0).finish(),
515            Self::Chunked(_) => write!(f, "Chunked"),
516            #[cfg(feature = "client")]
517            Self::CloseDelimited => write!(f, "CloseDelimited"),
518        }
519    }
520}
521
522#[cfg(test)]
523mod test {
524    use super::*;
525
526    #[test]
527    fn test_calculate_max_input() {
528        assert_eq!(calculate_max_input(0), 0);
529        assert_eq!(calculate_max_input(1), 0);
530        assert_eq!(calculate_max_input(2), 0);
531        assert_eq!(calculate_max_input(9), 1);
532        assert_eq!(calculate_max_input(10), 2);
533        assert_eq!(calculate_max_input(11), 3);
534
535        assert_eq!(calculate_max_input(10247), 10239);
536        assert_eq!(calculate_max_input(10248), 10240);
537        assert_eq!(calculate_max_input(10249), 10240);
538        assert_eq!(calculate_max_input(10250), 10240);
539
540        assert_eq!(calculate_max_input(10257), 10241);
541        assert_eq!(calculate_max_input(10258), 10242);
542        assert_eq!(calculate_max_input(10259), 10243);
543    }
544}