Skip to main content

ureq_proto/
body.rs

1use std::fmt;
2use std::io::Write;
3
4use http::{HeaderMap, HeaderName, HeaderValue, Method, header};
5
6use crate::Error;
7use crate::chunk::Dechunker;
8use crate::util::{Writer, compare_lowercase_ascii, log_data};
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(
272        http10: bool,
273        method: &Method,
274        force_send: bool,
275        headers: &HeaderMap,
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, headers)?.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(
297        http10: bool,
298        method: &Method,
299        status_code: u16,
300        force_recv: bool,
301        headers: &HeaderMap,
302    ) -> Result<Self, Error> {
303        let header_defined = Self::header_defined(http10, headers)?.unwrap_or(Self::CloseDelimited);
304
305        // Is body mode being defined in headers?
306        let body_mode_defined = header_defined.body_mode() != BodyMode::CloseDelimited;
307
308        // Are we allowed to receive a body?
309        let body_allowed = response_body_allowed(method, status_code, header_defined.body_mode());
310
311        if body_mode_defined && (body_allowed || force_recv) {
312            // Response contains a body header (even Content-Length: 0)
313            // and expects a body or is forcing a body.
314            Ok(header_defined)
315        } else if !body_mode_defined && body_allowed {
316            // Response has no body header but a body might follow.
317            // Assume close-delimited body.
318            Ok(header_defined)
319        } else {
320            Ok(Self::NoBody)
321        }
322    }
323
324    fn header_defined(http10: bool, headers: &HeaderMap) -> Result<Option<Self>, Error> {
325        let content_length = parse_content_length(headers.get_all(header::CONTENT_LENGTH).iter())?;
326
327        let mut chunked = false;
328
329        if let Some(value) = headers
330            .get(header::TRANSFER_ENCODING)
331            .and_then(|v| v.to_str().ok())
332        {
333            // Header can repeat, stop looking if we found "chunked"
334            chunked = value
335                .split(',')
336                .map(|v| v.trim())
337                .any(|v| compare_lowercase_ascii(v, "chunked"));
338        }
339
340        if chunked && !http10 {
341            // https://datatracker.ietf.org/doc/html/rfc2616#section-4.4
342            // Messages MUST NOT include both a Content-Length header field and a
343            // non-identity transfer-coding. If the message does include a non-
344            // identity transfer-coding, the Content-Length MUST be ignored.
345            return Ok(Some(Self::Chunked(Dechunker::new())));
346        }
347
348        if let Some(len) = content_length {
349            return Ok(Some(Self::LengthDelimited(len)));
350        }
351
352        Ok(None)
353    }
354
355    /// A request is allowed to have a body based solely upon it's method. A response,
356    /// however, requires a number of factors to be examined. This function checks these
357    /// factors.
358    pub fn read(
359        &mut self,
360        src: &[u8],
361        dst: &mut [u8],
362        stop_on_chunk_boundary: bool,
363    ) -> Result<(usize, usize), Error> {
364        // unwrap is ok because we can't be in state RECV_BODY without setting it.
365        let part = match self {
366            BodyReader::LengthDelimited(_) => self.read_limit(src, dst),
367            BodyReader::Chunked(_) => self.read_chunked(src, dst, stop_on_chunk_boundary),
368            BodyReader::NoBody => return Ok((0, 0)),
369            #[cfg(feature = "client")]
370            BodyReader::CloseDelimited => self.read_unlimit(src, dst),
371        }?;
372
373        log_data(&src[..part.0]);
374
375        Ok(part)
376    }
377
378    fn read_limit(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize), Error> {
379        let left = match self {
380            BodyReader::LengthDelimited(v) => v,
381            _ => unreachable!(),
382        };
383        let left_usize = (*left).min(usize::MAX as u64) as usize;
384
385        let to_read = src.len().min(dst.len()).min(left_usize);
386
387        dst[..to_read].copy_from_slice(&src[..to_read]);
388
389        *left -= to_read as u64;
390
391        Ok((to_read, to_read))
392    }
393
394    fn read_chunked(
395        &mut self,
396        src: &[u8],
397        dst: &mut [u8],
398        stop_on_chunk_boundary: bool,
399    ) -> Result<(usize, usize), Error> {
400        let dechunker = match self {
401            BodyReader::Chunked(v) => v,
402            _ => unreachable!(),
403        };
404
405        let mut input_used = 0;
406        let mut output_used = 0;
407
408        loop {
409            let (i, o) = dechunker.parse_input(&src[input_used..], &mut dst[output_used..])?;
410
411            input_used += i;
412            output_used += o;
413
414            if i == 0 || input_used == src.len() || output_used == dst.len() {
415                break;
416            }
417
418            if dechunker.is_ended() {
419                break;
420            }
421
422            if stop_on_chunk_boundary && dechunker.is_on_chunk_boundary() {
423                break;
424            }
425        }
426
427        Ok((input_used, output_used))
428    }
429
430    #[cfg(feature = "client")]
431    fn read_unlimit(&mut self, src: &[u8], dst: &mut [u8]) -> Result<(usize, usize), Error> {
432        let to_read = src.len().min(dst.len());
433
434        dst[..to_read].copy_from_slice(&src[..to_read]);
435
436        Ok((to_read, to_read))
437    }
438
439    pub fn is_ended(&self) -> bool {
440        match self {
441            BodyReader::NoBody => true,
442            BodyReader::LengthDelimited(v) => *v == 0,
443            BodyReader::Chunked(v) => v.is_ended(),
444            #[cfg(feature = "client")]
445            BodyReader::CloseDelimited => false,
446        }
447    }
448
449    #[cfg(feature = "client")]
450    pub fn is_ended_chunked(&self) -> bool {
451        match self {
452            BodyReader::Chunked(v) => v.is_ending() || v.is_ended(),
453            _ => false,
454        }
455    }
456
457    pub(crate) fn is_on_chunk_boundary(&self) -> bool {
458        match self {
459            BodyReader::NoBody => false,
460            BodyReader::LengthDelimited(_) => false,
461            BodyReader::Chunked(v) => v.is_on_chunk_boundary(),
462            #[cfg(feature = "client")]
463            BodyReader::CloseDelimited => false,
464        }
465    }
466}
467
468/// A request is allowed to have a body based solely upon it's method. A response,
469/// however, requires a number of factors to be examined. This function checks these
470/// factors.
471pub fn response_body_allowed(method: &Method, status_code: u16, body_mode: BodyMode) -> bool {
472    let is_success = (200..=299).contains(&status_code);
473    let is_informational = (100..=199).contains(&status_code);
474    let is_redirect = (300..=399).contains(&status_code) && status_code != 304;
475
476    // Implicitly we know that CloseDelimited means no header indicated that
477    // there was a body.
478    let has_body_header = body_mode != BodyMode::CloseDelimited;
479
480    // https://datatracker.ietf.org/doc/html/rfc2616#section-4.3
481    // All responses to the HEAD request method
482    // MUST NOT include a message-body, even though the presence of entity-
483    // header fields might lead one to believe they do.
484    let body_not_allowed = method == Method::HEAD ||
485            // A client MUST ignore any Content-Length or Transfer-Encoding
486            // header fields received in a successful response to CONNECT.
487            is_success && method == Method::CONNECT ||
488            // All 1xx (informational), 204 (no content), and 304 (not modified) responses
489            // MUST NOT include a message-body.
490            is_informational ||
491            matches!(status_code, 204 | 304) ||
492            // Surprisingly, redirects may have a body. Whether they do we need to
493            // check the existence of content-length or transfer-encoding headers.
494            is_redirect && !has_body_header;
495
496    !body_not_allowed
497}
498
499impl fmt::Debug for BodyReader {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        match self {
502            Self::NoBody => write!(f, "NoBody"),
503            Self::LengthDelimited(arg0) => f.debug_tuple("LengthDelimited").field(arg0).finish(),
504            Self::Chunked(_) => write!(f, "Chunked"),
505            #[cfg(feature = "client")]
506            Self::CloseDelimited => write!(f, "CloseDelimited"),
507        }
508    }
509}
510
511/// Parse the `Content-Length` of a message from all of its header lines.
512///
513/// This follows RFC 9110 §8.6 and RFC 9112 §6.3 the way libcurl does.
514///
515/// * A value is `1*DIGIT`. Leading zeros are allowed. A sign, whitespace
516///   inside the number or any other character is an error.
517/// * Repeated header lines are equivalent to one comma separated list
518///   (RFC 9110 §5.3). Every element must be a valid value and all elements
519///   must be the same number, `42` and `042` included. Differing values are
520///   rejected, there is no first-wins or last-wins.
521/// * An empty value, an empty list element and a number that does not fit
522///   in a `u64` are errors.
523///
524/// Returns `Ok(None)` when there is no `Content-Length` header at all.
525pub(crate) fn parse_content_length<'a>(
526    values: impl Iterator<Item = &'a HeaderValue>,
527) -> Result<Option<u64>, Error> {
528    let mut result = None;
529
530    for value in values {
531        for element in value.as_bytes().split(|b| *b == b',') {
532            let n = parse_content_length_value(element)?;
533
534            if result.is_some_and(|prev| prev != n) {
535                return Err(Error::TooManyContentLengthHeaders);
536            }
537
538            result = Some(n);
539        }
540    }
541
542    Ok(result)
543}
544
545/// Parse one `Content-Length` value: `1*DIGIT` with optional surrounding
546/// whitespace, nothing else.
547///
548/// This is the grammar a sender must produce (RFC 9110 §8.6), so it is what
549/// the outgoing request and response analyzers use. The list tolerance in
550/// [`parse_content_length`] is for received messages only.
551pub(crate) fn parse_content_length_value(value: &[u8]) -> Result<u64, Error> {
552    let trimmed = trim_ows(value);
553
554    if trimmed.is_empty() || !trimmed.iter().all(u8::is_ascii_digit) {
555        return Err(Error::BadContentLengthHeader);
556    }
557
558    // Only ASCII digits, so this is valid UTF-8 and cannot carry a sign.
559    // A number too large for u64 fails to parse.
560    std::str::from_utf8(trimmed)
561        .ok()
562        .and_then(|s| s.parse::<u64>().ok())
563        .ok_or(Error::BadContentLengthHeader)
564}
565
566/// Strip optional whitespace (SP / HTAB) from both ends.
567fn trim_ows(mut b: &[u8]) -> &[u8] {
568    while let [b' ' | b'\t', rest @ ..] = b {
569        b = rest;
570    }
571    while let [rest @ .., b' ' | b'\t'] = b {
572        b = rest;
573    }
574    b
575}
576
577#[cfg(test)]
578mod test {
579    use super::*;
580
581    fn cl(values: &[&str]) -> Result<Option<u64>, Error> {
582        let headers: Vec<HeaderValue> = values
583            .iter()
584            .map(|v| HeaderValue::from_bytes(v.as_bytes()).unwrap())
585            .collect();
586        parse_content_length(headers.iter())
587    }
588
589    #[test]
590    fn content_length_single_values() {
591        assert_eq!(cl(&[]), Ok(None));
592        assert_eq!(cl(&["0"]), Ok(Some(0)));
593        assert_eq!(cl(&["42"]), Ok(Some(42)));
594        assert_eq!(cl(&["042"]), Ok(Some(42)));
595        assert_eq!(cl(&[" 42 "]), Ok(Some(42)));
596        assert_eq!(cl(&["18446744073709551615"]), Ok(Some(u64::MAX)));
597    }
598
599    #[test]
600    fn content_length_repeats_must_agree() {
601        assert_eq!(cl(&["42", "42"]), Ok(Some(42)));
602        assert_eq!(cl(&["42, 42"]), Ok(Some(42)));
603        assert_eq!(cl(&["42", "042"]), Ok(Some(42)));
604        assert_eq!(cl(&["42,42", "42"]), Ok(Some(42)));
605        assert_eq!(cl(&["42", "43"]), Err(Error::TooManyContentLengthHeaders));
606        assert_eq!(cl(&["42, 43"]), Err(Error::TooManyContentLengthHeaders));
607        assert_eq!(
608            cl(&["43", "42, 42"]),
609            Err(Error::TooManyContentLengthHeaders)
610        );
611    }
612
613    #[test]
614    fn content_length_must_be_digits() {
615        assert_eq!(cl(&["+42"]), Err(Error::BadContentLengthHeader));
616        assert_eq!(cl(&["-1"]), Err(Error::BadContentLengthHeader));
617        assert_eq!(cl(&["4 2"]), Err(Error::BadContentLengthHeader));
618        assert_eq!(cl(&["42abc"]), Err(Error::BadContentLengthHeader));
619        assert_eq!(cl(&["0x2a"]), Err(Error::BadContentLengthHeader));
620        assert_eq!(cl(&[""]), Err(Error::BadContentLengthHeader));
621        assert_eq!(cl(&["  "]), Err(Error::BadContentLengthHeader));
622        assert_eq!(cl(&["42,"]), Err(Error::BadContentLengthHeader));
623        assert_eq!(cl(&["42,,42"]), Err(Error::BadContentLengthHeader));
624        assert_eq!(cl(&["42", ""]), Err(Error::BadContentLengthHeader));
625        assert_eq!(
626            cl(&["18446744073709551616"]),
627            Err(Error::BadContentLengthHeader)
628        );
629        assert_eq!(
630            cl(&["99999999999999999999999"]),
631            Err(Error::BadContentLengthHeader)
632        );
633    }
634
635    #[test]
636    fn test_calculate_max_input() {
637        assert_eq!(calculate_max_input(0), 0);
638        assert_eq!(calculate_max_input(1), 0);
639        assert_eq!(calculate_max_input(2), 0);
640        assert_eq!(calculate_max_input(9), 1);
641        assert_eq!(calculate_max_input(10), 2);
642        assert_eq!(calculate_max_input(11), 3);
643
644        assert_eq!(calculate_max_input(10247), 10239);
645        assert_eq!(calculate_max_input(10248), 10240);
646        assert_eq!(calculate_max_input(10249), 10240);
647        assert_eq!(calculate_max_input(10250), 10240);
648
649        assert_eq!(calculate_max_input(10257), 10241);
650        assert_eq!(calculate_max_input(10258), 10242);
651        assert_eq!(calculate_max_input(10259), 10243);
652    }
653}