committed by
					
						 Sean McArthur
						Sean McArthur
					
				
			
			
				
	
			
			
			
						parent
						
							81e0f1ff2a
						
					
				
				
					commit
					cf8944a0f0
				
			| @@ -2,10 +2,10 @@ | ||||
| use std::borrow::Cow; | ||||
| use std::fmt; | ||||
|  | ||||
| use http::HeaderMap; | ||||
| use mime_guess::Mime; | ||||
| use url::percent_encoding::{self, EncodeSet, PATH_SEGMENT_ENCODE_SET}; | ||||
| use uuid::Uuid; | ||||
| use http::HeaderMap; | ||||
|  | ||||
| use futures::Stream; | ||||
|  | ||||
| @@ -98,7 +98,7 @@ impl Form { | ||||
|  | ||||
|     /// Consume this instance and transform into an instance of hyper::Body for use in a request. | ||||
|     pub(crate) fn stream(mut self) -> hyper::Body { | ||||
|         if self.inner.fields.is_empty(){ | ||||
|         if self.inner.fields.is_empty() { | ||||
|             return hyper::Body::empty(); | ||||
|         } | ||||
|  | ||||
| @@ -117,7 +117,7 @@ impl Form { | ||||
|         hyper::Body::wrap_stream(stream.chain(last)) | ||||
|     } | ||||
|  | ||||
|     /// Generate a hyper::Body stream for a single Part instance of a Form request.  | ||||
|     /// Generate a hyper::Body stream for a single Part instance of a Form request. | ||||
|     pub(crate) fn part_stream<T>(&mut self, name: T, part: Part) -> hyper::Body | ||||
|     where | ||||
|         T: Into<Cow<'static, str>>, | ||||
| @@ -126,12 +126,20 @@ impl Form { | ||||
|         let boundary = hyper::Body::from(format!("--{}\r\n", self.boundary())); | ||||
|         // append headers | ||||
|         let header = hyper::Body::from({ | ||||
|             let mut h = self.inner.percent_encoding.encode_headers(&name.into(), &part.meta); | ||||
|             let mut h = self | ||||
|                 .inner | ||||
|                 .percent_encoding | ||||
|                 .encode_headers(&name.into(), &part.meta); | ||||
|             h.extend_from_slice(b"\r\n\r\n"); | ||||
|             h | ||||
|         }); | ||||
|         // then append form data followed by terminating CRLF | ||||
|         hyper::Body::wrap_stream(boundary.chain(header).chain(hyper::Body::wrap_stream(part.value)).chain(hyper::Body::from("\r\n".to_owned()))) | ||||
|         hyper::Body::wrap_stream( | ||||
|             boundary | ||||
|                 .chain(header) | ||||
|                 .chain(hyper::Body::wrap_stream(part.value)) | ||||
|                 .chain(hyper::Body::from("\r\n".to_owned())), | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn compute_length(&mut self) -> Option<u64> { | ||||
| @@ -188,7 +196,9 @@ impl Part { | ||||
|         T::Item: Into<Chunk>, | ||||
|         T::Error: std::error::Error + Send + Sync, | ||||
|     { | ||||
|         Part::new(Body::wrap(hyper::Body::wrap_stream(value.map(|chunk| chunk.into())))) | ||||
|         Part::new(Body::wrap(hyper::Body::wrap_stream( | ||||
|             value.map(|chunk| chunk.into()), | ||||
|         ))) | ||||
|     } | ||||
|  | ||||
|     fn new(value: Body) -> Part { | ||||
| @@ -306,7 +316,13 @@ impl<P: PartProps> FormParts<P> { | ||||
|                     // in Reader. Not the cleanest solution because if that format string is | ||||
|                     // ever changed then this formula needs to be changed too which is not an | ||||
|                     // obvious dependency in the code. | ||||
|                     length += 2 + self.boundary().len() as u64 + 2 + header_length as u64 + 4 + value_length + 2 | ||||
|                     length += 2 | ||||
|                         + self.boundary().len() as u64 | ||||
|                         + 2 | ||||
|                         + header_length as u64 | ||||
|                         + 4 | ||||
|                         + value_length | ||||
|                         + 2 | ||||
|                 } | ||||
|                 _ => return None, | ||||
|             } | ||||
| @@ -340,7 +356,7 @@ impl PartMetadata { | ||||
|         PartMetadata { | ||||
|             mime: None, | ||||
|             file_name: None, | ||||
|             headers: HeaderMap::default() | ||||
|             headers: HeaderMap::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -358,11 +374,10 @@ impl PartMetadata { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl PartMetadata { | ||||
|     pub(crate) fn fmt_fields<'f, 'fa, 'fb>( | ||||
|         &self, | ||||
|         debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb> | ||||
|         debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, | ||||
|     ) -> &'f mut fmt::DebugStruct<'fa, 'fb> { | ||||
|         debug_struct | ||||
|             .field("mime", &self.mime) | ||||
| @@ -377,22 +392,24 @@ pub(crate) struct AttrCharEncodeSet; | ||||
| impl EncodeSet for AttrCharEncodeSet { | ||||
|     fn contains(&self, ch: u8) -> bool { | ||||
|         match ch as char { | ||||
|              '!'  => false, | ||||
|              '#'  => false, | ||||
|              '$'  => false, | ||||
|              '&'  => false, | ||||
|              '+'  => false, | ||||
|              '-'  => false, | ||||
|              '.' => false, | ||||
|              '^'  => false, | ||||
|              '_'  => false, | ||||
|              '`'  => false, | ||||
|              '|'  => false, | ||||
|              '~' => false, | ||||
|               _ => { | ||||
|                   let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a || ch >= 0x30 && ch <= 0x39; | ||||
|                   !is_alpha_numeric | ||||
|               } | ||||
|             '!' => false, | ||||
|             '#' => false, | ||||
|             '$' => false, | ||||
|             '&' => false, | ||||
|             '+' => false, | ||||
|             '-' => false, | ||||
|             '.' => false, | ||||
|             '^' => false, | ||||
|             '_' => false, | ||||
|             '`' => false, | ||||
|             '|' => false, | ||||
|             '~' => false, | ||||
|             _ => { | ||||
|                 let is_alpha_numeric = ch >= 0x41 && ch <= 0x5a | ||||
|                     || ch >= 0x61 && ch <= 0x7a | ||||
|                     || ch >= 0x30 && ch <= 0x39; | ||||
|                 !is_alpha_numeric | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -417,36 +434,38 @@ impl PercentEncoding { | ||||
|                 None => "".to_string(), | ||||
|             }, | ||||
|         ); | ||||
|         field.headers.iter().fold(s.into_bytes(), |mut header, (k,v)| { | ||||
|             header.extend_from_slice(b"\r\n"); | ||||
|             header.extend_from_slice(k.as_str().as_bytes()); | ||||
|             header.extend_from_slice(b": "); | ||||
|             header.extend_from_slice(v.as_bytes()); | ||||
|             header | ||||
|         }) | ||||
|         field | ||||
|             .headers | ||||
|             .iter() | ||||
|             .fold(s.into_bytes(), |mut header, (k, v)| { | ||||
|                 header.extend_from_slice(b"\r\n"); | ||||
|                 header.extend_from_slice(k.as_str().as_bytes()); | ||||
|                 header.extend_from_slice(b": "); | ||||
|                 header.extend_from_slice(v.as_bytes()); | ||||
|                 header | ||||
|             }) | ||||
|     } | ||||
|  | ||||
|     // According to RFC7578 Section 4.2, `filename*=` syntax is invalid. | ||||
|     // See https://github.com/seanmonstar/reqwest/issues/419. | ||||
|     fn format_filename(&self, filename: &str) -> String { | ||||
|         let legal_filename = filename.replace("\\", "\\\\") | ||||
|                                      .replace("\"", "\\\"") | ||||
|                                      .replace("\r", "\\\r") | ||||
|                                      .replace("\n", "\\\n"); | ||||
|         let legal_filename = filename | ||||
|             .replace("\\", "\\\\") | ||||
|             .replace("\"", "\\\"") | ||||
|             .replace("\r", "\\\r") | ||||
|             .replace("\n", "\\\n"); | ||||
|         format!("filename=\"{}\"", legal_filename) | ||||
|     } | ||||
|  | ||||
|     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() | ||||
|             }, | ||||
|                 percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string() | ||||
|             } | ||||
|             PercentEncoding::AttrChar => { | ||||
|                 percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet) | ||||
|                     .to_string() | ||||
|             }, | ||||
|             PercentEncoding::NoOp => { value.to_string() }, | ||||
|                 percent_encoding::utf8_percent_encode(value, AttrCharEncodeSet).to_string() | ||||
|             } | ||||
|             PercentEncoding::NoOp => value.to_string(), | ||||
|         }; | ||||
|         if value.len() == legal_value.len() { | ||||
|             // nothing has been percent encoded | ||||
| @@ -477,38 +496,45 @@ mod tests { | ||||
|     #[test] | ||||
|     fn stream_to_end() { | ||||
|         let mut form = Form::new() | ||||
|             .part("reader1", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part1".to_owned()))))) | ||||
|             .part("key1", Part::text("value1")) | ||||
|             .part( | ||||
|                 "key2", | ||||
|                 Part::text("value2").mime(mime::IMAGE_BMP), | ||||
|                 "reader1", | ||||
|                 Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from( | ||||
|                     "part1".to_owned(), | ||||
|                 )))), | ||||
|             ) | ||||
|             .part("reader2", Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from("part2".to_owned()))))) | ||||
|             .part("key1", Part::text("value1")) | ||||
|             .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) | ||||
|             .part( | ||||
|                 "key3", | ||||
|                 Part::text("value3").file_name("filename"), | ||||
|             ); | ||||
|                 "reader2", | ||||
|                 Part::stream(futures::stream::once::<_, hyper::Error>(Ok(Chunk::from( | ||||
|                     "part2".to_owned(), | ||||
|                 )))), | ||||
|             ) | ||||
|             .part("key3", Part::text("value3").file_name("filename")); | ||||
|         form.inner.boundary = "boundary".to_string(); | ||||
|         let expected = "--boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ | ||||
|                         part1\r\n\ | ||||
|                         --boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ | ||||
|                         value1\r\n\ | ||||
|                         --boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"key2\"\r\n\ | ||||
|                         Content-Type: image/bmp\r\n\r\n\ | ||||
|                         value2\r\n\ | ||||
|                         --boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ | ||||
|                         part2\r\n\ | ||||
|                         --boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ | ||||
|                         value3\r\n--boundary--\r\n"; | ||||
|         let expected = | ||||
|             "--boundary\r\n\ | ||||
|              Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ | ||||
|              part1\r\n\ | ||||
|              --boundary\r\n\ | ||||
|              Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ | ||||
|              value1\r\n\ | ||||
|              --boundary\r\n\ | ||||
|              Content-Disposition: form-data; name=\"key2\"\r\n\ | ||||
|              Content-Type: image/bmp\r\n\r\n\ | ||||
|              value2\r\n\ | ||||
|              --boundary\r\n\ | ||||
|              Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ | ||||
|              part2\r\n\ | ||||
|              --boundary\r\n\ | ||||
|              Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ | ||||
|              value3\r\n--boundary--\r\n"; | ||||
|         let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|  | ||||
|         let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap(); | ||||
|         let out = rt | ||||
|             .block_on(body_ft.map(|c| c.into_bytes()).concat2()) | ||||
|             .unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
| @@ -534,7 +560,9 @@ mod tests { | ||||
|         let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); | ||||
|         let body_ft = form.stream(); | ||||
|  | ||||
|         let out = rt.block_on(body_ft.map(|c| c.into_bytes()).concat2()).unwrap(); | ||||
|         let out = rt | ||||
|             .block_on(body_ft.map(|c| c.into_bytes()).concat2()) | ||||
|             .unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user