Skip to main content

simoom_http1/
parse.rs

1//! Request, response and field-section parsing (RFC 9112 §2–§6).
2
3use crate::{Error, Framing, Header, Limits, Request, Response, Status, TargetForm, Version};
4use simoom_sys::scan::{field_line, find_byte, scan, FIELD_VALUE, TCHAR, URI};
5
6/// What the field section said about framing and the connection.
7#[derive(Default)]
8struct Semantics<'b> {
9    host: Option<&'b [u8]>,
10    hosts: u8,
11    content_length: Option<u64>,
12    te_codings: u32,
13    te_chunked: u32,
14    te_last_chunked: bool,
15    close: bool,
16    keep_alive: bool,
17    conn_upgrade: bool,
18    expect_continue: bool,
19    expect_other: bool,
20    upgrade: Option<&'b [u8]>,
21}
22
23#[derive(Clone, Copy, PartialEq, Eq)]
24enum Section {
25    Head,
26    Trailers,
27}
28
29#[inline]
30fn is_ws(b: u8) -> bool {
31    b == b' ' || b == b'\t'
32}
33
34fn trim(mut v: &[u8]) -> &[u8] {
35    while let [first, rest @ ..] = v {
36        if !is_ws(*first) {
37            break;
38        }
39        v = rest;
40    }
41    while let [rest @ .., last] = v {
42        if !is_ws(*last) {
43            break;
44        }
45        v = rest;
46    }
47    v
48}
49
50/// True if the comma-separated list `value` contains `token` (case-insensitive), as in
51/// `has_token(connection_value, b"close")`.
52pub fn has_token(value: &[u8], token: &[u8]) -> bool {
53    value.split(|&b| b == b',').any(|t| trim(t).eq_ignore_ascii_case(token))
54}
55
56fn hex(b: u8) -> bool {
57    b.is_ascii_hexdigit()
58}
59
60/// `uri-host [":" port]` (RFC 9110 §7.2); with `need_port` the port must be present and
61/// non-empty, as in the authority form.
62fn valid_host(v: &[u8], need_port: bool) -> bool {
63    if v.is_empty() {
64        return !need_port;
65    }
66    let rest = if v[0] == b'[' {
67        let Some(end) = v.iter().position(|&b| b == b']') else { return false };
68        let inner = &v[1..end];
69        if inner.is_empty() || !inner.iter().all(|&b| hex(b) || b == b':' || b == b'.') {
70            return false;
71        }
72        &v[end + 1..]
73    } else {
74        let colon = v.iter().position(|&b| b == b':').unwrap_or(v.len());
75        let host = &v[..colon];
76        let mut i = 0;
77        while i < host.len() {
78            let b = host[i];
79            if b == b'%' {
80                if i + 2 >= host.len() || !hex(host[i + 1]) || !hex(host[i + 2]) {
81                    return false;
82                }
83                i += 3;
84                continue;
85            }
86            if !(b.is_ascii_alphanumeric() || b"-._~!$&'()*+,;=".contains(&b)) {
87                return false;
88            }
89            i += 1;
90        }
91        if host.is_empty() {
92            return false;
93        }
94        &v[colon..]
95    };
96    match rest {
97        [] => !need_port,
98        [b':', port @ ..] => port.iter().all(u8::is_ascii_digit) && (!need_port || !port.is_empty()),
99        _ => false,
100    }
101}
102
103/// `absolute-URI`: a scheme, `:`, and the rest (already checked byte by byte); brackets only
104/// inside the authority.
105fn valid_absolute(t: &[u8], brackets: bool) -> bool {
106    let Some(colon) = t.iter().position(|&b| b == b':') else { return false };
107    let scheme = &t[..colon];
108    if scheme.is_empty()
109        || !scheme[0].is_ascii_alphabetic()
110        || !scheme.iter().all(|&b| b.is_ascii_alphanumeric() || b == b'+' || b == b'-' || b == b'.')
111    {
112        return false;
113    }
114    // userinfo hides the real authority (`http://trusted@evil/`): RFC 9110 §4.2.4 says to treat
115    // it as an error.
116    if let Some(rest) = t[colon + 1..].strip_prefix(b"//") {
117        let end = rest.iter().position(|&b| b == b'/' || b == b'?').unwrap_or(rest.len());
118        if rest[..end].contains(&b'@') {
119            return false;
120        }
121    }
122    if !brackets {
123        return true;
124    }
125    let Some(rest) = t[colon + 1..].strip_prefix(b"//") else { return false };
126    let auth_end = rest.iter().position(|&b| b == b'/' || b == b'?').unwrap_or(rest.len());
127    let auth = &rest[..auth_end];
128    let host = match auth.iter().rposition(|&b| b == b'@') {
129        Some(at) => &auth[at + 1..],
130        None => auth,
131    };
132    !rest[auth_end..].iter().any(|&b| b == b'[' || b == b']') && valid_host(host, false)
133}
134
135fn parse_content_length(v: &[u8]) -> Result<u64, Error> {
136    let digits = |s: &[u8]| -> Option<u64> {
137        if s.is_empty() {
138            return None;
139        }
140        s.iter().try_fold(0u64, |n, &b| {
141            if b.is_ascii_digit() {
142                n.checked_mul(10)?.checked_add(u64::from(b - b'0'))
143            } else {
144                None
145            }
146        })
147    };
148    if let Some(n) = digits(v) {
149        return Ok(n);
150    }
151    if v.contains(&b',') && v.split(|&b| b == b',').all(|p| digits(trim(p)).is_some()) {
152        return Err(Error::DuplicateContentLength);
153    }
154    Err(Error::InvalidContentLength)
155}
156
157impl<'b> Semantics<'b> {
158    fn field(&mut self, name: &[u8], value: &'b [u8]) -> Result<(), Error> {
159        match name.len() {
160            4 if name.eq_ignore_ascii_case(b"host") => {
161                self.hosts = self.hosts.saturating_add(1);
162                if self.hosts > 1 {
163                    return Err(Error::DuplicateHost);
164                }
165                if !valid_host(value, false) {
166                    return Err(Error::InvalidHost);
167                }
168                self.host = Some(value);
169            }
170            6 if name.eq_ignore_ascii_case(b"expect") => {
171                if value.eq_ignore_ascii_case(b"100-continue") {
172                    self.expect_continue = true;
173                } else {
174                    self.expect_other = true;
175                }
176            }
177            7 if name.eq_ignore_ascii_case(b"upgrade") => {
178                if self.upgrade.is_some() {
179                    return Err(Error::DuplicateUpgrade);
180                }
181                self.upgrade = Some(value);
182            }
183            10 if name.eq_ignore_ascii_case(b"connection") => {
184                for t in value.split(|&b| b == b',') {
185                    let t = trim(t);
186                    if t.eq_ignore_ascii_case(b"close") {
187                        self.close = true;
188                    } else if t.eq_ignore_ascii_case(b"keep-alive") {
189                        self.keep_alive = true;
190                    } else if t.eq_ignore_ascii_case(b"upgrade") {
191                        self.conn_upgrade = true;
192                    }
193                }
194            }
195            14 if name.eq_ignore_ascii_case(b"content-length") => {
196                if self.content_length.is_some() {
197                    return Err(Error::DuplicateContentLength);
198                }
199                self.content_length = Some(parse_content_length(value)?);
200            }
201            17 if name.eq_ignore_ascii_case(b"transfer-encoding") => {
202                for c in value.split(|&b| b == b',') {
203                    let c = trim(c);
204                    if c.is_empty() || scan(c, &TCHAR) != c.len() {
205                        return Err(Error::InvalidTransferEncoding);
206                    }
207                    self.te_codings += 1;
208                    self.te_last_chunked = c.eq_ignore_ascii_case(b"chunked");
209                    self.te_chunked += u32::from(self.te_last_chunked);
210                }
211            }
212            _ => {}
213        }
214        Ok(())
215    }
216
217    /// Request framing (RFC 9112 §6.3 rules 3–7).
218    fn request_framing(&self, version: Version, lim: &Limits) -> Result<Framing, Error> {
219        if self.te_codings > 0 {
220            if self.content_length.is_some() {
221                return Err(Error::TeWithContentLength);
222            }
223            if version == Version::Http10 {
224                return Err(Error::TransferEncodingHttp10);
225            }
226            if self.te_chunked != 1 || !self.te_last_chunked {
227                return Err(Error::ChunkedNotFinal);
228            }
229            if self.te_codings > 1 {
230                return Err(Error::UnsupportedTransferCoding);
231            }
232            return Ok(Framing::Chunked);
233        }
234        match self.content_length {
235            Some(n) if n > lim.max_body => Err(Error::BodyTooLarge),
236            Some(0) | None => Ok(Framing::None),
237            Some(n) => Ok(Framing::Length(n)),
238        }
239    }
240
241    fn keep_alive(&self, version: Version) -> bool {
242        !self.close && (version == Version::Http11 || self.keep_alive)
243    }
244}
245
246/// Parses field lines from `p` up to and including the empty line. Returns the end offset and
247/// the number of fields, or `None` when more bytes are needed. `start` is where the section (or
248/// the whole head) begins, for the `max_total` limit.
249#[allow(clippy::too_many_arguments)]
250fn parse_fields<'b>(
251    buf: &'b [u8],
252    mut p: usize,
253    start: usize,
254    hs: &mut [Header<'b>],
255    store: bool,
256    lim: &Limits,
257    section: Section,
258    sem: &mut Semantics<'b>,
259) -> Result<Option<(usize, usize)>, Error> {
260    let (max_total, too_large) = match section {
261        Section::Head => (lim.max_head, Error::HeadTooLarge),
262        Section::Trailers => (lim.max_trailers, Error::TrailersTooLarge),
263    };
264    let partial = |ls: usize| -> Result<Option<(usize, usize)>, Error> {
265        if buf.len() - ls > lim.max_header_line {
266            Err(Error::HeaderLineTooLong)
267        } else if buf.len() - start > max_total {
268            Err(too_large)
269        } else {
270            Ok(None)
271        }
272    };
273    let mut count = 0;
274    loop {
275        let ls = p;
276        let Some(&first) = buf.get(p) else { return partial(ls) };
277        match first {
278            b'\r' => {
279                let Some(&lf) = buf.get(p + 1) else { return partial(ls) };
280                if lf != b'\n' {
281                    return Err(Error::BareCr);
282                }
283                p += 2;
284                if p - start > max_total {
285                    return Err(too_large);
286                }
287                return Ok(Some((p, count)));
288            }
289            b'\n' => return Err(Error::BareLf),
290            b' ' | b'\t' => return Err(Error::ObsFold),
291            _ => {}
292        }
293        // Name, colon, OWS and value in one dispatch.
294        let (n, v0, v1) = field_line(&buf[p..]);
295        let Some(&stop) = buf.get(p + n) else { return partial(ls) };
296        match stop {
297            b':' if n > 0 => {}
298            b' ' | b'\t' if n > 0 => return Err(Error::WhitespaceBeforeColon),
299            _ => return Err(Error::InvalidHeaderName),
300        }
301        let name = &buf[p..p + n];
302        let vs = p + v0;
303        p += v1;
304        let Some(&cr) = buf.get(p) else { return partial(ls) };
305        if cr != b'\r' {
306            return Err(if cr == b'\n' { Error::BareLf } else { Error::InvalidHeaderValue });
307        }
308        let Some(&lf) = buf.get(p + 1) else { return partial(ls) };
309        if lf != b'\n' {
310            return Err(Error::BareCr);
311        }
312        let mut ve = p;
313        while ve > vs && is_ws(buf[ve - 1]) {
314            ve -= 1;
315        }
316        let value = &buf[vs..ve];
317        p += 2;
318        if p - ls > lim.max_header_line {
319            return Err(Error::HeaderLineTooLong);
320        }
321        if p - start > max_total {
322            return Err(too_large);
323        }
324        if count >= lim.max_headers || (store && count >= hs.len()) {
325            return Err(Error::TooManyHeaders);
326        }
327        if store {
328            hs[count] = Header { name, value };
329        }
330        count += 1;
331        match section {
332            Section::Head => sem.field(name, value)?,
333            Section::Trailers => {
334                if name.eq_ignore_ascii_case(b"content-length")
335                    || name.eq_ignore_ascii_case(b"transfer-encoding")
336                    || name.eq_ignore_ascii_case(b"host")
337                {
338                    return Err(Error::ForbiddenTrailer);
339                }
340            }
341        }
342    }
343}
344
345/// Skips the empty lines a recipient may ignore before a start line (RFC 9112 §2.2).
346fn skip_empty_lines(buf: &[u8], lim: &Limits) -> Result<Option<usize>, Error> {
347    let mut p = 0;
348    loop {
349        match buf.get(p) {
350            None => return Ok(None),
351            Some(b'\r') => match buf.get(p + 1) {
352                None => return Ok(None),
353                Some(b'\n') => {
354                    p += 2;
355                    if p > lim.max_head {
356                        return Err(Error::HeadTooLarge);
357                    }
358                }
359                Some(_) => return Err(Error::BareCr),
360            },
361            Some(b'\n') => return Err(Error::BareLf),
362            Some(_) => return Ok(Some(p)),
363        }
364    }
365}
366
367/// Checks that the bytes present at offsets 5..8 of a possibly short `HTTP/x.y` are digit, dot,
368/// digit.
369fn version_shape(v: &[u8]) -> Result<(), Error> {
370    let ok = v.iter().enumerate().take(8).skip(5).all(|(i, &b)| if i == 6 { b == b'.' } else { b.is_ascii_digit() });
371    if ok {
372        Ok(())
373    } else {
374        Err(Error::InvalidVersion)
375    }
376}
377
378/// Parses the `HTTP/x.y` at `p` and the CRLF after it; returns the version and the offset after
379/// the CRLF, or `None` if more bytes are needed.
380fn parse_version_crlf(buf: &[u8], p: usize) -> Result<Option<(Version, usize)>, Error> {
381    let rest = &buf[p..];
382    let want = b"HTTP/";
383    let k = rest.len().min(5);
384    if rest[..k] != want[..k] {
385        return Err(Error::InvalidVersion);
386    }
387    if rest.len() < 10 {
388        // Judge the bytes that are here, so a short version line ending in CRLF CRLF is an
389        // error now rather than a wait for bytes that change nothing.
390        version_shape(rest)?;
391        if rest.len() == 9 && rest[8] != b'\r' {
392            return Err(if rest[8] == b'\n' { Error::BareLf } else { Error::InvalidVersion });
393        }
394        return Ok(None);
395    }
396    let version = match &rest[..8] {
397        b"HTTP/1.1" => Version::Http11,
398        b"HTTP/1.0" => Version::Http10,
399        [.., a, b'.', b] if a.is_ascii_digit() && b.is_ascii_digit() => return Err(Error::VersionNotSupported),
400        _ => return Err(Error::InvalidVersion),
401    };
402    match &rest[8..10] {
403        b"\r\n" => Ok(Some((version, p + 10))),
404        [b'\r', _] => Err(Error::BareCr),
405        [b'\n', _] => Err(Error::BareLf),
406        _ => Err(Error::InvalidVersion),
407    }
408}
409
410/// Parses a whole request head from the start of `buf` into `hs`.
411///
412/// Returns `Partial` until the head is complete; limits are enforced on partial input too. The
413/// parse never allocates. For input that arrives in pieces, [`RequestParser`] avoids parsing
414/// the same bytes again until a head can be complete.
415pub fn parse_request<'b, 'h>(
416    buf: &'b [u8],
417    hs: &'h mut [Header<'b>],
418    lim: &Limits,
419) -> Result<Status<Request<'b, 'h>>, Error> {
420    let Some(ls) = skip_empty_lines(buf, lim)? else { return Ok(Status::Partial) };
421    let line_partial = || -> Result<Status<Request<'b, 'h>>, Error> {
422        if buf.len() - ls > lim.max_request_line {
423            Err(Error::RequestLineTooLong)
424        } else if buf.len() > lim.max_head {
425            Err(Error::HeadTooLarge)
426        } else {
427            Ok(Status::Partial)
428        }
429    };
430    // method SP
431    let mut p = ls;
432    let n = scan(&buf[p..], &TCHAR);
433    let Some(&sp) = buf.get(p + n) else { return line_partial() };
434    if n == 0 || sp != b' ' {
435        return Err(Error::InvalidMethod);
436    }
437    let method = &buf[p..p + n];
438    p += n + 1;
439    // request-target SP
440    let t0 = p;
441    let mut brackets = false;
442    loop {
443        p += scan(&buf[p..], &URI);
444        let Some(&b) = buf.get(p) else { return line_partial() };
445        match b {
446            b' ' => break,
447            b'%' => {
448                if p + 2 >= buf.len() {
449                    if buf[p + 1..].iter().all(|&c| hex(c)) {
450                        return line_partial();
451                    }
452                    return Err(Error::InvalidTarget);
453                }
454                if !hex(buf[p + 1]) || !hex(buf[p + 2]) {
455                    return Err(Error::InvalidTarget);
456                }
457                p += 3;
458            }
459            b'[' | b']' => {
460                brackets = true;
461                p += 1;
462            }
463            _ => return Err(Error::InvalidTarget),
464        }
465    }
466    let target = &buf[t0..p];
467    if target.is_empty() {
468        return Err(Error::InvalidTarget);
469    }
470    p += 1;
471    let Some((version, after)) = parse_version_crlf(buf, p)? else { return line_partial() };
472    if after - ls > lim.max_request_line {
473        return Err(Error::RequestLineTooLong);
474    }
475    let connect = method == b"CONNECT";
476    let form = if target[0] == b'/' && !connect {
477        if brackets {
478            return Err(Error::InvalidTarget);
479        }
480        TargetForm::Origin
481    } else if target == b"*" && method == b"OPTIONS" {
482        TargetForm::Asterisk
483    } else if connect {
484        if !valid_host(target, true) {
485            return Err(Error::InvalidTarget);
486        }
487        TargetForm::Authority
488    } else if valid_absolute(target, brackets) {
489        TargetForm::Absolute
490    } else {
491        return Err(Error::InvalidTarget);
492    };
493    let mut sem = Semantics::default();
494    let Some((end, count)) = parse_fields(buf, after, 0, hs, true, lim, Section::Head, &mut sem)? else {
495        return Ok(Status::Partial);
496    };
497    if version == Version::Http11 && sem.hosts == 0 {
498        return Err(Error::MissingHost);
499    }
500    let framing = sem.request_framing(version, lim)?;
501    let http11 = version == Version::Http11;
502    if sem.expect_other && http11 {
503        return Err(Error::ExpectationFailed);
504    }
505    Ok(Status::Complete(Request {
506        method,
507        target,
508        form,
509        version,
510        headers: &hs[..count],
511        framing,
512        keep_alive: sem.keep_alive(version),
513        expect_continue: sem.expect_continue && http11,
514        upgrade: if sem.conn_upgrade && http11 { sem.upgrade } else { None },
515        host: sem.host,
516        head_len: end,
517    }))
518}
519
520/// Incremental request-head parsing for one connection: remembers how far the input was
521/// scanned, so a head that trickles in is fully parsed only once the empty line (or a limit)
522/// arrives. Eight bytes of state; reset automatically after each complete head or error.
523#[derive(Clone, Copy, Debug, Default)]
524pub struct RequestParser {
525    scanned: u32,
526    line_start: u32,
527}
528
529impl RequestParser {
530    /// A parser at the start of a head.
531    pub fn new() -> Self {
532        Self::default()
533    }
534
535    /// Parses `buf`, which holds everything received since the head began (the previous call's
536    /// bytes plus new ones).
537    pub fn parse<'b, 'h>(
538        &mut self,
539        buf: &'b [u8],
540        hs: &'h mut [Header<'b>],
541        lim: &Limits,
542    ) -> Result<Status<Request<'b, 'h>>, Error> {
543        let scanned = self.scanned as usize;
544        if scanned > 0 && buf.len() >= scanned {
545            let mut i = scanned.saturating_sub(3);
546            let mut line_start = self.line_start as usize;
547            let mut complete = false;
548            while let Some(k) = find_byte(&buf[i..], b'\n') {
549                let at = i + k;
550                if at >= 3 && &buf[at - 3..=at] == b"\r\n\r\n" {
551                    complete = true;
552                    break;
553                }
554                line_start = at + 1;
555                i = at + 1;
556            }
557            let line_limit = if line_start == 0 { lim.max_request_line } else { lim.max_header_line };
558            if !complete && buf.len() - line_start <= line_limit && buf.len() <= lim.max_head {
559                self.scanned = buf.len() as u32;
560                self.line_start = line_start as u32;
561                return Ok(Status::Partial);
562            }
563        }
564        let r = parse_request(buf, hs, lim);
565        match r {
566            Ok(Status::Partial) => {
567                self.scanned = buf.len().max(1) as u32;
568                self.line_start = buf.iter().rposition(|&b| b == b'\n').map_or(0, |i| i + 1) as u32;
569            }
570            _ => *self = Self::default(),
571        }
572        r
573    }
574
575    /// Forgets any partial head.
576    pub fn reset(&mut self) {
577        *self = Self::default();
578    }
579}
580
581/// Parses a response head. `request_method` decides the framing of HEAD and CONNECT replies.
582/// A 1xx head is returned like any other; read the next head after it.
583pub fn parse_response<'b, 'h>(
584    buf: &'b [u8],
585    hs: &'h mut [Header<'b>],
586    lim: &Limits,
587    request_method: &[u8],
588) -> Result<Status<Response<'b, 'h>>, Error> {
589    let line_partial = || -> Result<Status<Response<'b, 'h>>, Error> {
590        if buf.len() > lim.max_request_line {
591            Err(Error::RequestLineTooLong)
592        } else {
593            Ok(Status::Partial)
594        }
595    };
596    let k = buf.len().min(5);
597    if buf[..k] != b"HTTP/"[..k] {
598        return Err(Error::InvalidVersion);
599    }
600    if buf.len() < 13 {
601        version_shape(buf)?;
602        if buf.len() > 8 && buf[8] != b' ' {
603            return Err(Error::InvalidVersion);
604        }
605        if buf.iter().skip(9).any(|b| !b.is_ascii_digit()) {
606            return Err(Error::InvalidStatus);
607        }
608        return line_partial();
609    }
610    let version = match &buf[..8] {
611        b"HTTP/1.1" => Version::Http11,
612        b"HTTP/1.0" => Version::Http10,
613        [.., a, b'.', b] if a.is_ascii_digit() && b.is_ascii_digit() => return Err(Error::VersionNotSupported),
614        _ => return Err(Error::InvalidVersion),
615    };
616    if buf[8] != b' ' {
617        return Err(Error::InvalidVersion);
618    }
619    let code = &buf[9..12];
620    if !code.iter().all(u8::is_ascii_digit) || buf[12] != b' ' {
621        return Err(Error::InvalidStatus);
622    }
623    let status = code.iter().fold(0u16, |n, &d| n * 10 + u16::from(d - b'0'));
624    if !(100..=599).contains(&status) {
625        return Err(Error::InvalidStatus);
626    }
627    let rs = 13;
628    let mut p = rs + scan(&buf[rs..], &FIELD_VALUE);
629    let Some(&cr) = buf.get(p) else { return line_partial() };
630    if cr != b'\r' {
631        return Err(if cr == b'\n' { Error::BareLf } else { Error::InvalidReason });
632    }
633    let Some(&lf) = buf.get(p + 1) else { return line_partial() };
634    if lf != b'\n' {
635        return Err(Error::BareCr);
636    }
637    let reason = &buf[rs..p];
638    p += 2;
639    if p > lim.max_request_line {
640        return Err(Error::RequestLineTooLong);
641    }
642    let mut sem = Semantics::default();
643    let Some((end, count)) = parse_fields(buf, p, 0, hs, true, lim, Section::Head, &mut sem)? else {
644        return Ok(Status::Partial);
645    };
646    let ok_connect = request_method == b"CONNECT" && (200..300).contains(&status);
647    let upgraded = status == 101 || ok_connect;
648    let framing = if request_method == b"HEAD" || status < 200 || status == 204 || status == 304 || ok_connect {
649        Framing::None
650    } else if sem.te_codings > 0 {
651        if sem.content_length.is_some() {
652            return Err(Error::TeWithContentLength);
653        }
654        if sem.te_chunked > 1 {
655            return Err(Error::ChunkedNotFinal);
656        }
657        match (sem.te_last_chunked, sem.te_codings) {
658            (true, 1) => Framing::Chunked,
659            (true, _) => return Err(Error::UnsupportedTransferCoding),
660            (false, _) => Framing::Close,
661        }
662    } else {
663        match sem.content_length {
664            Some(0) => Framing::None,
665            Some(n) => Framing::Length(n),
666            None => Framing::Close,
667        }
668    };
669    Ok(Status::Complete(Response {
670        version,
671        status,
672        reason,
673        headers: &hs[..count],
674        framing,
675        keep_alive: framing != Framing::Close && sem.keep_alive(version),
676        upgraded,
677        head_len: end,
678    }))
679}
680
681/// Parses a trailer section as returned by [`BodyEvent::End`](crate::BodyEvent::End): field
682/// lines followed by the empty line, or nothing.
683pub fn parse_trailers<'b, 'h>(
684    section: &'b [u8],
685    hs: &'h mut [Header<'b>],
686    lim: &Limits,
687) -> Result<&'h [Header<'b>], Error> {
688    if section.is_empty() {
689        return Ok(&hs[..0]);
690    }
691    let mut sem = Semantics::default();
692    match parse_fields(section, 0, 0, hs, true, lim, Section::Trailers, &mut sem)? {
693        Some((end, count)) if end == section.len() => Ok(&hs[..count]),
694        _ => Err(Error::InvalidHeaderValue),
695    }
696}
697
698/// Validates a complete trailer section (ending with the empty line) without storing it.
699pub(crate) fn check_trailers(section: &[u8], lim: &Limits) -> Result<(), Error> {
700    let mut sem = Semantics::default();
701    match parse_fields(section, 0, 0, &mut [], false, lim, Section::Trailers, &mut sem)? {
702        Some((end, _)) if end == section.len() => Ok(()),
703        _ => Err(Error::InvalidHeaderValue),
704    }
705}