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:
		| @@ -6,8 +6,7 @@ use std::io::{self, Cursor, Read}; | |||||||
| use std::path::Path; | use std::path::Path; | ||||||
|  |  | ||||||
| use mime_guess::{self, Mime}; | use mime_guess::{self, Mime}; | ||||||
| use url::percent_encoding; | use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET}; | ||||||
| use url::percent_encoding::EncodeSet; |  | ||||||
| use uuid::Uuid; | use uuid::Uuid; | ||||||
| use http::HeaderMap; | use http::HeaderMap; | ||||||
|  |  | ||||||
| @@ -18,6 +17,12 @@ pub struct Form { | |||||||
|     boundary: String, |     boundary: String, | ||||||
|     fields: Vec<(Cow<'static, str>, Part)>, |     fields: Vec<(Cow<'static, str>, Part)>, | ||||||
|     headers: Vec<Vec<u8>>, |     headers: Vec<Vec<u8>>, | ||||||
|  |     percent_encoding: PercentEncoding, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum PercentEncoding { | ||||||
|  |     PathSegment, | ||||||
|  |     AttrChar, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Form { | impl Form { | ||||||
| @@ -27,6 +32,7 @@ impl Form { | |||||||
|             boundary: format!("{}", Uuid::new_v4().to_simple()), |             boundary: format!("{}", Uuid::new_v4().to_simple()), | ||||||
|             fields: Vec::new(), |             fields: Vec::new(), | ||||||
|             headers: Vec::new(), |             headers: Vec::new(), | ||||||
|  |             percent_encoding: PercentEncoding::PathSegment, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -84,6 +90,18 @@ impl Form { | |||||||
|         self |         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 { |     pub(crate) fn reader(self) -> Reader { | ||||||
|         Reader::new(self) |         Reader::new(self) | ||||||
|     } |     } | ||||||
| @@ -98,7 +116,7 @@ impl Form { | |||||||
|                 Some(value_length) => { |                 Some(value_length) => { | ||||||
|                     // We are constructing the header just to get its length. To not have to |                     // 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. |                     // 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(); |                     let header_length = header.len(); | ||||||
|                     self.headers.push(header); |                     self.headers.push(header); | ||||||
|                     // The additions mimick the format string out of which the field is constructed |                     // 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 { |                 let mut h = if self.form.headers.len() > 0 { | ||||||
|                     self.form.headers.remove(0) |                     self.form.headers.remove(0) | ||||||
|                 } else { |                 } else { | ||||||
|                     header(&name, &field) |                     self.form.percent_encoding.encode_headers(&name, &field) | ||||||
|                 }; |                 }; | ||||||
|                 h.extend_from_slice(b"\r\n\r\n"); |                 h.extend_from_slice(b"\r\n\r\n"); | ||||||
|                 h |                 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!( |         let s = format!( | ||||||
|             "Content-Disposition: form-data; {}{}{}", |             "Content-Disposition: form-data; {}{}{}", | ||||||
|         format_parameter("name", name), |             self.format_parameter("name", name), | ||||||
|             match field.file_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(), |                 None => String::new(), | ||||||
|             }, |             }, | ||||||
|             match field.mime { |             match field.mime { | ||||||
| @@ -372,10 +391,17 @@ fn header(name: &str, field: &Part) -> Vec<u8> { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| fn format_parameter(name: &str, value: &str) -> String { |     fn format_parameter(&self, name: &str, value: &str) -> String { | ||||||
|     let legal_value = |         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) |                 percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet) | ||||||
|             .to_string(); |                     .to_string() | ||||||
|  |             }, | ||||||
|  |         }; | ||||||
|         if value.len() == legal_value.len() { |         if value.len() == legal_value.len() { | ||||||
|             // nothing has been percent encoded |             // nothing has been percent encoded | ||||||
|             format!("{}=\"{}\"", name, value) |             format!("{}=\"{}\"", name, value) | ||||||
| @@ -384,6 +410,8 @@ fn format_parameter(name: &str, value: &str) -> String { | |||||||
|             format!("{}*=utf-8''{}", name, legal_value) |             format!("{}*=utf-8''{}", name, legal_value) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
| @@ -507,8 +535,15 @@ mod tests { | |||||||
|     fn header_percent_encoding() { |     fn header_percent_encoding() { | ||||||
|         let name = "start%'\"\r\nßend"; |         let name = "start%'\"\r\nßend"; | ||||||
|         let field = Part::text(""); |         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"[..] | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user