Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions core/codegen/src/derive/from_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ pub fn derive_from_form(input: proc_macro::TokenStream) -> TokenStream {
<#ty as #_form::FromForm<'r>>::push_value(#ctxt, __f.shift());
})))
)
.inner_mapper(MapperBuild::new()
.with_output(|_, output| quote! {
fn push_value_with_bytes(
__c: &mut Self::Context,
__f: #_form::ValueField<'r>,
__bytes: &'r [u8]
) {
#output
}
})
.try_fields_map(|_, f| fields_map(f, |ty, ctxt| quote_spanned!(ty.span() => {
<#ty as #_form::FromForm<'r>>::push_value_with_bytes(#ctxt, __f.shift(), __bytes);
})))
)
.inner_mapper(MapperBuild::new()
.try_input_map(|mapper, input| {
let (ctxt_ty, gen) = context_type(input)?;
Expand Down
1 change: 1 addition & 0 deletions core/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ tempfile = "3"
async-trait = "0.1.43"
async-stream = "0.3.2"
multer = { version = "3.1.0", features = ["tokio-io"] }
encoding_rs = "0.8"
tokio-stream = { version = "0.1.6", features = ["signal", "time"] }
cookie = { version = "0.18", features = ["percent-encode"] }
futures = { version = "0.3.30", default-features = false, features = ["std"] }
Expand Down
9 changes: 9 additions & 0 deletions core/lib/src/data/capped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,15 @@ macro_rules! impl_strict_from_form_field_from_capped {
Ok(capped.value)
}

fn from_value_with_bytes(f: ValueField<'v>, bytes: &'v [u8]) -> Result<'v, Self> {
let capped = <Capped<$T> as FromFormField<'v>>::from_value_with_bytes(f, bytes)?;
if !capped.is_complete() {
Err((None, Some(capped.n.written)))?;
}

Ok(capped.value)
}

async fn from_data(field: DataField<'v, '_>) -> Result<'v, Self> {
let capped = <Capped<$T> as FromFormField<'v>>::from_data(field);
let capped = capped.await?;
Expand Down
9 changes: 9 additions & 0 deletions core/lib/src/form/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,15 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Contextual<'v, T> {
T::push_value(val_ctxt, field);
}

fn push_value_with_bytes(
(ref mut val_ctxt, ctxt): &mut Self::Context,
field: ValueField<'v>,
bytes: &'v [u8]
) {
ctxt.values.entry(field.name.source()).or_default().push(field.value);
T::push_value_with_bytes(val_ctxt, field, bytes);
}

async fn push_data((ref mut val_ctxt, ctxt): &mut Self::Context, field: DataField<'v, '_>) {
ctxt.data_fields.insert(field.name.source());
T::push_data(val_ctxt, field).await;
Expand Down
2 changes: 2 additions & 0 deletions core/lib/src/form/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use crate::fs::FileName;
/// Rocket preprocesses all form fields into either [`ValueField`]s or
/// [`DataField`]s. All fields from url-encoded forms, and fields without
/// Content-Types from multipart forms, are preprocessed as a `ValueField`.
/// Multipart value fields also supply their original bytes through
/// [`FromForm::push_value_with_bytes()`](crate::form::FromForm::push_value_with_bytes).
#[derive(Debug, Clone)]
pub struct ValueField<'r> {
/// The (decoded) name of the form field.
Expand Down
9 changes: 6 additions & 3 deletions core/lib/src/form/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,14 +319,17 @@ impl<'r, T: FromForm<'r>> FromData<'r> for Form<T> {
type Error = Errors<'r>;

async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
use either::Either;
use crate::form::parser::Field;

let mut parser = try_outcome!(Parser::new(req, data).await);
let mut context = T::init(Options::Lenient);
while let Some(field) = parser.next().await {
match field {
Ok(Either::Left(value)) => T::push_value(&mut context, value),
Ok(Either::Right(data)) => T::push_data(&mut context, data).await,
Ok(Field::Value(value)) => T::push_value(&mut context, value),
Ok(Field::ValueWithBytes(value, bytes)) => {
T::push_value_with_bytes(&mut context, value, bytes);
}
Ok(Field::Data(data)) => T::push_data(&mut context, data).await,
Err(e) => T::push_error(&mut context, e),
}
}
Expand Down
71 changes: 70 additions & 1 deletion core/lib/src/form/from_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ use crate::http::uncased::AsUncased;
///
/// `FromForm` describes a push-based parser for Rocket's [field wire format].
/// Fields are preprocessed into either [`ValueField`]s or [`DataField`]s which
/// are then pushed to the parser in [`FromForm::push_value()`] or
/// are then pushed to the parser in [`FromForm::push_value()`],
/// [`FromForm::push_value_with_bytes()`], or
/// [`FromForm::push_data()`], respectively. Both url-encoded forms and
/// multipart forms are supported. All url-encoded form fields are preprocessed
/// as [`ValueField`]s. Multipart form fields with Content-Types are processed
Expand Down Expand Up @@ -539,6 +540,26 @@ pub trait FromForm<'r>: Send + Sized {
/// Processes the value field `field`.
fn push_value(ctxt: &mut Self::Context, field: ValueField<'r>);

/// Processes a multipart value field with its original body bytes.
///
/// `field.value` contains the decoded text, while `bytes` contains the
/// original bytes before text decoding, without multipart headers or
/// delimiters. Rocket calls this method instead of `push_value` for
/// multipart value fields. URL-encoded and manually parsed value fields
/// continue to use `push_value`.
///
/// The default implementation calls [`Self::push_value()`] once. Wrappers
/// should forward this method to their inner form guard to preserve raw
/// bytes for guards such as [`TempFile`](crate::fs::TempFile). Forwarding
/// only `push_value` continues to work, but passes only decoded text.
fn push_value_with_bytes(
ctxt: &mut Self::Context,
field: ValueField<'r>,
_bytes: &'r [u8]
) {
Self::push_value(ctxt, field)
}

/// Processes the data field `field`.
async fn push_data(ctxt: &mut Self::Context, field: DataField<'r, '_>);

Expand Down Expand Up @@ -621,6 +642,10 @@ impl<'v, T: FromForm<'v> + 'v> FromForm<'v> for Vec<T> {
T::push_value(this.context(&field.name), field.shift());
}

fn push_value_with_bytes(this: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
T::push_value_with_bytes(this.context(&field.name), field.shift(), bytes);
}

async fn push_data(this: &mut Self::Context, field: DataField<'v, '_>) {
T::push_data(this.context(&field.name), field.shift()).await
}
Expand Down Expand Up @@ -728,6 +753,14 @@ impl<'v, K, V> MapContext<'v, K, V>
}
}

fn push_value_with_bytes(&mut self, field: ValueField<'v>, bytes: &'v [u8]) {
match self.push(field.name) {
Some(Either::Left(ctxt)) => K::push_value_with_bytes(ctxt, field.shift(), bytes),
Some(Either::Right(ctxt)) => V::push_value_with_bytes(ctxt, field.shift(), bytes),
_ => {}
}
}

async fn push_data(&mut self, field: DataField<'v, '_>) {
match self.push(field.name) {
Some(Either::Left(ctxt)) => K::push_data(ctxt, field.shift()).await,
Expand Down Expand Up @@ -782,6 +815,10 @@ impl<'v, K, V> FromForm<'v> for HashMap<K, V>
ctxt.push_value(field);
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
ctxt.push_value_with_bytes(field, bytes);
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
ctxt.push_data(field).await;
}
Expand All @@ -805,6 +842,10 @@ impl<'v, K, V> FromForm<'v> for BTreeMap<K, V>
ctxt.push_value(field);
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
ctxt.push_value_with_bytes(field, bytes);
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
ctxt.push_data(field).await;
}
Expand All @@ -826,6 +867,10 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Option<T> {
T::push_value(ctxt, field)
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
T::push_value_with_bytes(ctxt, field, bytes)
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
T::push_data(ctxt, field).await
}
Expand All @@ -847,6 +892,10 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Result<'v, T> {
T::push_value(ctxt, field)
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
T::push_value_with_bytes(ctxt, field, bytes)
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
T::push_data(ctxt, field).await
}
Expand Down Expand Up @@ -898,6 +947,14 @@ impl<'v, A: FromForm<'v>, B: FromForm<'v>> FromForm<'v> for (A, B) {
}
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
match ctxt.context(field.name) {
Ok(Either::Left(ctxt)) => A::push_value_with_bytes(ctxt, field.shift(), bytes),
Ok(Either::Right(ctxt)) => B::push_value_with_bytes(ctxt, field.shift(), bytes),
Err(e) => ctxt.errors.push(e),
}
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
match ctxt.context(field.name) {
Ok(Either::Left(ctxt)) => A::push_data(ctxt, field.shift()).await,
Expand Down Expand Up @@ -931,6 +988,10 @@ impl<'v, T: FromForm<'v> + Sync> FromForm<'v> for Arc<T> {
T::push_value(ctxt, field)
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
T::push_value_with_bytes(ctxt, field, bytes)
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
T::push_data(ctxt, field).await
}
Expand Down Expand Up @@ -966,6 +1027,14 @@ macro_rules! impl_via_proxy {
<proxy::$T<$($G),*>>::push_value(ctxt, field)
}

