Skip to main content

base_http_content/
read_content.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::{ReadError, ReadErrorKind, UploadStore};
5use base_http::{
6    Fields, Request,
7    body::{BodyFrame, ReadableBody},
8};
9use base_scheduler::Cancellation;
10use base_web_content::{
11    Accepted, ContentError, ContentEvent, ContentHandles, ContentParser, ParameterRules, Rejection,
12};
13use base_web_media_type::MediaType;
14
15/// Options for [`read_content_with`], which may gain more: start from [`ReadOptions::default`] and set those you need.
16#[derive(Clone, Copy, Debug, Default)]
17#[non_exhaustive]
18pub struct ReadOptions {
19    /// The rules for reading multipart part names and filenames, rather than those the media type gives, as [`ContentParser::with_parameter_rules`] describes.
20    pub parameter_rules: Option<ParameterRules>,
21}
22
23/// Read a request's content into the struct `T` declares, streaming its uploads to `store`, with the default [`ReadOptions`].
24///
25/// The format is chosen by the request's `Content-Type`. A request whose length, from its body or its `Content-Length`, exceeds the largest body `T` could need in that format is refused with [`ReadErrorKind::ContentTooLarge`] before any of its body is read; a request without a known length is refused as soon as its body exceeds it. The body is read as it arrives, retaining only what the parser has not yet consumed, and each upload is streamed to `store` as it arrives.
26///
27/// When the content is refused, or reading or storing fails, the upload still arriving is aborted and every upload stored so far is removed, without cancellation, before the error is returned; any failures to remove them are in [`ReadError::cleanup`]. When reading stops before the body's end, the body is closed. The content is accepted exactly when [`ContentParser::finish`] accepts it, so a body that fails after the content is complete, in a multipart epilogue, still yields it. Accepted content holds its uploads' handles, which belong to the application, as do those in [`Accepted::discarded`], which later uploads for the same fields replaced; remove those with [`UploadStore::remove`] if they are not wanted.
28///
29/// Cleanup is asynchronous, so dropping the returned future before it completes skips it, and leaves everything it stored in place. To stop a read yourself, give it a child of the handler's cancellation, from [`Cancellation::child`], and cancel that: the body's read and the store's operations observe it, and the future, awaited, then cleans up. `base-http-server` drops a handler's future when the server's cancellation fires, at shutdown, so a read in progress then leaves everything it stored, and a handler still running leaves the uploads it accepted. Stage uploads in a directory the application clears when it starts, and move accepted uploads out of it. A client cannot make the server drop a handler, since its disconnection reaches the read as a body failure, which is cleaned up, but a client that stalls keeps its read in progress until shutdown.
30pub async fn read_content<T, B, S>(
31    request: &mut Request<B>,
32    store: &mut S,
33    cancellation: &Cancellation,
34) -> Result<Accepted<T, S::Handle>, ReadError<B::Error, S::Error>>
35where
36    T: ContentHandles<S::Handle>,
37    B: ReadableBody,
38    S: UploadStore,
39{
40    read_content_with(request, store, &ReadOptions::default(), cancellation).await
41}
42
43/// Read a request's content as [`read_content`] does, with `options`.
44pub async fn read_content_with<T, B, S>(
45    request: &mut Request<B>,
46    store: &mut S,
47    options: &ReadOptions,
48    cancellation: &Cancellation,
49) -> Result<Accepted<T, S::Handle>, ReadError<B::Error, S::Error>>
50where
51    T: ContentHandles<S::Handle>,
52    B: ReadableBody,
53    S: UploadStore,
54{
55    let parser = match parser(request, options) {
56        Ok(parser) => parser,
57        Err(kind) => {
58            request.body.close(None);
59            return Err(ReadError::new(kind));
60        }
61    };
62    let mut reader = Reader {
63        parser,
64        store,
65        upload: None,
66    };
67    let stopped = reader.read_body(&mut request.body, cancellation).await;
68    let Reader {
69        parser,
70        store,
71        upload,
72    } = reader;
73    if stopped.is_err() {
74        request.body.close(None);
75    }
76    let mut cleanup = Vec::new();
77    if let Some(upload) = upload {
78        if let Err(error) = store.abort(upload).await {
79            cleanup.push(error);
80        }
81    }
82    // An upload in progress leaves the content incomplete, so accepted content never follows an abort.
83    let rejected = match parser.finish() {
84        Ok(accepted) => return Ok(accepted),
85        Err(rejected) => rejected,
86    };
87    for handle in rejected.handles {
88        if let Err(error) = store.remove(handle).await {
89            cleanup.push(error);
90        }
91    }
92    // A body or store failure leaves the content incomplete, which the parser refuses as a consequence; the failure is the cause.
93    let kind = match stopped {
94        Err(Stop::Body(error)) => ReadErrorKind::Body(error),
95        Err(Stop::Store(error)) => ReadErrorKind::Store(error),
96        Ok(()) | Err(Stop::Content) => ReadErrorKind::Rejected(rejected.reason),
97    };
98    Err(ReadError { kind, cleanup })
99}
100
101/// The parser for the request's media type, once the request's length, if known, is within the largest body the declaration could need.
102fn parser<T, H, B, S>(
103    request: &Request<B>,
104    options: &ReadOptions,
105) -> Result<ContentParser<T, H>, ReadErrorKind<B::Error, S>>
106where
107    T: ContentHandles<H>,
108    B: ReadableBody,
109{
110    let media_type = media_type(&request.fields).ok_or(ReadErrorKind::InvalidMediaType)?;
111    let mut parser = ContentParser::<T, H>::new(media_type.as_ref())
112        .map_err(|error| ReadErrorKind::Rejected(Rejection::Error(error)))?;
113    if let Some(rules) = options.parameter_rules {
114        parser = parser.with_parameter_rules(rules);
115    }
116    let limit = parser.body_size_limit();
117    match request.length() {
118        Some(length) if length > limit as u64 => Err(ReadErrorKind::ContentTooLarge {
119            length,
120            limit: limit as u64,
121        }),
122        _ => Ok(parser),
123    }
124}
125
126/// The media type of the single `Content-Type` field, `Some(None)` without one, or `None` when there are several or it is not a valid media type.
127fn media_type(fields: &Fields) -> Option<Option<MediaType>> {
128    let mut values = fields.values("content-type");
129    let Some(value) = values.next() else {
130        return Some(None);
131    };
132    if values.next().is_some() {
133        return None;
134    }
135    let text = std::str::from_utf8(value).ok()?;
136    MediaType::parse(text).ok().map(Some)
137}
138
139/// Why reading stopped early.
140enum Stop<B, S> {
141    /// The parser reported a fatal error, which `finish` reports again.
142    Content,
143    Body(B),
144    Store(S),
145}
146
147/// A parser's fatal error, which `finish` reports with the handles to remove.
148fn content<B, S>(_: ContentError) -> Stop<B, S> {
149    Stop::Content
150}
151
152/// What one parser event leaves the reader to do.
153enum Step {
154    Continue,
155    Incomplete,
156    End,
157}
158
159struct Reader<'s, T, S: UploadStore> {
160    parser: ContentParser<T, S::Handle>,
161    store: &'s mut S,
162    /// The upload whose content is arriving.
163    upload: Option<S::Upload>,
164}
165
166impl<T, S: UploadStore> Reader<'_, T, S> {
167    /// Read the body to its end, giving each frame to the parser as it arrives, and only retaining what the parser has not consumed.
168    async fn read_body<B: ReadableBody>(
169        &mut self,
170        body: &mut B,
171        cancellation: &Cancellation,
172    ) -> Result<(), Stop<B::Error, S::Error>> {
173        let mut retained = Vec::new();
174        loop {
175            let frame = body.read(cancellation).await.map_err(Stop::Body)?;
176            let end = frame.is_end();
177            if let BodyFrame::Data { data, .. } = frame {
178                let data = data.as_ref();
179                if retained.is_empty() {
180                    let consumed = self.parse(data, false, cancellation).await?;
181                    retained.extend_from_slice(&data[consumed..]);
182                } else {
183                    retained.extend_from_slice(data);
184                    let consumed = self.parse(&retained, false, cancellation).await?;
185                    retained.drain(..consumed);
186                }
187            }
188            if end {
189                return self.parse(&retained, true, cancellation).await.map(drop);
190            }
191        }
192    }
193
194    /// Parse `input`, returning how much was consumed: while more may follow, until the parser needs more, or once the body has ended, with `end`, until the parser reports the content's end.
195    async fn parse<B>(
196        &mut self,
197        input: &[u8],
198        end: bool,
199        cancellation: &Cancellation,
200    ) -> Result<usize, Stop<B, S::Error>> {
201        let mut offset = 0;
202        loop {
203            let rest = &input[offset..];
204            let parsed = match end {
205                false => self.parser.parse(rest),
206                true => self.parser.parse_end(rest),
207            }
208            .map_err(content)?;
209            offset += parsed.consumed;
210            match self
211                .handle(parsed.event, cancellation)
212                .await
213                .map_err(Stop::Store)?
214            {
215                Step::Continue => {}
216                // A multipart body's epilogue may follow its end, and is parsed until the input is consumed; once the body has ended, `finish` checks what remains.
217                Step::End if !end && offset < input.len() => {}
218                // `parse_end` never reports `Incomplete`.
219                Step::Incomplete | Step::End => return Ok(offset),
220            }
221        }
222    }
223
224    /// Store an upload as the parser reports it.
225    async fn handle(
226        &mut self,
227        event: ContentEvent<'_>,
228        cancellation: &Cancellation,
229    ) -> Result<Step, S::Error> {
230        match event {
231            ContentEvent::Upload(head) => {
232                self.upload = Some(self.store.create(&head, cancellation).await?);
233            }
234            ContentEvent::UploadContent(content) => {
235                let upload = self.upload.as_mut().expect("content follows its upload");
236                self.store.write(upload, content, cancellation).await?;
237            }
238            ContentEvent::UploadEnd => {
239                let upload = self.upload.as_mut().expect("an upload ends after it began");
240                let handle = self.store.finish(upload, cancellation).await?;
241                self.upload = None;
242                self.parser.store(handle);
243            }
244            ContentEvent::End => return Ok(Step::End),
245            ContentEvent::Incomplete => return Ok(Step::Incomplete),
246        }
247        Ok(Step::Continue)
248    }
249}