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. Failed reads and dropped head reads prohibit further
180/// admission; a dropped or cancelled body read loses no input.
181pub struct RequestReader<R> {
182    reader: R,
183    buffer: Vec<u8>,
184    start: usize,
185    end: usize,
186    pub(crate) options: Options,
187    pub(crate) poisoned: bool,
188    pub(crate) previous: Option<Exchange>,
189}
190
191impl<R: ReadableStream> RequestReader<R> {
192    pub fn new(reader: R, options: Options) -> Result<Self, Error> {
193        options.validate()?;
194        Ok(Self {
195            reader,
196            buffer: vec![0; options.read_size],
197            start: 0,
198            end: 0,
199            options,
200            poisoned: false,
201            previous: None,
202        })
203    }
204
205    pub fn is_reusable(&self) -> bool {
206        !self.poisoned
207            && self
208                .previous
209                .as_ref()
210                .is_none_or(|x| x.is_complete() && !x.is_tunnel() && !x.is_abandoned())
211    }
212
213    fn available(&self) -> &[u8] {
214        &self.buffer[self.start..self.end]
215    }
216
217    async fn fill(&mut self, cancellation: &Cancellation) -> Result<bool, Error> {
218        if self.start != 0 {
219            self.buffer.copy_within(self.start..self.end, 0);
220            self.end -= self.start;
221            self.start = 0;
222        }
223        // Neither the buffer nor its initial `read_size` allocation can exceed `isize::MAX`, so this cannot overflow.
224        let limit = self.end + self.options.read_size;
225        if self.buffer.len() < limit {
226            // Keep initialized spare capacity across reads. A pending/cancelled
227            // read never publishes it: only a successful count advances end.
228            self.buffer.resize(limit, 0);
229        }
230        let n = self
231            .reader
232            .read_into(&mut self.buffer[self.end..limit], cancellation)
233            .await?;
234        self.end += n;
235        Ok(n != 0)
236    }
237
238    fn take(&mut self, n: usize) -> Vec<u8> {
239        let data = self.available()[..n].to_vec();
240        self.start += n;
241        data
242    }
243
244    /// Parse a response head or trailer section with `parse`, which returns
245    /// the section's length once it is complete. As for requests, the first
246    /// read is parsed directly, and an incomplete section is parsed again only
247    /// once its end arrives. `Ok(None)` means the input ended cleanly first.
248    pub(crate) async fn read_section<T>(
249        &mut self,
250        cancellation: &Cancellation,
251        limit: &'static str,
252        parse: impl Fn(&[u8]) -> Result<Option<(T, usize)>, Error>,
253    ) -> Result<Option<T>, Error> {
254        let max_head_bytes = self.options.max_head_bytes;
255        let mut scanned = None;
256        loop {
257            let available = self.available();
258            let window = &available[..available.len().min(max_head_bytes)];
259            // A trailer section can be only the empty line.
260            let terminated = window.starts_with(b"\r\n")
261                || scanned.is_none_or(|scanned: usize| {
262                    window[scanned..].windows(4).any(|w| w == b"\r\n\r\n")
263                });
264            if terminated && !window.is_empty() {
265                if let Some((section, n)) = parse(window)? {
266                    self.start += n;
267                    return Ok(Some(section));
268                }
269            }
270            if available.len() >= max_head_bytes {
271                return Err(Error::Limit(limit));
272            }
273            if !available.is_empty() {
274                scanned = Some(available.len().saturating_sub(3));
275            }
276            if !self.fill(cancellation).await? {
277                if self.available().is_empty() {
278                    return Ok(None);
279                }
280                return Err(Error::Truncated);
281            }
282        }
283    }
284
285    /// Clean EOF between requests returns `None`; partial heads are errors.
286    pub async fn read_request(
287        &mut self,
288        cancellation: &Cancellation,
289    ) -> Result<Option<(Request<IncomingBody<'_, R>>, Exchange)>, Error> {
290        cancellation.check()?;
291        if !self.is_reusable() {
292            return Err(Error::Invalid("request input is not reusable"));
293        }
294        // Parse the first input available, which is usually a complete head.
295        // A head that is still incomplete is parsed again only once its
296        // terminator arrives, so fragmented input costs linear work. The loop stays in this function so the parsed metadata
297        // is not moved through another future's output.
298        self.poisoned = true;
299        let max_head_bytes = self.options.max_head_bytes;
300        let mut scanned = None;
301        let (head, facts) = loop {
302            let available = self.available();
303            let window = &available[..available.len().min(max_head_bytes)];
304            let terminated = scanned.is_none_or(|scanned: usize| {
305                window[scanned..].windows(4).any(|w| w == b"\r\n\r\n")
306            });
307            if terminated && !window.is_empty() {
308                if let Some((head, facts, n)) = wire::request(window, self.options.max_fields)? {
309                    self.start += n;
310                    break (head, facts);
311                }
312            }
313            if available.len() >= max_head_bytes {
314                return Err(Error::Limit("message head"));
315            }
316            if !available.is_empty() {
317                scanned = Some(available.len().saturating_sub(3));
318            }
319            if !self.fill(cancellation).await? {
320                if self.available().is_empty() {
321                    self.poisoned = false;
322                    return Ok(None);
323                }
324                return Err(Error::Truncated);
325            }
326        };
327        let length = facts.length;
328        let chunked = facts.chunked;
329        if chunked && head.version != Some(Version::Http11) {
330            return Err(Error::Invalid("HTTP/1.0 transfer-encoding"));
331        }
332        let framing = if chunked {
333            Framing::CHUNKED
334        } else {
335            Framing::Fixed(length.unwrap_or(0))
336        };
337        let exchange = Exchange::from_request(&head, facts);
338        self.previous = Some(exchange.clone());
339        self.poisoned = false;
340        let body = IncomingBody::new(
341            self,
342            exchange.clone(),
343            framing,
344            if chunked {
345                None
346            } else {
347                Some(length.unwrap_or(0))
348            },
349            false,
350            false,
351        );
352        Ok(Some((Request::from_parts(head, body), exchange)))
353    }
354
355    /// Establish backend retirement even after cancellation or parse failure.
356    pub async fn close(&mut self) -> io::Result<()> {
357        self.poisoned = true;
358        self.reader.close().await
359    }
360
361    async fn line(&mut self, limit: usize, cancellation: &Cancellation) -> Result<Vec<u8>, Error> {
362        loop {
363            if let Some(end) = self.available().windows(2).position(|w| w == b"\r\n") {
364                if end + 2 > limit {
365                    return Err(Error::Limit("line length"));
366                }
367                return Ok(self.take(end + 2));
368            }
369            if self.available().len() >= limit {
370                return Err(Error::Limit("line length"));
371            }
372            if !self.fill(cancellation).await? {
373                return Err(Error::Truncated);
374            }
375        }
376    }
377
378    async fn trailers(&mut self, cancellation: &Cancellation) -> Result<Fields, Error> {
379        let max_fields = self.options.max_fields;
380        let fields = self
381            .read_section(cancellation, "trailer section", |bytes| {
382                wire::trailers(bytes, max_fields)
383            })
384            .await?
385            .ok_or(Error::Truncated)?;
386        crate::output::validate_trailer_fields(fields.pairs())?;
387        Ok(fields)
388    }
389}
390
391pub(crate) enum Framing {
392    Fixed(u64),
393    /// Chunked input position: the unread data of the current chunk, whether
394    /// its CRLF delimiter remains unread, and whether the last-chunk line has
395    /// been read so that only the trailer section remains.
396    Chunked {
397        remaining: u64,
398        delimiter: bool,
399        trailers: bool,
400    },
401}
402
403impl Framing {
404    pub(crate) const CHUNKED: Self = Self::Chunked {
405        remaining: 0,
406        delimiter: false,
407        trailers: false,
408    };
409}
410
411/// A pull body borrowing the connection's input lane. Returning it as the
412/// response body provides streaming echo and managed duplex tunnels without
413/// boxing, queueing, spawning, or transferring socket ownership.
414///
415/// Reads are cancellation safe: dropping a pending read, or cancelling its
416/// wait, loses no input, and a later read resumes at the same position. This
417/// relies on `R` transferring bytes only within a `read_into` poll that returns
418/// them, as scheduler socket readers and the TLS stream do.
419pub struct IncomingBody<'a, R> {
420    input: &'a mut RequestReader<R>,
421    exchange: Exchange,
422    framing: Framing,
423    length: Option<u64>,
424    complete: bool,
425    closed: bool,
426    raw: bool,
427    forbid_content: bool,
428}
429
430impl<'a, R: ReadableStream> IncomingBody<'a, R> {
431    pub(crate) fn new(
432        input: &'a mut RequestReader<R>,
433        exchange: Exchange,
434        framing: Framing,
435        length: Option<u64>,
436        raw: bool,
437        forbid_content: bool,
438    ) -> Self {
439        let complete = !raw && matches!(framing, Framing::Fixed(0));
440        exchange.set_complete(complete);
441        Self {
442            input,
443            exchange,
444            framing,
445            length,
446            complete,
447            closed: false,
448            raw,
449            forbid_content,
450        }
451    }
452    async fn read_inner(
453        &mut self,
454        cancellation: &Cancellation,
455    ) -> Result<BodyFrame<Vec<u8>>, Error> {
456        if self.exchange.is_tunnel() && !self.raw {
457            self.raw = true;
458            self.complete = false;
459        }
460        if self.complete || self.closed {
461            return Ok(BodyFrame::End);
462        }
463        loop {
464            if self.raw {
465                if self.input.available().is_empty() && !self.input.fill(cancellation).await? {
466                    self.complete = true;
467                    return Ok(BodyFrame::End);
468                }
469                return Ok(BodyFrame::Data {
470                    data: self.input.take(self.input.available().len()),
471                    end: false,
472                });
473            }
474            match &mut self.framing {
475                Framing::Fixed(remaining) => {
476                    if self.input.available().is_empty() && !self.input.fill(cancellation).await? {
477                        return Err(Error::Truncated);
478                    }
479                    let n = (*remaining).min(self.input.available().len() as u64) as usize;
480                    *remaining -= n as u64;
481                    let data = self.input.take(n);
482                    if *remaining == 0 {
483                        self.complete = true;
484                    }
485                    return Ok(BodyFrame::Data {
486                        data,
487                        end: self.complete,
488                    });
489                }
490                Framing::Chunked {
491                    remaining,
492                    delimiter,
493                    trailers,
494                } => {
495                    if *remaining != 0 {
496                        if self.input.available().is_empty()
497                            && !self.input.fill(cancellation).await?
498                        {
499                            return Err(Error::Truncated);
500                        }
501                        let n = (*remaining).min(self.input.available().len() as u64) as usize;
502                        *remaining -= n as u64;
503                        return Ok(BodyFrame::Data {
504                            data: self.input.take(n),
505                            end: false,
506                        });
507                    }
508                    // Each wait below consumes no input, and consumption is
509                    // recorded before the next wait, so a dropped read resumes
510                    // at the same position.
511                    if !*trailers {
512                        if *delimiter {
513                            while self.input.available().len() < 2 {
514                                if !self.input.fill(cancellation).await? {
515                                    return Err(Error::Truncated);
516                                }
517                            }
518                            if !self.input.available().starts_with(b"\r\n") {
519                                return Err(Error::Invalid("missing chunk delimiter"));
520                            }
521                            self.input.start += 2;
522                            *delimiter = false;
523                        }
524                        let line = self
525                            .input
526                            .line(self.input.options.max_chunk_line_bytes, cancellation)
527                            .await?;
528                        let size = chunk_size(&line)?;
529                        if size != 0 {
530                            *remaining = size;
531                            *delimiter = true;
532                            continue;
533                        }
534                        *trailers = true;
535                    }
536                    let mut fields = self.input.trailers(cancellation).await?;
537                    self.exchange.filter_trailers(&mut fields);
538                    self.complete = true;
539                    return Ok(BodyFrame::Headers { fields, end: true });
540                }
541            }
542        }
543    }
544}
545
546impl<R: ReadableStream> ReadableBody for IncomingBody<'_, R> {
547    type Data = Vec<u8>;
548    type Error = Error;
549    fn may_have_trailers(&self) -> bool {
550        !self.raw && !self.exchange.is_tunnel() && matches!(self.framing, Framing::Chunked { .. })
551    }
552    fn is_ready(&self) -> bool {
553        !self.input.available().is_empty() || self.complete || self.closed
554    }
555    fn is_empty(&self) -> bool {
556        (self.complete && !self.exchange.is_tunnel()) || self.closed
557    }
558    fn length(&self) -> Option<u64> {
559        if self.exchange.is_tunnel() {
560            None
561        } else {
562            self.length
563        }
564    }
565    fn close(&mut self, _: Option<Error>) {
566        if !self.complete {
567            self.exchange.abandon();
568        }
569        self.closed = true;
570    }
571    async fn read(&mut self, cancellation: &Cancellation) -> Result<BodyFrame<Vec<u8>>, Error> {
572        cancellation.check()?;
573        if self.input.poisoned {
574            return Err(Error::Invalid("previous body read failed"));
575        }
576        // `read_inner` records each consumption before its next wait, so a
577        // dropped read or a cancelled wait loses nothing and a later read
578        // resumes. Any other failure leaves the input position unknown.
579        let result = self.read_inner(cancellation).await.and_then(|chunk| {
580            if self.forbid_content
581                && matches!(&chunk, BodyFrame::Data { data, .. } if !data.is_empty())
582            {
583                Err(Error::Invalid("content on 205"))
584            } else {
585                Ok(chunk)
586            }
587        });
588        match &result {
589            Ok(_) => self.exchange.set_complete(self.complete),
590            Err(Error::Cancelled) => {}
591            Err(_) => self.input.poisoned = true,
592        }
593        result
594    }
595}
596
597impl<R> Drop for IncomingBody<'_, R> {
598    fn drop(&mut self) {
599        if !self.complete {
600            self.exchange.abandon();
601        }
602    }
603}
604
605// Validate extension grammar rather than trusting ignored bytes after ';'.
606// A line ends at its first CRLF, and this grammar rejects any other CR or LF.
607fn chunk_size(line: &[u8]) -> Result<u64, Error> {
608    let bytes = &line[..line.len() - 2];
609    let end = bytes
610        .iter()
611        .position(|b| !b.is_ascii_hexdigit())
612        .unwrap_or(bytes.len());
613    if end == 0 {
614        return Err(Error::Invalid("invalid chunk size"));
615    }
616    let size = u64::from_str_radix(std::str::from_utf8(&bytes[..end]).unwrap(), 16)
617        .map_err(|_| Error::Invalid("chunk size overflow"))?;
618    let mut rest = &bytes[end..];
619    while !rest.is_empty() {
620        rest = wire::trim(rest);
621        if rest.first() != Some(&b';') {
622            return Err(Error::Invalid("invalid chunk extension"));
623        }
624        rest = &rest[1..];
625        rest = wire::trim(rest);
626        let end = rest
627            .iter()
628            .position(|b| !wire::token(&[*b]))
629            .unwrap_or(rest.len());
630        if end == 0 {
631            return Err(Error::Invalid("missing chunk extension name"));
632        }
633        rest = wire::trim(&rest[end..]);
634        if rest.first() == Some(&b'=') {
635            rest = wire::trim(&rest[1..]);
636            if rest.first() == Some(&b'"') {
637                rest = &rest[1..];
638                loop {
639                    let b = *rest
640                        .first()
641                        .ok_or(Error::Invalid("unterminated chunk extension"))?;
642                    rest = &rest[1..];
643                    if b == b'"' {
644                        break;
645                    }
646                    if b == b'\\' {
647                        let b = *rest
648                            .first()
649                            .ok_or(Error::Invalid("invalid quoted escape"))?;
650                        rest = &rest[1..];
651                        if b < 32 && b != b'\t' || b == 127 {
652                            return Err(Error::Invalid("invalid quoted escape"));
653                        }
654                    } else if b < 32 && b != b'\t' || b == 127 {
655                        return Err(Error::Invalid("invalid chunk extension"));
656                    }
657                }
658            } else {
659                let end = rest
660                    .iter()
661                    .position(|b| !wire::token(&[*b]))
662                    .unwrap_or(rest.len());
663                if end == 0 {
664                    return Err(Error::Invalid("missing chunk extension value"));
665                }
666                rest = &rest[end..];
667            }
668        }
669    }
670    Ok(size)
671}
672
673#[cfg(test)]
674mod tests {
675    use super::chunk_size;
676
677    #[test]
678    fn chunk_lines_reject_stray_line_controls() {
679        assert_eq!(chunk_size(b"1a;name=\"v\\\"\";flag\r\n").unwrap(), 26);
680        for line in [
681            &b"1\r;x\r\n"[..],
682            b"1\n\r\n",
683            b"1;x\r\r\n",
684            b"1;x=\r\r\n",
685            b"1;x=\"\r\"\r\n",
686            b"1;x=\"\\\n\"\r\n",
687            b"1;x\0\r\n",
688        ] {
689            assert!(chunk_size(line).is_err(), "{line:?}");
690        }
691    }
692
693    #[test]
694    fn chunk_lines_accept_token_extension_values_and_reject_malformed_ones() {
695        assert_eq!(chunk_size(b"ff ; name = value ; flag\r\n").unwrap(), 255);
696        for (line, reason) in [
697            (&b"11111111111111111\r\n"[..], "chunk size overflow"),
698            (b"1;x=\"open\r\n", "unterminated chunk extension"),
699            (b"1;x=\"\\\r\n", "invalid quoted escape"),
700        ] {
701            assert!(
702                matches!(chunk_size(line), Err(crate::Error::Invalid(r)) if r == reason),
703                "{line:?}"
704            );
705        }
706    }
707}