1 //! multipart/form-data 2 //! 3 //! To send a `multipart/form-data` body, a [`Form`](crate::multipart::Form) is built up, adding 4 //! fields or customized [`Part`](crate::multipart::Part)s, and then calling the 5 //! [`multipart`][builder] method on the `RequestBuilder`. 6 //! 7 //! # Example 8 //! 9 //! ``` 10 //! use reqwest::blocking::multipart; 11 //! 12 //! # fn run() -> Result<(), Box<dyn std::error::Error>> { 13 //! let form = multipart::Form::new() 14 //! // Adding just a simple text field... 15 //! .text("username", "seanmonstar") 16 //! // And a file... 17 //! .file("photo", "/path/to/photo.png")?; 18 //! 19 //! // Customize all the details of a Part if needed... 20 //! let bio = multipart::Part::text("hallo peeps") 21 //! .file_name("bio.txt") 22 //! .mime_str("text/plain")?; 23 //! 24 //! // Add the custom part to our form... 25 //! let form = form.part("biography", bio); 26 //! 27 //! // And finally, send the form 28 //! let client = reqwest::blocking::Client::new(); 29 //! let resp = client 30 //! .post("http://localhost:8080/user") 31 //! .multipart(form) 32 //! .send()?; 33 //! # Ok(()) 34 //! # } 35 //! # fn main() {} 36 //! ``` 37 //! 38 //! [builder]: ../struct.RequestBuilder.html#method.multipart 39 use std::borrow::Cow; 40 use std::fmt; 41 use std::fs::File; 42 use std::io::{self, Cursor, Read}; 43 use std::path::Path; 44 45 use mime_guess::{self, Mime}; 46 47 use super::Body; 48 use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps}; 49 50 /// A multipart/form-data request. 51 pub struct Form { 52 inner: FormParts<Part>, 53 } 54 55 /// A field in a multipart form. 56 pub struct Part { 57 meta: PartMetadata, 58 value: Body, 59 } 60 61 impl Default for Form { default() -> Self62 fn default() -> Self { 63 Self::new() 64 } 65 } 66 67 impl Form { 68 /// Creates a new Form without any content. new() -> Form69 pub fn new() -> Form { 70 Form { 71 inner: FormParts::new(), 72 } 73 } 74 75 /// Get the boundary that this form will use. 76 #[inline] boundary(&self) -> &str77 pub fn boundary(&self) -> &str { 78 self.inner.boundary() 79 } 80 81 /// Add a data field with supplied name and value. 82 /// 83 /// # Examples 84 /// 85 /// ``` 86 /// let form = reqwest::blocking::multipart::Form::new() 87 /// .text("username", "seanmonstar") 88 /// .text("password", "secret"); 89 /// ``` text<T, U>(self, name: T, value: U) -> Form where T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>,90 pub fn text<T, U>(self, name: T, value: U) -> Form 91 where 92 T: Into<Cow<'static, str>>, 93 U: Into<Cow<'static, str>>, 94 { 95 self.part(name, Part::text(value)) 96 } 97 98 /// Adds a file field. 99 /// 100 /// The path will be used to try to guess the filename and mime. 101 /// 102 /// # Examples 103 /// 104 /// ```no_run 105 /// # fn run() -> std::io::Result<()> { 106 /// let files = reqwest::blocking::multipart::Form::new() 107 /// .file("key", "/path/to/file")?; 108 /// # Ok(()) 109 /// # } 110 /// ``` 111 /// 112 /// # Errors 113 /// 114 /// Errors when the file cannot be opened. file<T, U>(self, name: T, path: U) -> io::Result<Form> where T: Into<Cow<'static, str>>, U: AsRef<Path>,115 pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form> 116 where 117 T: Into<Cow<'static, str>>, 118 U: AsRef<Path>, 119 { 120 Ok(self.part(name, Part::file(path)?)) 121 } 122 123 /// Adds a customized Part. part<T>(self, name: T, part: Part) -> Form where T: Into<Cow<'static, str>>,124 pub fn part<T>(self, name: T, part: Part) -> Form 125 where 126 T: Into<Cow<'static, str>>, 127 { 128 self.with_inner(move |inner| inner.part(name, part)) 129 } 130 131 /// Configure this `Form` to percent-encode using the `path-segment` rules. percent_encode_path_segment(self) -> Form132 pub fn percent_encode_path_segment(self) -> Form { 133 self.with_inner(|inner| inner.percent_encode_path_segment()) 134 } 135 136 /// Configure this `Form` to percent-encode using the `attr-char` rules. percent_encode_attr_chars(self) -> Form137 pub fn percent_encode_attr_chars(self) -> Form { 138 self.with_inner(|inner| inner.percent_encode_attr_chars()) 139 } 140 141 /// Configure this `Form` to skip percent-encoding percent_encode_noop(self) -> Form142 pub fn percent_encode_noop(self) -> Form { 143 self.with_inner(|inner| inner.percent_encode_noop()) 144 } 145 reader(self) -> Reader146 pub(crate) fn reader(self) -> Reader { 147 Reader::new(self) 148 } 149 150 // If predictable, computes the length the request will have 151 // The length should be preditable if only String and file fields have been added, 152 // but not if a generic reader has been added; compute_length(&mut self) -> Option<u64>153 pub(crate) fn compute_length(&mut self) -> Option<u64> { 154 self.inner.compute_length() 155 } 156 with_inner<F>(self, func: F) -> Self where F: FnOnce(FormParts<Part>) -> FormParts<Part>,157 fn with_inner<F>(self, func: F) -> Self 158 where 159 F: FnOnce(FormParts<Part>) -> FormParts<Part>, 160 { 161 Form { 162 inner: func(self.inner), 163 } 164 } 165 } 166 167 impl fmt::Debug for Form { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 169 self.inner.fmt_fields("Form", f) 170 } 171 } 172 173 impl Part { 174 /// Makes a text parameter. text<T>(value: T) -> Part where T: Into<Cow<'static, str>>,175 pub fn text<T>(value: T) -> Part 176 where 177 T: Into<Cow<'static, str>>, 178 { 179 let body = match value.into() { 180 Cow::Borrowed(slice) => Body::from(slice), 181 Cow::Owned(string) => Body::from(string), 182 }; 183 Part::new(body) 184 } 185 186 /// Makes a new parameter from arbitrary bytes. bytes<T>(value: T) -> Part where T: Into<Cow<'static, [u8]>>,187 pub fn bytes<T>(value: T) -> Part 188 where 189 T: Into<Cow<'static, [u8]>>, 190 { 191 let body = match value.into() { 192 Cow::Borrowed(slice) => Body::from(slice), 193 Cow::Owned(vec) => Body::from(vec), 194 }; 195 Part::new(body) 196 } 197 198 /// Adds a generic reader. 199 /// 200 /// Does not set filename or mime. reader<T: Read + Send + 'static>(value: T) -> Part201 pub fn reader<T: Read + Send + 'static>(value: T) -> Part { 202 Part::new(Body::new(value)) 203 } 204 205 /// Adds a generic reader with known length. 206 /// 207 /// Does not set filename or mime. reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part208 pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part { 209 Part::new(Body::sized(value, length)) 210 } 211 212 /// Makes a file parameter. 213 /// 214 /// # Errors 215 /// 216 /// Errors when the file cannot be opened. file<T: AsRef<Path>>(path: T) -> io::Result<Part>217 pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> { 218 let path = path.as_ref(); 219 let file_name = path 220 .file_name() 221 .map(|filename| filename.to_string_lossy().into_owned()); 222 let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or(""); 223 let mime = mime_guess::from_ext(ext).first_or_octet_stream(); 224 let file = File::open(path)?; 225 let field = Part::new(Body::from(file)).mime(mime); 226 227 Ok(if let Some(file_name) = file_name { 228 field.file_name(file_name) 229 } else { 230 field 231 }) 232 } 233 new(value: Body) -> Part234 fn new(value: Body) -> Part { 235 Part { 236 meta: PartMetadata::new(), 237 value, 238 } 239 } 240 241 /// Tries to set the mime of this part. mime_str(self, mime: &str) -> crate::Result<Part>242 pub fn mime_str(self, mime: &str) -> crate::Result<Part> { 243 Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) 244 } 245 246 // Re-export when mime 0.4 is available, with split MediaType/MediaRange. mime(self, mime: Mime) -> Part247 fn mime(self, mime: Mime) -> Part { 248 self.with_inner(move |inner| inner.mime(mime)) 249 } 250 251 /// Sets the filename, builder style. file_name<T>(self, filename: T) -> Part where T: Into<Cow<'static, str>>,252 pub fn file_name<T>(self, filename: T) -> Part 253 where 254 T: Into<Cow<'static, str>>, 255 { 256 self.with_inner(move |inner| inner.file_name(filename)) 257 } 258 with_inner<F>(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata,259 fn with_inner<F>(self, func: F) -> Self 260 where 261 F: FnOnce(PartMetadata) -> PartMetadata, 262 { 263 Part { 264 meta: func(self.meta), 265 value: self.value, 266 } 267 } 268 } 269 270 impl fmt::Debug for Part { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result271 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 272 let mut dbg = f.debug_struct("Part"); 273 dbg.field("value", &self.value); 274 self.meta.fmt_fields(&mut dbg); 275 dbg.finish() 276 } 277 } 278 279 impl PartProps for Part { value_len(&self) -> Option<u64>280 fn value_len(&self) -> Option<u64> { 281 self.value.len() 282 } 283 metadata(&self) -> &PartMetadata284 fn metadata(&self) -> &PartMetadata { 285 &self.meta 286 } 287 } 288 289 pub(crate) struct Reader { 290 form: Form, 291 active_reader: Option<Box<dyn Read + Send>>, 292 } 293 294 impl fmt::Debug for Reader { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result295 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 296 f.debug_struct("Reader").field("form", &self.form).finish() 297 } 298 } 299 300 impl Reader { new(form: Form) -> Reader301 fn new(form: Form) -> Reader { 302 let mut reader = Reader { 303 form, 304 active_reader: None, 305 }; 306 reader.next_reader(); 307 reader 308 } 309 next_reader(&mut self)310 fn next_reader(&mut self) { 311 self.active_reader = if !self.form.inner.fields.is_empty() { 312 // We need to move out of the vector here because we are consuming the field's reader 313 let (name, field) = self.form.inner.fields.remove(0); 314 let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary())); 315 let header = Cursor::new({ 316 // Try to use cached headers created by compute_length 317 let mut h = if !self.form.inner.computed_headers.is_empty() { 318 self.form.inner.computed_headers.remove(0) 319 } else { 320 self.form 321 .inner 322 .percent_encoding 323 .encode_headers(&name, field.metadata()) 324 }; 325 h.extend_from_slice(b"\r\n\r\n"); 326 h 327 }); 328 let reader = boundary 329 .chain(header) 330 .chain(field.value.into_reader()) 331 .chain(Cursor::new("\r\n")); 332 // According to https://tools.ietf.org/html/rfc2046#section-5.1.1 333 // the very last field has a special boundary 334 if !self.form.inner.fields.is_empty() { 335 Some(Box::new(reader)) 336 } else { 337 Some(Box::new(reader.chain(Cursor::new(format!( 338 "--{}--\r\n", 339 self.form.boundary() 340 ))))) 341 } 342 } else { 343 None 344 } 345 } 346 } 347 348 impl Read for Reader { read(&mut self, buf: &mut [u8]) -> io::Result<usize>349 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { 350 let mut total_bytes_read = 0usize; 351 let mut last_read_bytes; 352 loop { 353 match self.active_reader { 354 Some(ref mut reader) => { 355 last_read_bytes = reader.read(&mut buf[total_bytes_read..])?; 356 total_bytes_read += last_read_bytes; 357 if total_bytes_read == buf.len() { 358 return Ok(total_bytes_read); 359 } 360 } 361 None => return Ok(total_bytes_read), 362 }; 363 if last_read_bytes == 0 && !buf.is_empty() { 364 self.next_reader(); 365 } 366 } 367 } 368 } 369 370 #[cfg(test)] 371 mod tests { 372 use super::*; 373 374 #[test] form_empty()375 fn form_empty() { 376 let mut output = Vec::new(); 377 let mut form = Form::new(); 378 let length = form.compute_length(); 379 form.reader().read_to_end(&mut output).unwrap(); 380 assert_eq!(output, b""); 381 assert_eq!(length.unwrap(), 0); 382 } 383 384 #[test] read_to_end()385 fn read_to_end() { 386 let mut output = Vec::new(); 387 let mut form = Form::new() 388 .part("reader1", Part::reader(std::io::empty())) 389 .part("key1", Part::text("value1")) 390 .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) 391 .part("reader2", Part::reader(std::io::empty())) 392 .part("key3", Part::text("value3").file_name("filename")); 393 form.inner.boundary = "boundary".to_string(); 394 let length = form.compute_length(); 395 let expected = 396 "--boundary\r\n\ 397 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ 398 \r\n\ 399 --boundary\r\n\ 400 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ 401 value1\r\n\ 402 --boundary\r\n\ 403 Content-Disposition: form-data; name=\"key2\"\r\n\ 404 Content-Type: image/bmp\r\n\r\n\ 405 value2\r\n\ 406 --boundary\r\n\ 407 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ 408 \r\n\ 409 --boundary\r\n\ 410 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ 411 value3\r\n--boundary--\r\n"; 412 form.reader().read_to_end(&mut output).unwrap(); 413 // These prints are for debug purposes in case the test fails 414 println!( 415 "START REAL\n{}\nEND REAL", 416 std::str::from_utf8(&output).unwrap() 417 ); 418 println!("START EXPECTED\n{}\nEND EXPECTED", expected); 419 assert_eq!(std::str::from_utf8(&output).unwrap(), expected); 420 assert!(length.is_none()); 421 } 422 423 #[test] read_to_end_with_length()424 fn read_to_end_with_length() { 425 let mut output = Vec::new(); 426 let mut form = Form::new() 427 .text("key1", "value1") 428 .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) 429 .part("key3", Part::text("value3").file_name("filename")); 430 form.inner.boundary = "boundary".to_string(); 431 let length = form.compute_length(); 432 let expected = 433 "--boundary\r\n\ 434 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ 435 value1\r\n\ 436 --boundary\r\n\ 437 Content-Disposition: form-data; name=\"key2\"\r\n\ 438 Content-Type: image/bmp\r\n\r\n\ 439 value2\r\n\ 440 --boundary\r\n\ 441 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ 442 value3\r\n--boundary--\r\n"; 443 form.reader().read_to_end(&mut output).unwrap(); 444 // These prints are for debug purposes in case the test fails 445 println!( 446 "START REAL\n{}\nEND REAL", 447 std::str::from_utf8(&output).unwrap() 448 ); 449 println!("START EXPECTED\n{}\nEND EXPECTED", expected); 450 assert_eq!(std::str::from_utf8(&output).unwrap(), expected); 451 assert_eq!(length.unwrap(), expected.len() as u64); 452 } 453 454 #[test] read_to_end_with_header()455 fn read_to_end_with_header() { 456 let mut output = Vec::new(); 457 let mut part = Part::text("value2").mime(mime::IMAGE_BMP); 458 part.meta.headers.insert("Hdr3", "/a/b/c".parse().unwrap()); 459 let mut form = Form::new().part("key2", part); 460 form.inner.boundary = "boundary".to_string(); 461 let expected = "--boundary\r\n\ 462 Content-Disposition: form-data; name=\"key2\"\r\n\ 463 Content-Type: image/bmp\r\n\ 464 hdr3: /a/b/c\r\n\ 465 \r\n\ 466 value2\r\n\ 467 --boundary--\r\n"; 468 form.reader().read_to_end(&mut output).unwrap(); 469 // These prints are for debug purposes in case the test fails 470 println!( 471 "START REAL\n{}\nEND REAL", 472 std::str::from_utf8(&output).unwrap() 473 ); 474 println!("START EXPECTED\n{}\nEND EXPECTED", expected); 475 assert_eq!(std::str::from_utf8(&output).unwrap(), expected); 476 } 477 } 478