add configuration to multipart::Form to choose percent-encoding format

Sets the default back to `path-segment`, since the "fix" breaks anyone
using reqwest from before.

Closes #363
This commit is contained in:
Sean McArthur
2018-10-26 13:57:45 -07:00
parent e1a67f32aa
commit 70b68c2b0a

View File

@@ -6,8 +6,7 @@ use std::io::{self, Cursor, Read};
use std::path::Path;
use mime_guess::{self, Mime};
use url::percent_encoding;
use url::percent_encoding::EncodeSet;
use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET};
use uuid::Uuid;
use http::HeaderMap;
@@ -18,6 +17,12 @@ pub struct Form {
boundary: String,
fields: Vec<(Cow<'static, str>, Part)>,
headers: Vec<Vec<u8>>,
percent_encoding: PercentEncoding,
}
enum PercentEncoding {
PathSegment,
AttrChar,
}
impl Form {
@@ -27,6 +32,7 @@ impl Form {
boundary: format!("{}", Uuid::new_v4().to_simple()),
fields: Vec::new(),
headers: Vec::new(),
percent_encoding: PercentEncoding::PathSegment,
}
}
@@ -84,6 +90,18 @@ impl Form {
self
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub fn percent_encode_path_segment(mut self) -> Form {
self.percent_encoding = PercentEncoding::PathSegment;
self
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub fn percent_encode_attr_chars(mut self) -> Form {
self.percent_encoding = PercentEncoding::AttrChar;
self
}
pub(crate) fn reader(self) -> Reader {
Reader::new(self)
}
@@ -98,7 +116,7 @@ impl Form {
Some(value_length) => {
// We are constructing the header just to get its length. To not have to
// construct it again when the request is sent we cache these headers.
let header = header(name, field);
let header = self.percent_encoding.encode_headers(name, field);
let header_length = header.len();
self.headers.push(header);
// The additions mimick the format string out of which the field is constructed
@@ -277,7 +295,7 @@ impl Reader {
let mut h = if self.form.headers.len() > 0 {
self.form.headers.remove(0)
} else {
header(&name, &field)
self.form.percent_encoding.encode_headers(&name, &field)
};
h.extend_from_slice(b"\r\n\r\n");
h
@@ -350,12 +368,13 @@ impl EncodeSet for AttrCharEncodeSet {
}
fn header(name: &str, field: &Part) -> Vec<u8> {
impl PercentEncoding {
fn encode_headers(&self, name: &str, field: &Part) -> Vec<u8> {
let s = format!(
"Content-Disposition: form-data; {}{}{}",
format_parameter("name", name),
self.format_parameter("name", name),
match field.file_name {
Some(ref file_name) => format!("; {}", format_parameter("filename", file_name)),
Some(ref file_name) => format!("; {}", self.format_parameter("filename", file_name)),
None => String::new(),
},
match field.mime {
@@ -372,10 +391,17 @@ fn header(name: &str, field: &Part) -> Vec<u8> {
})
}
fn format_parameter(name: &str, value: &str) -> String {
let legal_value =
fn format_parameter(&self, name: &str, value: &str) -> String {
let legal_value = match *self {
PercentEncoding::PathSegment => {
percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET)
.to_string()
},
PercentEncoding::AttrChar => {
percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet)
.to_string();
.to_string()
},
};
if value.len() == legal_value.len() {
// nothing has been percent encoded
format!("{}=\"{}\"", name, value)
@@ -384,6 +410,8 @@ fn format_parameter(name: &str, value: &str) -> String {
format!("{}*=utf-8''{}", name, legal_value)
}
}
}
#[cfg(test)]
mod tests {
@@ -507,8 +535,15 @@ mod tests {
fn header_percent_encoding() {
let name = "start%'\"\r\nßend";
let field = Part::text("");
let expected = "Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend";
assert_eq!(header(name, &field), expected.as_bytes());
assert_eq!(
PercentEncoding::PathSegment.encode_headers(name, &field),
&b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
);
assert_eq!(
PercentEncoding::AttrChar.encode_headers(name, &field),
&b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
);
}
}