fn push_value_with_bytes(
ctxt: &mut Self::Context,
field: ValueField<'v>,
bytes: &'v [u8]
) {
<proxy::$T<$($G),*>>::push_value_with_bytes(ctxt, field, bytes)
}

async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
<proxy::$T<$($G),*>>::push_data(ctxt, field).await
}
Expand Down
40 changes: 40 additions & 0 deletions core/lib/src/form/from_form_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,35 @@ pub trait FromFormField<'v>: Send + Sized {
Err(field.unexpected())?
}

/// Parses a multipart value field with its original body bytes.
///
/// `field.value` contains the decoded text. `bytes` contains the original
/// bytes before text decoding, without multipart headers or delimiters.
/// The default implementation calls [`Self::from_value()`] once. Override
/// this method to preserve binary contents in multipart value fields.
/// URL-encoded and manually parsed values still use `from_value`.
///
/// # Example
///
/// ```rust
/// use rocket::form::{self, FromFormField, ValueField};
///
/// struct Bytes<'r>(&'r [u8]);
///
/// impl<'r> FromFormField<'r> for Bytes<'r> {
/// fn from_value(field: ValueField<'r>) -> form::Result<'r, Self> {
/// Ok(Bytes(field.value.as_bytes()))
/// }
///
/// fn from_value_with_bytes(_: ValueField<'r>, bytes: &'r [u8]) -> form::Result<'r, Self> {
/// Ok(Bytes(bytes))
/// }
/// }
/// ```
fn from_value_with_bytes(field: ValueField<'v>, _bytes: &'v [u8]) -> Result<'v, Self> {
Self::from_value(field)
}

