Skip to main content

ContentParser

Struct ContentParser 

Source
pub struct ContentParser<T, H = ()> { /* private fields */ }
Expand description

A sans-I/O parser for a request’s content, into the struct T declares, fed bytes as they arrive.

The format is chosen by the request’s media type. Every field and upload is matched against the declaration as it arrives: a name the declaration does not accept, and any limit exceeded, stop parsing with a fatal ContentError before any more content is read. Fields that arrived but cannot be accepted, and required fields that did not, are collected as Violations and returned together when parsing finishes.

The caller owns the buffer, as with base-web-multipart’s MultipartParser. Each call to Self::parse reports how many bytes it consumed and what they held: the caller drops the consumed bytes, appends whatever arrives next, and calls again with what remains. When the input ends, the caller gives what remains to Self::parse_end until it reports ContentEvent::End; then Self::finish returns the struct, or why it was refused. The result, events, violations, and errors are the same however the body is split, except that an upload’s content may arrive in different pieces.

Fields other than uploads are buffered within their limits. An accepted upload’s content is reported to the caller, borrowed from the input, to store wherever it chooses; when the upload ends, the caller gives the parser a handle of type H for it, which the result holds in its Upload. Uploads are stored before the whole body has been validated, so when parsing fails, Rejected returns every handle the caller gave, for the caller to remove what it stored. An upload whose content was still arriving when parsing failed has no handle, and the caller must remove what it stored of it. The handles are only returned by Self::finish, so the caller must finish the parser however parsing ends: after a fatal error from Self::parse or Self::parse_end, which Self::finish reports again, and when the input stops early, such as when the client disconnects or the request is cancelled, which it refuses as ContentErrorKind::UnexpectedEnd. Dropping the parser drops the handles it holds.

use base_web_content::{Content, ContentEvent, ContentParser, Upload};
use base_web_media_type::MediaType;

#[derive(Content)]
struct Comment {
    body: String,
    #[content(accept("text/*"))]
    attachment: Option<Upload<usize>>,
}

let media_type = MediaType::parse("multipart/form-data; boundary=b")?;
let body = b"--b\r\nContent-Disposition: form-data; name=\"body\"\r\n\r\nHello!\r\n--b\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"notes.txt\"\r\nContent-Type: text/plain\r\n\r\nSome notes.\r\n--b--\r\n";

let mut parser = ContentParser::<Comment, usize>::new(Some(&media_type))?;
let mut stored: Vec<Vec<u8>> = Vec::new();
// The whole body is in memory, so its end is known.
let mut input = &body[..];
loop {
    // After a fatal error, `finish` reports it, with every handle given so far, for the caller to remove what it stored.
    let Ok(parsed) = parser.parse_end(input) else { break };
    input = &input[parsed.consumed..];
    match parsed.event {
        // Begin storing an upload, such as in a temporary file.
        ContentEvent::Upload(_) => stored.push(Vec::new()),
        ContentEvent::UploadContent(content) => stored.last_mut().unwrap().extend_from_slice(content),
        // Give the parser a handle for what was stored.
        ContentEvent::UploadEnd => parser.store(stored.len() - 1),
        ContentEvent::End => break,
        ContentEvent::Incomplete => unreachable!("parse_end is given the rest of the input"),
    }
}

let comment = parser.finish().expect("the comment is accepted").content;
assert_eq!(comment.body, "Hello!");
let attachment = comment.attachment.expect("an attachment");
assert_eq!(stored[*attachment.handle()], b"Some notes.");

Implementations§

Source§

impl<T: ContentHandles<H>, H> ContentParser<T, H>

Source

pub fn new(media_type: Option<&MediaType>) -> Result<Self, ContentError>

A parser for content with this media type, the request’s Content-Type, if it has one.

multipart/form-data is parsed with base-web-multipart, reading part names and filenames under the ParameterRules its media type gives, which Self::with_parameter_rules can override. application/x-www-form-urlencoded is parsed with base-web-url, in UTF-8 or US-ASCII as its charset parameter says.

§Errors

