Skip to main content

base_http1/
input.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::{Error, Options, wire};
5use base_endpoint::stream::ReadableStream;
6use base_http::{
7    Fields, Method, Request, RequestMetadata, Version,
8    body::{BodyFrame, ReadableBody},
9};
10use base_scheduler::Cancellation;
11use std::{
12    io,
13    sync::{
14        Arc,
15        atomic::{AtomicBool, Ordering},
16    },
17};
18
19#[derive(Default)]
20struct ExchangeState {
21    upgrade: Option<Box<[u8]>>,
22    trailer_options: Vec<Vec<u8>>,
23    complete: AtomicBool,
24    tunnel: AtomicBool,
25    abandoned: AtomicBool,
26}
27
28#[derive(Clone, Copy)]
29struct ExchangeFacts {
30    version: Version,
31    head: bool,
32    connect: bool,
33    persistent: bool,
34    expect_continue: bool,
35}
36
37/// Captured framing facts and, when input can still change, shared state. A successful switch changes incoming body reads to raw data without releasing transport.
38#[derive(Clone)]
39pub struct Exchange {
40    facts: ExchangeFacts,
41    state: Option<Arc<ExchangeState>>,
42}
43
44pub(crate) fn request_persistent(head: &RequestMetadata) -> bool {
45    head.persistent != Some(false)
46        && !wire::has_token(&head.fields, "connection", b"close")
47        && (head.version.unwrap_or(Version::Http11) == Version::Http11
48            || head.persistent == Some(true)
49            || wire::has_token(&head.fields, "connection", b"keep-alive"))
50}
51
52impl Exchange {
53    pub(crate) fn new(head: &RequestMetadata, trailer_options: Vec<Vec<u8>>) -> Self {
54        // Retain only protocol decisions. Ordinary fields and target strings belong solely to the application request; mutations cannot change already established framing. Trailer exclusions retain only the bytes needed for later protocol processing. The client selects upgrades from its own request, so its exchange retains no upgrade offers.
55        Self {
56            facts: ExchangeFacts {
57                version: head.version.unwrap_or(Version::Http11),
58                head: head.method == Method::HEAD,
59                connect: head.method == Method::CONNECT,
60                persistent: request_persistent(head),
61                expect_continue: wire::has_token(&head.fields, "expect", b"100-continue"),
62            },
63            // Client response completion depends on incoming response framing,
64            // independently of the matching outgoing request's body length.
65            state: Some(Arc::new(ExchangeState {
66                trailer_options,
67                ..Default::default()
68            })),
69        }
70    }
71    fn from_request(head: &RequestMetadata, framing: wire::RequestFraming) -> Self {
72        let facts = ExchangeFacts {
73            version: head.version.unwrap_or(Version::Http11),
74            head: head.method == Method::HEAD,
75            connect: head.method == Method::CONNECT,
76            persistent: framing.persistent,
77            expect_continue: framing.expect_continue,
78        };
79        // Completed ordinary server input cannot become active or abandoned.
80        // CONNECT and offered upgrades can become raw even when initially empty.
81        let mutable = facts.connect
82            || framing.upgrade.is_some()
83            || framing.chunked
84            || framing.length.is_some_and(|n| n != 0);
85        Self {
86            facts,
87            state: mutable.then(|| {
88                Arc::new(ExchangeState {
89                    upgrade: framing.upgrade,
90                    trailer_options: framing.trailer_options,
91                    ..Default::default()
92                })
93            }),
94        }
95    }
96
97    fn filter_trailers(&self, fields: &mut Fields) {
98        let state = self
99            .state
100            .as_ref()
101            .expect("chunked input retains shared state");
102        fields.retain(|field| {
103            !state
104                .trailer_options
105                .iter()
106                .any(|option| field.name().as_bytes().eq_ignore_ascii_case(option))
107        });
108    }
109
110    pub fn version(&self) -> Version {
111        self.facts.version
112    }
113    pub fn is_head(&self) -> bool {
114        self.facts.head
115    }
116    pub fn is_connect(&self) -> bool {
117        self.facts.connect
118    }
119    pub fn expects_continue(&self) -> bool {
120        self.facts.expect_continue
121    }
122    pub(crate) fn has_upgrade(&self) -> bool {
123        self.state
124            .as_ref()
125            .is_some_and(|state| state.upgrade.is_some())
126    }
127    pub(crate) fn accepts_upgrade(&self, protocol: &[u8]) -> bool {
128        self.state
129            .as_ref()
130            .and_then(|state| state.upgrade.as_ref())
131            .is_some_and(|offers| {
132                offers
133                    .split(|byte| *byte == b',')
134                    .any(|offer| wire::trim(offer).eq_ignore_ascii_case(protocol))
135            })
136    }
137    pub fn is_complete(&self) -> bool {
138        self.state
139            .as_ref()
140            .is_none_or(|state| state.complete.load(Ordering::Acquire))
141    }
142    pub fn is_tunnel(&self) -> bool {
143        self.state
144            .as_ref()
145            .is_some_and(|state| state.tunnel.load(Ordering::Acquire))
146    }
147    /// Whether the application discarded input before reaching its boundary.
148    pub fn is_abandoned(&self) -> bool {
149        self.state
150            .as_ref()
151            .is_some_and(|state| state.abandoned.load(Ordering::Acquire))
152    }
153    pub fn is_persistent(&self) -> bool {
154        self.facts.persistent
155    }
156    pub(crate) fn switch(&self) {
157        self.state
158            .as_ref()
159            .expect("switches retain shared state")
160            .tunnel
161            .store(true, Ordering::Release);
162    }
163    fn set_complete(&self, complete: bool) {
164        if let Some(state) = &self.state {
165            state.complete.store(complete, Ordering::Release);
166        }
167    }
168    fn abandon(&self) {
169        // Input without shared state is complete from the start, so it is never abandoned.
170        self.state
171            .as_ref()
172            .expect("incomplete input retains shared state")
173            .abandoned
174            .store(true, Ordering::Release);
175    }
176}
177
178/// Sequential request admission. Input read-ahead survives body completion and
179/// protocol switches. Dropped/failed partial reads prohibit further admission.
180pub struct RequestReader<R> {
181    reader: R,
182    buffer: Vec<u8>,
183    start: usize,
184    end: usize,
185    pub(crate) options: Options,
186    pub(crate) poisoned: bool,
187    pub(crate) previous: Option<Exchange>,
188}
189
190impl<R: ReadableStream> RequestReader<R> {
191    pub fn new(reader: R, options: Options) -> Result<Self, Error> {
192        options.validate()?;
193        Ok(Self {
194            reader,
195            buffer: vec![0; options.read_size],
196            start: 0,
197            end: 0,
198            options,
199            poisoned: false,
200            previous: None,
201        })
202    }
203
204    pub fn is_reusable(&self) -> bool {
205        !self.poisoned
206            && self
207                .previous
208                .as_ref()
209                .is_none_or(|x| x.is_complete() && !x.is_tunnel() && !x.is_abandoned())
210    }
211
212    fn available(&self) -> &[u8] {
213        &self.buffer[self.start..self.end]
214    }
215
216    async fn fill(&mut self, cancellation: &Cancellation) -> Result<bool, Error> {
217        if self.start != 0 {
218            self.buffer.copy_within(self.start..self.end, 0);
219            self.end -= self.start;
220            self.start = 0;
221        }
222        // Neither the buffer nor its initial `read_size` allocation can exceed `isize::MAX`, so this cannot overflow.
223        let limit = self.end + self.options.read_size;
224        if self.buffer.len() < limit {
225            // Keep initialized spare capacity across reads. A pending/cancelled
226            // read never publishes it: only a successful count advances end.
227            self.buffer.resize(limit, 0);
228        }
229        let n = self
230            .reader
231            .read_into(&mut self.buffer[self.end..limit], cancellation)
232            .await?;
233        self.end += n;
234        Ok(n != 0)
235    }
236
237    fn take(&mut self, n: usize) -> Vec<u8> {
238        let data = self.available()[..n].to_vec();
239        self.start += n;
240        data
241    }
242
243    /// Parse a response head or trailer section with `parse`, which returns
244    /// the section's length once it is complete. As for requests, the first
245    /// read is parsed directly, and an incomplete section is parsed again only
246    /// once its end arrives. `Ok(None)` means the input ended cleanly first.
247    pub(crate) async fn read_section<T>(
248        &mut self,
249        cancellation: &Cancellation,
250        limit: &'static str,
251        parse: impl Fn(&[u8]) -> Result<Option<(T, usize)>, Error>,
252    ) -> Result<Option<T>, Error> {
253        let max_head_bytes = self.options.max_head_bytes;
254        let mut scanned = None;
255        loop {
256            let available = self.available();
257            let window = &available[..available.len().min(max_head_bytes)];
258            // A trailer section can be only the empty line.
259            let terminated = window.starts_with(b"\r\n")
260                || scanned.is_none_or(|scanned: usize| {
261                    window[scanned..].windows(4).any(|w| w == b"\r\n\r\n")
262                });
263            if terminated && !window.is_empty() {
264                if let Some((section, n)) = parse(window)? {
265                    self.start += n;
266                    return Ok(Some(section));
267                }
268            }
269            if available.len() >= max_head_bytes {
270                return Err(Error::Limit(limit));
271            }
272            if !available.is_empty() {
273                scanned = Some(available.len().saturating_sub(3));
274            }
275            if !self.fill(cancellation).await? {
276                if self.available().is_empty() {
277                    return Ok(None);
278                }
279                return Err(Error::Truncated);
280            }
281        }
282    }
283
284    /// Clean EOF between requests returns `None`; partial heads are errors.
285    pub async fn read_request(
286        &mut self,
287        cancellation: &Cancellation,
288    ) -> Result<Option<(Request<IncomingBody<'_, R>>, Exchange)>, Error> {
289        cancellation.check()?;
290        if !self.is_reusable() {
291            return Err(Error::Invalid("request input is not reusable"));
292        }
293        // Parse the first input available, which is usually a complete head.
294        // A head that is still incomplete is parsed again only once its
295        // terminator arrives, so fragmented input costs linear work. The loop stays in this function so the parsed metadata
296        // is not moved through another future's output.
297        self.poisoned = true;
298        let max_head_bytes = self.options.max_head_bytes;
299        let mut scanned = None;
300        let (head, facts) = loop {
301            let available = self.available();
302            let window = &available[..available.len().min(max_head_bytes)];
303            let terminated = scanned.is_none_or(|scanned: usize| {
304                window[scanned..].windows(4).any(|w| w == b"\r\n\r\n")
305            });
306            if terminated && !window.is_empty() {
307                if let Some((head, facts, n)) = wire::request(window, self.options.max_fields)? {
308                    self.start += n;
309                    break (head, facts);
310                }
311            }
312            if available.len() >= max_head_bytes {
313                return Err(Error::Limit("message head"));
314            }
315            if !available.is_empty() {
316                scanned = Some(available.len().saturating_sub(3));
317            }
318            if !self.fill(cancellation).await? {
319                if self.available().is_empty() {
320                    self.poisoned = false;
321                    return Ok(None);
322                }
323                return Err(Error::Truncated);
324            }
325        };
326        let length = facts.length;
327        let chunked = facts.chunked;
328        if chunked && head.version != Some(Version::Http11) {
329            return Err(Error::Invalid("HTTP/1.0 transfer-encoding"));
330        }
331        let framing = if chunked {
332            Framing::Chunked {
333                remaining: 0,
334                delimiter: false,
335            }
336        } else {
337            Framing::Fixed(length.unwrap_or(0))
338        };
339        let exchange = Exchange::from_request(&head, facts);
340        self.previous = Some(exchange.clone());
341        self.poisoned = false;
342        let body = IncomingBody::new(
343            self,
344            exchange.clone(),
345            framing,
346            if chunked {
347                None
348            } else {
349                Some(length.unwrap_or(0))
350            },
351            false,
352            false,
353        );
354        Ok(Some((Request::from_parts(head, body), exchange)))
355    }
356
357    /// Establish backend retirement even after cancellation or parse failure.
358    pub async fn close(&mut self) -> io::Result<()> {
359        self.poisoned = true;
360        self.reader.close().await
361    }
362
363    async fn line(&mut self, limit: usize, cancellation: &Cancellation) -> Result<Vec<u8>, Error> {
364        loop {
365            if let Some(end) = self.available().windows(2).position(|w| w == b"\r\n") {
366                if end + 2 > limit {
367                    return Err(Error::Limit("line length"));
368                }
369                return Ok(self.take(end + 2));
370            }
371            if self.available().len() >= limit {
372                return Err(Error::Limit("line length"));
373            }
374            if !self.fill(cancellation).await? {
375                return Err(Error::Truncated);
376            }
377        }
378    }
379
380    async fn trailers(&mut self, cancellation: &Cancellation) -> Result<Fields, Error> {
381        let max_fields = self.options.max_fields;
382        let fields = self
383            .read_section(cancellation, "trailer section", |bytes| {
384                wire::trailers(bytes, max_fields)
385            })
386            .await?
387            .ok_or(Error::Truncated)?;
388        crate::output::validate_trailer_fields(fields.pairs())?;
389        Ok(fields)
390    }
391}
392
393pub(crate) enum Framing {
394    Fixed(u64),
395    Chunked { remaining: u64, delimiter: bool },
396}
397
398/// A pull body borrowing the connection's input lane. Returning it as the
399/// response body provides streaming echo and managed duplex tunnels without
400/// boxing, queueing, spawning, or transferring socket ownership.
401pub struct IncomingBody<'a, R> {
402    input: &'a mut RequestReader<R>,
403    exchange: Exchange,
404    framing: Framing,
405    length: Option<u64>,
406    complete: bool,
407    closed: bool,
408    raw: bool,
409    forbid_content: bool,
410}
411
412impl<'a, R: ReadableStream> IncomingBody<'a, R> {
413    pub(crate) fn new(
414        input: &'a mut RequestReader<R>,
415        exchange: Exchange,
416        framing: Framing,
417        length: Option<u64>,
418        raw: bool,
419        forbid_content: bool,
420    ) -> Self {
421        let complete = !raw && matches!(framing, Framing::Fixed(0));
422        exchange.set_complete(complete);
423        Self {
424            input,
425            exchange,
426            framing,
427            length,
428            complete,
429            closed: false,
430            raw,
431            forbid_content,
432        }
433    }
434    async fn read_inner(
435        &mut self,
436        cancellation: &Cancellation,
437    ) -> Result<BodyFrame<Vec<u8>>, Error> {
438        if self.exchange.is_tunnel() && !self.raw {
439            self.raw = true;
440            self.complete = false;
441        }
442        if self.complete || self.closed {
443            return Ok(BodyFrame::End);
444        }
445        loop {
446            if self.raw {
447                if self.input.available().is_empty() && !self.input.fill(cancellation).await? {
448                    self.complete = true;
449                    return Ok(BodyFrame::End);
450                }
451                return Ok(BodyFrame::Data {
452                    data: self.input.take(self.input.available().len()),
453                    end: false,
454                });
455            }
456            match &mut self.framing {
457                Framing::Fixed(remaining) => {
458                    if self.input.available().is_empty() && !self.input.fill(cancellation).await? {
459                        return Err(Error::Truncated);
460                    }
461                    let n = (*remaining).min(self.input.available().len() as u64) as usize;
462                    *remaining -= n as u64;
463                    let data = self.input.take(n);
464                    if *remaining == 0 {
465                        self.complete = true;
466                    }
467                    return Ok(BodyFrame::Data {
468                        data,
469                        end: self.complete,
470                    });
471                }
472                Framing::Chunked {
473                    remaining,
474                    delimiter,
475                } => {
476                    if *remaining != 0 {
477                        if self.input.available().is_empty()
478                            && !self.input.fill(cancellation).await?
479                        {
480                            return Err(Error::Truncated);
481                        }
482                        let n = (*remaining).min(self.input.available().len() as u64) as usize;
483                        *remaining -= n as u64;
484                        return Ok(BodyFrame::Data {
485                            data: self.input.take(n),
486                            end: false,
487                        });
488                    }
489                    if *delimiter {
490                        while self.input.available().len() < 2 {
491                            if !self.input.fill(cancellation).await? {
492                                return Err(Error::Truncated);
493                            }
494                        }
495                        if !self.input.available().starts_with(b"\r\n") {
496                            return Err(Error::Invalid("missing chunk delimiter"));
497                        }
498                        self.input.start += 2;
499                    }
500                    let line = self
501                        .input
502                        .line(self.input.options.max_chunk_line_bytes, cancellation)
503                        .await?;
504                    let size = chunk_size(&line)?;
505                    if size == 0 {
506                        let mut fields = self.input.trailers(cancellation).await?;
507                        self.exchange.filter_trailers(&mut fields);
508                        self.complete = true;
509                        return Ok(BodyFrame::Headers { fields, end: true });
510                    }
511                    *remaining = size;
512                    *delimiter = true;
513                }
514            }
515        }
516    }
517}
518
519impl<R: ReadableStream> ReadableBody for IncomingBody<'_, R> {
520    type Data = Vec<u8>;
521    type Error = Error;
522    fn may_have_trailers(&self) -> bool {
523        !self.raw && !self.exchange.is_tunnel() && matches!(self.framing, Framing::Chunked { .. })
524    }
525    fn is_ready(&self) -> bool {
526        !self.input.available().is_empty() || self.complete || self.closed
527    }
528    fn is_empty(&self) -> bool {
529        (self.complete && !self.exchange.is_tunnel()) || self.closed
530    }
531    fn length(&self) -> Option<u64> {
532        if self.exchange.is_tunnel() {
533            None
534        } else {
535            self.length
536        }
537    }
538    fn close(&mut self, _: Option<Error>) {
539        if !self.complete {
540            self.exchange.abandon();
541        }
542        self.closed = true;
543    }
544    async fn read(&mut self, cancellation: &Cancellation) -> Result<BodyFrame<Vec<u8>>, Error> {
545        cancellation.check()?;
546        if self.input.poisoned {
547            return Err(Error::Invalid("previous body read failed or was abandoned"));
548        }
549        self.input.poisoned = true;
550        let result = self.read_inner(cancellation).await.and_then(|chunk| {
551            if self.forbid_content
552                && matches!(&chunk, BodyFrame::Data { data, .. } if !data.is_empty())
553            {
554                Err(Error::Invalid("content on 205"))
555            } else {
556                Ok(chunk)
557            }
558        });
559        if result.is_ok() {
560            self.input.poisoned = false;
561            self.exchange.set_complete(self.complete);
562        }
563        result
564    }
565}
566
567impl<R> Drop for IncomingBody<'_, R> {
568    fn drop(&mut self) {
569        if !self.complete {
570            self.exchange.abandon();
571        }
572    }
573}
574
575// Validate extension grammar rather than trusting ignored bytes after ';'.
576// A line ends at its first CRLF, and this grammar rejects any other CR or LF.
577fn chunk_size(line: &[u8]) -> Result<u64, Error> {
578    let bytes = &line[..line.len() - 2];
579    let end = bytes
580        .iter()
581        .position(|b| !b.is_ascii_hexdigit())
582        .unwrap_or(bytes.len());
583    if end == 0 {
584        return Err(Error::Invalid("invalid chunk size"));
585    }
586    let size = u64::from_str_radix(std::str::from_utf8(&bytes[..end]).unwrap(), 16)
587        .map_err(|_| Error::Invalid("chunk size overflow"))?;
588    let mut rest = &bytes[end..];
589    while !rest.is_empty() {
590        rest = wire::trim(rest);
591        if rest.first() != Some(&b';') {
592            return Err(Error::Invalid("invalid chunk extension"));
593        }
594        rest = &rest[1..];
595        rest = wire::trim(rest);
596        let end = rest
597            .iter()
598            .position(|b| !wire::token(&[*b]))
599            .unwrap_or(rest.len());
600        if end == 0 {
601            return Err(Error::Invalid("missing chunk extension name"));
602        }
603        rest = wire::trim(&rest[end..]);
604        if rest.first() == Some(&b'=') {
605            rest = wire::trim(&rest[1..]);
606            if rest.first() == Some(&b'"') {
607                rest = &rest[1..];
608                loop {
609                    let b = *rest
610                        .first()
611                        .ok_or(Error::Invalid("unterminated chunk extension"))?;
612                    rest = &rest[1..];
613                    if b == b'"' {
614                        break;
615                    }
616                    if b == b'\\' {
617                        let b = *rest
618                            .first()
619                            .ok_or(Error::Invalid("invalid quoted escape"))?;
620                        rest = &rest[1..];
621                        if b < 32 && b != b'\t' || b == 127 {
622                            return Err(Error::Invalid("invalid quoted escape"));
623                        }
624                    } else if b < 32 && b != b'\t' || b == 127 {
625                        return Err(Error::Invalid("invalid chunk extension"));
626                    }
627                }
628            } else {
629                let end = rest
630                    .iter()
631                    .position(|b| !wire::token(&[*b]))
632                    .unwrap_or(rest.len());
633                if end == 0 {
634                    return Err(Error::Invalid("missing chunk extension value"));
635                }
636                rest = &rest[end..];
637            }
638        }
639    }
640    Ok(size)
641}
642
643#[cfg(test)]
644mod tests {
645    use super::chunk_size;
646
647    #[test]
648    fn chunk_lines_reject_stray_line_controls() {
649        assert_eq!(chunk_size(b"1a;name=\"v\\\"\";flag\r\n").unwrap(), 26);
650        for line in [
651            &b"1\r;x\r\n"[..],
652            b"1\n\r\n",
653            b"1;x\r\r\n",
654            b"1;x=\r\r\n",
655            b"1;x=\"\r\"\r\n",
656            b"1;x=\"\\\n\"\r\n",
657            b"1;x\0\r\n",
658        ] {
659            assert!(chunk_size(line).is_err(), "{line:?}");
660        }
661    }
662
663    #[test]
664    fn chunk_lines_accept_token_extension_values_and_reject_malformed_ones() {
665        assert_eq!(chunk_size(b"ff ; name = value ; flag\r\n").unwrap(), 255);
666        for (line, reason) in [
667            (&b"11111111111111111\r\n"[..], "chunk size overflow"),
668            (b"1;x=\"open\r\n", "unterminated chunk extension"),
669            (b"1;x=\"\\\r\n", "invalid quoted escape"),
670        ] {
671            assert!(
672                matches!(chunk_size(line), Err(crate::Error::Invalid(r)) if r == reason),
673                "{line:?}"
674            );
675        }
676    }
677}