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>
impl<T: ContentHandles<H>, H> ContentParser<T, H>
Sourcepub fn new(media_type: Option<&MediaType>) -> Result<Self, ContentError>
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>
impl<T, H> ContentParser<T, H>
Sourcepub fn with_parameter_rules(self, rules: ParameterRules) -> Self
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.
Sourcepub fn format(&self) -> ContentFormat
pub fn format(&self) -> ContentFormat
The format being parsed.
Sourcepub fn body_size_limit(&self) -> usize
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.
Sourcepub fn minimum_buffer_size(&self) -> usize
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.
Sourcepub fn parse<'a>(
&mut self,
input: &'a [u8],
) -> Result<ContentParsed<'a>, ContentError>
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.
Sourcepub fn parse_end<'a>(
&mut self,
input: &'a [u8],
) -> Result<ContentParsed<'a>, ContentError>
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.
Sourcepub fn store(&mut self, handle: H)
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.
Sourcepub fn finish(self) -> Result<Accepted<T, H>, Rejected<H>>where
T: ContentHandles<H>,
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.