A ContentError at offset 0 when the media type is missing, is not a format this crate parses, is a format T does not accept, is multipart/form-data without a valid boundary, or is application/x-www-form-urlencoded with a charset other than UTF-8 or US-ASCII.

Source§

impl<T, H> ContentParser<T, H>

Source

pub fn with_parameter_rules(self, rules: ParameterRules) -> Self

The parser, reading part names and filenames under these rules rather than those of the media type. URL-encoded content has no parameters, so its parser is unchanged.

Source

pub fn format(&self) -> ContentFormat

The format being parsed.

Source

pub fn body_size_limit(&self) -> usize

The largest body the declaration could need in this format. Parsing refuses a longer body with ContentErrorKind::BodySizeLimit.

Source

pub fn minimum_buffer_size(&self) -> usize

The input a caller must be able to buffer: given at least this many bytes, Self::parse always consumes some or reports an event other than ContentEvent::Incomplete, since a multipart part’s header section, and a URL-encoded name, are read whole.

Source

pub fn parse<'a>( &mut self, input: &'a [u8], ) -> Result<ContentParsed<'a>, ContentError>

Parse the next event from input, which must begin with the bytes the previous call did not consume, while more input may follow. Once the input has ended, call Self::parse_end instead.

§Errors

A fatal ContentError when the body is malformed, has a name the declaration does not accept, or exceeds a limit. Once the parser reports an error, it reports the same error for every later call.

§Panics

When an upload ended and its handle has not been given with Self::store.

Source

pub fn parse_end<'a>( &mut self, input: &'a [u8], ) -> Result<ContentParsed<'a>, ContentError>

Parse the next event from input, which must be all of the input that remains after the bytes already consumed, once the request’s body has ended. Call it until it reports ContentEvent::End, handling uploads as Self::parse reports them; it never reports ContentEvent::Incomplete. URL-encoded content ends only where its input does, so its last pair is only complete here.

§Errors

As for Self::parse, and ContentErrorKind::UnexpectedEnd when the content is not complete, such as a multipart body without its closing boundary. An error in what follows a multipart body’s closing boundary, the epilogue, is reported by the call after the one that reports ContentEvent::End, and by Self::finish.

§Panics

When an upload ended and its handle has not been given with Self::store.

Source

pub fn store(&mut self, handle: H)

Give the handle for what the caller stored of the upload that just ended.

§Panics

When no upload has ended since the last handle was given.

Source

pub fn finish(self) -> Result<Accepted<T, H>, Rejected<H>>
where T: ContentHandles<H>,

Finish once the input has ended and Self::parse_end has reported ContentEvent::End: the declared struct, or why it was refused, with every handle the caller gave. Call it however parsing ended, after a fatal error or when the input stopped early too, since only it returns the handles.

§Errors

Rejected, with a fatal ContentError when parsing failed or the body ended early, or every Violation when it parsed but some fields cannot be accepted.

Trait Implementations§

Source§

impl<T, H> Debug for ContentParser<T, H>

Shows the format, the offset, and any error, but no content.

Source§

fn fmt(&self, formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T, H> Forget for ContentParser<T, H>

§

impl<T, H> Freeze for ContentParser<T, H>
where Form<H>: Freeze, PhantomData<fn() -> T>: Freeze,

§

impl<T, H> RefUnwindSafe for ContentParser<T, H>
where Form<H>: RefUnwindSafe, PhantomData<fn() -> T>: RefUnwindSafe,

§

impl<T, H> Send for ContentParser<T, H>
where Form<H>: Send, PhantomData<fn() -> T>: Send,

§

impl<T, H> Sync for ContentParser<T, H>
where Form<H>: Sync, PhantomData<fn() -> T>: Sync,

§

impl<T, H> Unpin for ContentParser<T, H>
where Form<H>: Unpin, PhantomData<fn() -> T>: Unpin,

§

impl<T, H> UnsafeUnpin for ContentParser<T, H>
where Form<H>: UnsafeUnpin, PhantomData<fn() -> T>: UnsafeUnpin,

§

impl<T, H> UnwindSafe for ContentParser<T, H>
where Form<H>: UnwindSafe, PhantomData<fn() -> T>: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.