/// Parse a value of `T` from a form data field.
///
/// The default implementation returns an error of
Expand Down Expand Up @@ -256,6 +285,13 @@ impl<'v, T: FromFormField<'v>> FromForm<'v> for T {
}
}

fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
if ctxt.should_push() {
ctxt.field_value = Some(field.value);
ctxt.push(field.name, Self::from_value_with_bytes(field, bytes))
}
}

async fn push_data(ctxt: &mut FromFieldContext<'v, T>, field: DataField<'v, '_>) {
if ctxt.should_push() {
ctxt.push(field.name, Self::from_data(field).await);
Expand Down Expand Up @@ -349,6 +385,10 @@ impl<'v> FromFormField<'v> for Capped<&'v [u8]> {
Ok(Capped::from(field.value.as_bytes()))
}

fn from_value_with_bytes(_: ValueField<'v>, bytes: &'v [u8]) -> Result<'v, Self> {
Ok(Capped::from(bytes))
}

async fn from_data(f: DataField<'v, '_>) -> Result<'v, Self> {
use crate::data::{Capped, Outcome, FromData};

Expand Down
5 changes: 5 additions & 0 deletions core/lib/src/form/lenient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ impl<'v, T: FromForm<'v>> FromForm<'v> for Lenient<T> {
T::push_value(ctxt, field)
}

