slumber_reqwest/blocking/
multipart.rs1use std::borrow::Cow;
40use std::fmt;
41use std::fs::File;
42use std::io::{self, Cursor, Read};
43use std::path::Path;
44
45use mime_guess::{self, Mime};
46
47use super::Body;
48use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
49use crate::header::HeaderMap;
50
51pub struct Form {
53 inner: FormParts<Part>,
54}
55
56pub struct Part {
58 meta: PartMetadata,
59 value: Body,
60}
61
62impl Default for Form {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl Form {
69 pub fn new() -> Form {
71 Form {
72 inner: FormParts::new(),
73 }
74 }
75
76 #[inline]
78 pub fn boundary(&self) -> &str {
79 self.inner.boundary()
80 }
81
82 pub fn set_boundary(&mut self, boundary: impl Into<String>) {
90 self.inner.boundary = boundary.into();
91 }
92
93 pub fn text<T, U>(self, name: T, value: U) -> Form
103 where
104 T: Into<Cow<'static, str>>,
105 U: Into<Cow<'static, str>>,
106 {
107 self.part(name, Part::text(value))
108 }
109
110 pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
128 where
129 T: Into<Cow<'static, str>>,
130 U: AsRef<Path>,
131 {
132 Ok(self.part(name, Part::file(path)?))
133 }
134
135 pub fn part<T>(self, name: T, part: Part) -> Form
137 where
138 T: Into<Cow<'static, str>>,
139 {
140 self.with_inner(move |inner| inner.part(name, part))
141 }
142
143 pub fn percent_encode_path_segment(self) -> Form {
145 self.with_inner(|inner| inner.percent_encode_path_segment())
146 }
147
148 pub fn percent_encode_attr_chars(self) -> Form {
150 self.with_inner(|inner| inner.percent_encode_attr_chars())
151 }
152
153 pub fn percent_encode_noop(self) -> Form {
155 self.with_inner(|inner| inner.percent_encode_noop())
156 }
157
158 pub(crate) fn reader(self) -> Reader {
159 Reader::new(self)
160 }
161
162 pub fn into_reader(self) -> impl Read {
164 self.reader()
165 }
166
167 pub(crate) fn compute_length(&mut self) -> Option<u64> {
171 self.inner.compute_length()
172 }
173
174 fn with_inner<F>(self, func: F) -> Self
175 where
176 F: FnOnce(FormParts<Part>) -> FormParts<Part>,
177 {
178 Form {
179 inner: func(self.inner),
180 }
181 }
182}
183
184impl fmt::Debug for Form {
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186 self.inner.fmt_fields("Form", f)
187 }
188}
189
190impl Part {
191 pub fn text<T>(value: T) -> Part
193 where
194 T: Into<Cow<'static, str>>,
195 {
196 let body = match value.into() {
197 Cow::Borrowed(slice) => Body::from(slice),
198 Cow::Owned(string) => Body::from(string),
199 };
200 Part::new(body)
201 }
202
203 pub fn bytes<T>(value: T) -> Part
205 where
206 T: Into<Cow<'static, [u8]>>,
207 {
208 let body = match value.into() {
209 Cow::Borrowed(slice) => Body::from(slice),
210 Cow::Owned(vec) => Body::from(vec),
211 };
212 Part::new(body)
213 }
214
215 pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
219 Part::new(Body::new(value))
220 }
221
222 pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
226 Part::new(Body::sized(value, length))
227 }
228
229 pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
235 let path = path.as_ref();
236 let file_name = path
237 .file_name()
238 .map(|filename| filename.to_string_lossy().into_owned());
239 let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
240 let mime = mime_guess::from_ext(ext).first_or_octet_stream();
241 let file = File::open(path)?;
242 let field = Part::new(Body::from(file)).mime(mime);
243
244 Ok(if let Some(file_name) = file_name {
245 field.file_name(file_name)
246 } else {
247 field
248 })
249 }
250
251 fn new(value: Body) -> Part {
252 Part {
253 meta: PartMetadata::new(),
254 value,
255 }
256 }
257
258 pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
260 Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
261 }
262
263 fn mime(self, mime: Mime) -> Part {
265 self.with_inner(move |inner| inner.mime(mime))
266 }
267
268 pub fn file_name<T>(self, filename: T) -> Part
270 where
271 T: Into<Cow<'static, str>>,
272 {
273 self.with_inner(move |inner| inner.file_name(filename))
274 }
275
276 pub fn headers(self, headers: HeaderMap) -> Part {
278 self.with_inner(move |inner| inner.headers(headers))
279 }
280
281 fn with_inner<F>(self, func: F) -> Self
282 where
283 F: FnOnce(PartMetadata) -> PartMetadata,
284 {
285 Part {
286 meta: func(self.meta),
287 value: self.value,
288 }
289 }
290}
291
292impl fmt::Debug for Part {
293 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
294 let mut dbg = f.debug_struct("Part");
295 dbg.field("value", &self.value);
296 self.meta.fmt_fields(&mut dbg);
297 dbg.finish()
298 }
299}
300
301impl PartProps for Part {
302 fn value_len(&self) -> Option<u64> {
303 self.value.len()
304 }
305
306 fn metadata(&self) -> &PartMetadata {
307 &self.meta
308 }
309}
310
311pub(crate) struct Reader {
312 form: Form,
313 active_reader: Option<Box<dyn Read + Send>>,
314}
315
316impl fmt::Debug for Reader {
317 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
318 f.debug_struct("Reader").field("form", &self.form).finish()
319 }
320}
321
322impl Reader {
323 fn new(form: Form) -> Reader {
324 let mut reader = Reader {
325 form,
326 active_reader: None,
327 };
328 reader.next_reader();
329 reader
330 }
331
332 fn next_reader(&mut self) {
333 self.active_reader = if !self.form.inner.fields.is_empty() {
334 let (name, field) = self.form.inner.fields.remove(0);
336 let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
337 let header = Cursor::new({
338 let mut h = if !self.form.inner.computed_headers.is_empty() {
340 self.form.inner.computed_headers.remove(0)
341 } else {
342 self.form
343 .inner
344 .percent_encoding
345 .encode_headers(&name, field.metadata())
346 };
347 h.extend_from_slice(b"\r\n\r\n");
348 h
349 });
350 let reader = boundary
351 .chain(header)
352 .chain(field.value.into_reader())
353 .chain(Cursor::new("\r\n"));
354 if !self.form.inner.fields.is_empty() {
357 Some(Box::new(reader))
358 } else {
359 Some(Box::new(reader.chain(Cursor::new(format!(
360 "--{}--\r\n",
361 self.form.boundary()
362 )))))
363 }
364 } else {
365 None
366 }
367 }
368}
369
370impl Read for Reader {
371 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
372 let mut total_bytes_read = 0usize;
373 let mut last_read_bytes;
374 loop {
375 match self.active_reader {
376 Some(ref mut reader) => {
377 last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
378 total_bytes_read += last_read_bytes;
379 if total_bytes_read == buf.len() {
380 return Ok(total_bytes_read);
381 }
382 }
383 None => return Ok(total_bytes_read),
384 };
385 if last_read_bytes == 0 && !buf.is_empty() {
386 self.next_reader();
387 }
388 }
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395
396 #[test]
397 fn form_empty() {
398 let mut output = Vec::new();
399 let mut form = Form::new();
400 let length = form.compute_length();
401 form.reader().read_to_end(&mut output).unwrap();
402 assert_eq!(output, b"");
403 assert_eq!(length.unwrap(), 0);
404 }
405
406 #[test]
407 fn read_to_end() {
408 let mut output = Vec::new();
409 let mut form = Form::new()
410 .part("reader1", Part::reader(std::io::empty()))
411 .part("key1", Part::text("value1"))
412 .part(
413 "key2",
414 Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
415 )
416 .part("reader2", Part::reader(std::io::empty()))
417 .part("key3", Part::text("value3").file_name("filename"));
418 form.inner.boundary = "boundary".to_string();
419 let length = form.compute_length();
420 let expected = "--boundary\r\n\
421 Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
422 \r\n\
423 --boundary\r\n\
424 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
425 value1\r\n\
426 --boundary\r\n\
427 Content-Disposition: form-data; name=\"key2\"\r\n\
428 Content-Type: image/bmp\r\n\r\n\
429 value2\r\n\
430 --boundary\r\n\
431 Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
432 \r\n\
433 --boundary\r\n\
434 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
435 value3\r\n--boundary--\r\n";
436 form.reader().read_to_end(&mut output).unwrap();
437 println!(
439 "START REAL\n{}\nEND REAL",
440 std::str::from_utf8(&output).unwrap()
441 );
442 println!("START EXPECTED\n{expected}\nEND EXPECTED");
443 assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
444 assert!(length.is_none());
445 }
446
447 #[test]
448 fn read_to_end_with_length() {
449 let mut output = Vec::new();
450 let mut form = Form::new()
451 .text("key1", "value1")
452 .part(
453 "key2",
454 Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
455 )
456 .part("key3", Part::text("value3").file_name("filename"));
457 form.inner.boundary = "boundary".to_string();
458 let length = form.compute_length();
459 let expected = "--boundary\r\n\
460 Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
461 value1\r\n\
462 --boundary\r\n\
463 Content-Disposition: form-data; name=\"key2\"\r\n\
464 Content-Type: image/bmp\r\n\r\n\
465 value2\r\n\
466 --boundary\r\n\
467 Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
468 value3\r\n--boundary--\r\n";
469 form.reader().read_to_end(&mut output).unwrap();
470 println!(
472 "START REAL\n{}\nEND REAL",
473 std::str::from_utf8(&output).unwrap()
474 );
475 println!("START EXPECTED\n{expected}\nEND EXPECTED");
476 assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
477 assert_eq!(length.unwrap(), expected.len() as u64);
478 }
479
480 #[test]
481 fn read_to_end_with_header() {
482 let mut output = Vec::new();
483 let mut part = Part::text("value2").mime(mime_guess::mime::IMAGE_BMP);
484 let mut headers = HeaderMap::new();
485 headers.insert("Hdr3", "/a/b/c".parse().unwrap());
486 part = part.headers(headers);
487 let mut form = Form::new().part("key2", part);
488 form.inner.boundary = "boundary".to_string();
489 let expected = "--boundary\r\n\
490 Content-Disposition: form-data; name=\"key2\"\r\n\
491 Content-Type: image/bmp\r\n\
492 hdr3: /a/b/c\r\n\
493 \r\n\
494 value2\r\n\
495 --boundary--\r\n";
496 form.reader().read_to_end(&mut output).unwrap();
497 println!(
499 "START REAL\n{}\nEND REAL",
500 std::str::from_utf8(&output).unwrap()
501 );
502 println!("START EXPECTED\n{expected}\nEND EXPECTED");
503 assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
504 }
505}