#[inline(always)]
fn push_value_with_bytes(ctxt: &mut Self::Context, field: ValueField<'v>, bytes: &'v [u8]) {
T::push_value_with_bytes(ctxt, field, bytes)
}

#[inline(always)]
async fn push_data(ctxt: &mut Self::Context, field: DataField<'v, '_>) {
T::push_data(ctxt, field).await
Expand Down
38 changes: 26 additions & 12 deletions core/lib/src/form/parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::borrow::Cow;

use multer::Multipart;
use either::Either;
use encoding_rs::{Encoding, UTF_8};

use crate::request::{Request, local_cache_once};
use crate::data::{Data, Limits, Outcome};
Expand All @@ -8,11 +10,16 @@ use crate::form::prelude::*;

type Result<'r, T> = std::result::Result<T, Error<'r>>;

type Field<'r, 'i> = Either<ValueField<'r>, DataField<'r, 'i>>;
pub enum Field<'r, 'i> {
Value(ValueField<'r>),
ValueWithBytes(ValueField<'r>, &'r [u8]),
Data(DataField<'r, 'i>),
}

pub struct MultipartParser<'r, 'i> {
request: &'r Request<'i>,
buffer: &'r SharedStack<String>,
bytes: &'r SharedStack<Vec<u8>>,
source: Multipart<'r>,
done: bool,
}
Expand Down Expand Up @@ -77,6 +84,7 @@ impl<'r, 'i> Parser<'r, 'i> {
Ok(Parser::Multipart(MultipartParser {
request: req,
buffer: local_cache_once!(req, SharedStack::new()),
bytes: local_cache_once!(req, SharedStack::new()),
source: Multipart::with_reader_with_constraints(stream, boundary, constraints),
done: false,
}))
Expand All @@ -85,7 +93,7 @@ impl<'r, 'i> Parser<'r, 'i> {
pub async fn next(&mut self) -> Option<Result<'r, Field<'r, 'i>>> {
match self {
Parser::Multipart(ref mut p) => p.next().await,
Parser::RawStr(ref mut p) => p.next().map(|f| Ok(Either::Left(f)))
Parser::RawStr(ref mut p) => p.next().map(|f| Ok(Field::Value(f)))
}
}
}
Expand Down Expand Up @@ -181,26 +189,32 @@ impl<'r, 'i> MultipartParser<'r, 'i> {
}
};

Either::Right(DataField {
Field::Data(DataField {
content_type,
request: self.request,
name: NameView::new(name),
file_name: file_name.map(crate::fs::FileName::new),
data: Data::from(field),
})
} else {
let (mut buf, len) = match field.name() {
Some(s) => (s.to_string(), s.len()),
None => (String::new(), 0)
let name = self.buffer.push(field.name().unwrap_or(""));
let encoding = field.content_type()
.and_then(|mime| mime.get_param("charset"))
.and_then(|charset| Encoding::for_label(charset.as_str().as_bytes()))
.unwrap_or(UTF_8);
let bytes = match field.bytes().await {
Ok(bytes) => self.bytes.push(bytes.to_vec()),
Err(e) => return Some(Err(e.into())),
};

match field.text().await {
Ok(text) => buf.push_str(&text),
Err(e) => return Some(Err(e.into())),
// Match multer's text decoding, including BOM handling, while
// retaining the original bytes for binary value guards.
let value = match encoding.decode(bytes).0 {
Cow::Borrowed(value) => value,
Cow::Owned(value) => self.buffer.push(value),
};

let name_val = self.buffer.push_split(buf, len);
Either::Left(ValueField::from(name_val))
Field::ValueWithBytes(ValueField::from((name, value)), bytes)
};

Some(Ok(field))
Expand Down
Loading