1 //! multipart/form-data
2 use std::borrow::Cow;
3 use std::fmt;
4 use std::pin::Pin;
5 
6 use bytes::Bytes;
7 use http::HeaderMap;
8 use mime_guess::Mime;
9 use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
10 
11 use futures_core::Stream;
12 use futures_util::{future, stream, StreamExt};
13 
14 use super::Body;
15 
16 /// An async multipart/form-data request.
17 pub struct Form {
18     inner: FormParts<Part>,
19 }
20 
21 /// A field in a multipart form.
22 pub struct Part {
23     meta: PartMetadata,
24     value: Body,
25     body_length: Option<u64>,
26 }
27 
28 pub(crate) struct FormParts<P> {
29     pub(crate) boundary: String,
30     pub(crate) computed_headers: Vec<Vec<u8>>,
31     pub(crate) fields: Vec<(Cow<'static, str>, P)>,
32     pub(crate) percent_encoding: PercentEncoding,
33 }
34 
35 pub(crate) struct PartMetadata {
36     mime: Option<Mime>,
37     file_name: Option<Cow<'static, str>>,
38     pub(crate) headers: HeaderMap,
39 }
40 
41 pub(crate) trait PartProps {
value_len(&self) -> Option<u64>42     fn value_len(&self) -> Option<u64>;
metadata(&self) -> &PartMetadata43     fn metadata(&self) -> &PartMetadata;
44 }
45 
46 // ===== impl Form =====
47 
48 impl Default for Form {
default() -> Self49     fn default() -> Self {
50         Self::new()
51     }
52 }
53 
54 impl Form {
55     /// Creates a new async Form without any content.
new() -> Form56     pub fn new() -> Form {
57         Form {
58             inner: FormParts::new(),
59         }
60     }
61 
62     /// Get the boundary that this form will use.
63     #[inline]
boundary(&self) -> &str64     pub fn boundary(&self) -> &str {
65         self.inner.boundary()
66     }
67 
68     /// Add a data field with supplied name and value.
69     ///
70     /// # Examples
71     ///
72     /// ```
73     /// let form = reqwest::multipart::Form::new()
74     ///     .text("username", "seanmonstar")
75     ///     .text("password", "secret");
76     /// ```
text<T, U>(self, name: T, value: U) -> Form where T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>,77     pub fn text<T, U>(self, name: T, value: U) -> Form
78     where
79         T: Into<Cow<'static, str>>,
80         U: Into<Cow<'static, str>>,
81     {
82         self.part(name, Part::text(value))
83     }
84 
85     /// Adds a customized Part.
part<T>(self, name: T, part: Part) -> Form where T: Into<Cow<'static, str>>,86     pub fn part<T>(self, name: T, part: Part) -> Form
87     where
88         T: Into<Cow<'static, str>>,
89     {
90         self.with_inner(move |inner| inner.part(name, part))
91     }
92 
93     /// Configure this `Form` to percent-encode using the `path-segment` rules.
percent_encode_path_segment(self) -> Form94     pub fn percent_encode_path_segment(self) -> Form {
95         self.with_inner(|inner| inner.percent_encode_path_segment())
96     }
97 
98     /// Configure this `Form` to percent-encode using the `attr-char` rules.
percent_encode_attr_chars(self) -> Form99     pub fn percent_encode_attr_chars(self) -> Form {
100         self.with_inner(|inner| inner.percent_encode_attr_chars())
101     }
102 
103     /// Configure this `Form` to skip percent-encoding
percent_encode_noop(self) -> Form104     pub fn percent_encode_noop(self) -> Form {
105         self.with_inner(|inner| inner.percent_encode_noop())
106     }
107 
108     /// Consume this instance and transform into an instance of Body for use in a request.
stream(mut self) -> Body109     pub(crate) fn stream(mut self) -> Body {
110         if self.inner.fields.is_empty() {
111             return Body::empty();
112         }
113 
114         // create initial part to init reduce chain
115         let (name, part) = self.inner.fields.remove(0);
116         let start = Box::pin(self.part_stream(name, part))
117             as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
118 
119         let fields = self.inner.take_fields();
120         // for each field, chain an additional stream
121         let stream = fields.into_iter().fold(start, |memo, (name, part)| {
122             let part_stream = self.part_stream(name, part);
123             Box::pin(memo.chain(part_stream))
124                 as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
125         });
126         // append special ending boundary
127         let last = stream::once(future::ready(Ok(
128             format!("--{}--\r\n", self.boundary()).into()
129         )));
130         Body::stream(stream.chain(last))
131     }
132 
133     /// Generate a hyper::Body stream for a single Part instance of a Form request.
part_stream<T>( &mut self, name: T, part: Part, ) -> impl Stream<Item = Result<Bytes, crate::Error>> where T: Into<Cow<'static, str>>,134     pub(crate) fn part_stream<T>(
135         &mut self,
136         name: T,
137         part: Part,
138     ) -> impl Stream<Item = Result<Bytes, crate::Error>>
139     where
140         T: Into<Cow<'static, str>>,
141     {
142         // start with boundary
143         let boundary = stream::once(future::ready(Ok(
144             format!("--{}\r\n", self.boundary()).into()
145         )));
146         // append headers
147         let header = stream::once(future::ready(Ok({
148             let mut h = self
149                 .inner
150                 .percent_encoding
151                 .encode_headers(&name.into(), &part.meta);
152             h.extend_from_slice(b"\r\n\r\n");
153             h.into()
154         })));
155         // then append form data followed by terminating CRLF
156         boundary
157             .chain(header)
158             .chain(part.value.into_stream())
159             .chain(stream::once(future::ready(Ok("\r\n".into()))))
160     }
161 
compute_length(&mut self) -> Option<u64>162     pub(crate) fn compute_length(&mut self) -> Option<u64> {
163         self.inner.compute_length()
164     }
165 
with_inner<F>(self, func: F) -> Self where F: FnOnce(FormParts<Part>) -> FormParts<Part>,166     fn with_inner<F>(self, func: F) -> Self
167     where
168         F: FnOnce(FormParts<Part>) -> FormParts<Part>,
169     {
170         Form {
171             inner: func(self.inner),
172         }
173     }
174 }
175 
176 impl fmt::Debug for Form {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result177     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178         self.inner.fmt_fields("Form", f)
179     }
180 }
181 
182 // ===== impl Part =====
183 
184 impl Part {
185     /// Makes a text parameter.
text<T>(value: T) -> Part where T: Into<Cow<'static, str>>,186     pub fn text<T>(value: T) -> Part
187     where
188         T: Into<Cow<'static, str>>,
189     {
190         let body = match value.into() {
191             Cow::Borrowed(slice) => Body::from(slice),
192             Cow::Owned(string) => Body::from(string),
193         };
194         Part::new(body, None)
195     }
196 
197     /// Makes a new parameter from arbitrary bytes.
bytes<T>(value: T) -> Part where T: Into<Cow<'static, [u8]>>,198     pub fn bytes<T>(value: T) -> Part
199     where
200         T: Into<Cow<'static, [u8]>>,
201     {
202         let body = match value.into() {
203             Cow::Borrowed(slice) => Body::from(slice),
204             Cow::Owned(vec) => Body::from(vec),
205         };
206         Part::new(body, None)
207     }
208 
209     /// Makes a new parameter from an arbitrary stream.
stream<T: Into<Body>>(value: T) -> Part210     pub fn stream<T: Into<Body>>(value: T) -> Part {
211         Part::new(value.into(), None)
212     }
213 
214     /// Makes a new parameter from an arbitrary stream with a known length. This is particularly
215     /// useful when adding something like file contents as a stream, where you can know the content
216     /// length beforehand.
stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part217     pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
218         Part::new(value.into(), Some(length))
219     }
220 
new(value: Body, body_length: Option<u64>) -> Part221     fn new(value: Body, body_length: Option<u64>) -> Part {
222         Part {
223             meta: PartMetadata::new(),
224             value,
225             body_length,
226         }
227     }
228 
229     /// Tries to set the mime of this part.
mime_str(self, mime: &str) -> crate::Result<Part>230     pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
231         Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
232     }
233 
234     // Re-export when mime 0.4 is available, with split MediaType/MediaRange.
mime(self, mime: Mime) -> Part235     fn mime(self, mime: Mime) -> Part {
236         self.with_inner(move |inner| inner.mime(mime))
237     }
238 
239     /// Sets the filename, builder style.
file_name<T>(self, filename: T) -> Part where T: Into<Cow<'static, str>>,240     pub fn file_name<T>(self, filename: T) -> Part
241     where
242         T: Into<Cow<'static, str>>,
243     {
244         self.with_inner(move |inner| inner.file_name(filename))
245     }
246 
with_inner<F>(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata,247     fn with_inner<F>(self, func: F) -> Self
248     where
249         F: FnOnce(PartMetadata) -> PartMetadata,
250     {
251         Part {
252             meta: func(self.meta),
253             ..self
254         }
255     }
256 }
257 
258 impl fmt::Debug for Part {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result259     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260         let mut dbg = f.debug_struct("Part");
261         dbg.field("value", &self.value);
262         self.meta.fmt_fields(&mut dbg);
263         dbg.finish()
264     }
265 }
266 
267 impl PartProps for Part {
value_len(&self) -> Option<u64>268     fn value_len(&self) -> Option<u64> {
269         if self.body_length.is_some() {
270             self.body_length
271         } else {
272             self.value.content_length()
273         }
274     }
275 
metadata(&self) -> &PartMetadata276     fn metadata(&self) -> &PartMetadata {
277         &self.meta
278     }
279 }
280 
281 // ===== impl FormParts =====
282 
283 impl<P: PartProps> FormParts<P> {
new() -> Self284     pub(crate) fn new() -> Self {
285         FormParts {
286             boundary: gen_boundary(),
287             computed_headers: Vec::new(),
288             fields: Vec::new(),
289             percent_encoding: PercentEncoding::PathSegment,
290         }
291     }
292 
boundary(&self) -> &str293     pub(crate) fn boundary(&self) -> &str {
294         &self.boundary
295     }
296 
297     /// Adds a customized Part.
part<T>(mut self, name: T, part: P) -> Self where T: Into<Cow<'static, str>>,298     pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
299     where
300         T: Into<Cow<'static, str>>,
301     {
302         self.fields.push((name.into(), part));
303         self
304     }
305 
306     /// Configure this `Form` to percent-encode using the `path-segment` rules.
percent_encode_path_segment(mut self) -> Self307     pub(crate) fn percent_encode_path_segment(mut self) -> Self {
308         self.percent_encoding = PercentEncoding::PathSegment;
309         self
310     }
311 
312     /// Configure this `Form` to percent-encode using the `attr-char` rules.
percent_encode_attr_chars(mut self) -> Self313     pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
314         self.percent_encoding = PercentEncoding::AttrChar;
315         self
316     }
317 
318     /// Configure this `Form` to skip percent-encoding
percent_encode_noop(mut self) -> Self319     pub(crate) fn percent_encode_noop(mut self) -> Self {
320         self.percent_encoding = PercentEncoding::NoOp;
321         self
322     }
323 
324     // If predictable, computes the length the request will have
325     // The length should be preditable if only String and file fields have been added,
326     // but not if a generic reader has been added;
compute_length(&mut self) -> Option<u64>327     pub(crate) fn compute_length(&mut self) -> Option<u64> {
328         let mut length = 0u64;
329         for &(ref name, ref field) in self.fields.iter() {
330             match field.value_len() {
331                 Some(value_length) => {
332                     // We are constructing the header just to get its length. To not have to
333                     // construct it again when the request is sent we cache these headers.
334                     let header = self.percent_encoding.encode_headers(name, field.metadata());
335                     let header_length = header.len();
336                     self.computed_headers.push(header);
337                     // The additions mimic the format string out of which the field is constructed
338                     // in Reader. Not the cleanest solution because if that format string is
339                     // ever changed then this formula needs to be changed too which is not an
340                     // obvious dependency in the code.
341                     length += 2
342                         + self.boundary().len() as u64
343                         + 2
344                         + header_length as u64
345                         + 4
346                         + value_length
347                         + 2
348                 }
349                 _ => return None,
350             }
351         }
352         // If there is a at least one field there is a special boundary for the very last field.
353         if !self.fields.is_empty() {
354             length += 2 + self.boundary().len() as u64 + 4
355         }
356         Some(length)
357     }
358 
359     /// Take the fields vector of this instance, replacing with an empty vector.
take_fields(&mut self) -> Vec<(Cow<'static, str>, P)>360     fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
361         std::mem::replace(&mut self.fields, Vec::new())
362     }
363 }
364 
365 impl<P: fmt::Debug> FormParts<P> {
fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result366     pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
367         f.debug_struct(ty_name)
368             .field("boundary", &self.boundary)
369             .field("parts", &self.fields)
370             .finish()
371     }
372 }
373 
374 // ===== impl PartMetadata =====
375 
376 impl PartMetadata {
new() -> Self377     pub(crate) fn new() -> Self {
378         PartMetadata {
379             mime: None,
380             file_name: None,
381             headers: HeaderMap::default(),
382         }
383     }
384 
mime(mut self, mime: Mime) -> Self385     pub(crate) fn mime(mut self, mime: Mime) -> Self {
386         self.mime = Some(mime);
387         self
388     }
389 
file_name<T>(mut self, filename: T) -> Self where T: Into<Cow<'static, str>>,390     pub(crate) fn file_name<T>(mut self, filename: T) -> Self
391     where
392         T: Into<Cow<'static, str>>,
393     {
394         self.file_name = Some(filename.into());
395         self
396     }
397 }
398 
399 impl PartMetadata {
fmt_fields<'f, 'fa, 'fb>( &self, debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, ) -> &'f mut fmt::DebugStruct<'fa, 'fb>400     pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
401         &self,
402         debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
403     ) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
404         debug_struct
405             .field("mime", &self.mime)
406             .field("file_name", &self.file_name)
407             .field("headers", &self.headers)
408     }
409 }
410 
411 // https://url.spec.whatwg.org/#fragment-percent-encode-set
412 const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
413     .add(b' ')
414     .add(b'"')
415     .add(b'<')
416     .add(b'>')
417     .add(b'`');
418 
419 // https://url.spec.whatwg.org/#path-percent-encode-set
420 const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
421 
422 const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
423 
424 // https://tools.ietf.org/html/rfc8187#section-3.2.1
425 const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
426     .remove(b'!')
427     .remove(b'#')
428     .remove(b'$')
429     .remove(b'&')
430     .remove(b'+')
431     .remove(b'-')
432     .remove(b'.')
433     .remove(b'^')
434     .remove(b'_')
435     .remove(b'`')
436     .remove(b'|')
437     .remove(b'~');
438 
439 pub(crate) enum PercentEncoding {
440     PathSegment,
441     AttrChar,
442     NoOp,
443 }
444 
445 impl PercentEncoding {
encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8>446     pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
447         let s = format!(
448             "Content-Disposition: form-data; {}{}{}",
449             self.format_parameter("name", name),
450             match field.file_name {
451                 Some(ref file_name) => format!("; {}", self.format_filename(file_name)),
452                 None => String::new(),
453             },
454             match field.mime {
455                 Some(ref mime) => format!("\r\nContent-Type: {}", mime),
456                 None => "".to_string(),
457             },
458         );
459         field
460             .headers
461             .iter()
462             .fold(s.into_bytes(), |mut header, (k, v)| {
463                 header.extend_from_slice(b"\r\n");
464                 header.extend_from_slice(k.as_str().as_bytes());
465                 header.extend_from_slice(b": ");
466                 header.extend_from_slice(v.as_bytes());
467                 header
468             })
469     }
470 
471     // According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
472     // See https://github.com/seanmonstar/reqwest/issues/419.
format_filename(&self, filename: &str) -> String473     fn format_filename(&self, filename: &str) -> String {
474         let legal_filename = filename
475             .replace("\\", "\\\\")
476             .replace("\"", "\\\"")
477             .replace("\r", "\\\r")
478             .replace("\n", "\\\n");
479         format!("filename=\"{}\"", legal_filename)
480     }
481 
format_parameter(&self, name: &str, value: &str) -> String482     fn format_parameter(&self, name: &str, value: &str) -> String {
483         let legal_value = match *self {
484             PercentEncoding::PathSegment => {
485                 percent_encoding::utf8_percent_encode(value, PATH_SEGMENT_ENCODE_SET).to_string()
486             }
487             PercentEncoding::AttrChar => {
488                 percent_encoding::utf8_percent_encode(value, ATTR_CHAR_ENCODE_SET).to_string()
489             }
490             PercentEncoding::NoOp => value.to_string(),
491         };
492         if value.len() == legal_value.len() {
493             // nothing has been percent encoded
494             format!("{}=\"{}\"", name, value)
495         } else {
496             // something has been percent encoded
497             format!("{}*=utf-8''{}", name, legal_value)
498         }
499     }
500 }
501 
gen_boundary() -> String502 fn gen_boundary() -> String {
503     use crate::util::fast_random as random;
504 
505     let a = random();
506     let b = random();
507     let c = random();
508     let d = random();
509 
510     format!("{:016x}-{:016x}-{:016x}-{:016x}", a, b, c, d)
511 }
512 
513 #[cfg(test)]
514 mod tests {
515     use super::*;
516     use futures_util::TryStreamExt;
517     use futures_util::{future, stream};
518     use tokio::{self, runtime};
519 
520     #[test]
form_empty()521     fn form_empty() {
522         let form = Form::new();
523 
524         let rt = runtime::Builder::new_current_thread()
525             .enable_all()
526             .build()
527             .expect("new rt");
528         let body = form.stream().into_stream();
529         let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
530 
531         let out = rt.block_on(s);
532         assert!(out.unwrap().is_empty());
533     }
534 
535     #[test]
stream_to_end()536     fn stream_to_end() {
537         let mut form = Form::new()
538             .part(
539                 "reader1",
540                 Part::stream(Body::stream(stream::once(future::ready::<
541                     Result<String, crate::Error>,
542                 >(Ok(
543                     "part1".to_owned()
544                 ))))),
545             )
546             .part("key1", Part::text("value1"))
547             .part("key2", Part::text("value2").mime(mime::IMAGE_BMP))
548             .part(
549                 "reader2",
550                 Part::stream(Body::stream(stream::once(future::ready::<
551                     Result<String, crate::Error>,
552                 >(Ok(
553                     "part2".to_owned()
554                 ))))),
555             )
556             .part("key3", Part::text("value3").file_name("filename"));
557         form.inner.boundary = "boundary".to_string();
558         let expected = "--boundary\r\n\
559              Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
560              part1\r\n\
561              --boundary\r\n\
562              Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
563              value1\r\n\
564              --boundary\r\n\
565              Content-Disposition: form-data; name=\"key2\"\r\n\
566              Content-Type: image/bmp\r\n\r\n\
567              value2\r\n\
568              --boundary\r\n\
569              Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
570              part2\r\n\
571              --boundary\r\n\
572              Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
573              value3\r\n--boundary--\r\n";
574         let rt = runtime::Builder::new_current_thread()
575             .enable_all()
576             .build()
577             .expect("new rt");
578         let body = form.stream().into_stream();
579         let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
580 
581         let out = rt.block_on(s).unwrap();
582         // These prints are for debug purposes in case the test fails
583         println!(
584             "START REAL\n{}\nEND REAL",
585             std::str::from_utf8(&out).unwrap()
586         );
587         println!("START EXPECTED\n{}\nEND EXPECTED", expected);
588         assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
589     }
590 
591     #[test]
stream_to_end_with_header()592     fn stream_to_end_with_header() {
593         let mut part = Part::text("value2").mime(mime::IMAGE_BMP);
594         part.meta.headers.insert("Hdr3", "/a/b/c".parse().unwrap());
595         let mut form = Form::new().part("key2", part);
596         form.inner.boundary = "boundary".to_string();
597         let expected = "--boundary\r\n\
598                         Content-Disposition: form-data; name=\"key2\"\r\n\
599                         Content-Type: image/bmp\r\n\
600                         hdr3: /a/b/c\r\n\
601                         \r\n\
602                         value2\r\n\
603                         --boundary--\r\n";
604         let rt = runtime::Builder::new_current_thread()
605             .enable_all()
606             .build()
607             .expect("new rt");
608         let body = form.stream().into_stream();
609         let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
610 
611         let out = rt.block_on(s).unwrap();
612         // These prints are for debug purposes in case the test fails
613         println!(
614             "START REAL\n{}\nEND REAL",
615             std::str::from_utf8(&out).unwrap()
616         );
617         println!("START EXPECTED\n{}\nEND EXPECTED", expected);
618         assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
619     }
620 
621     #[test]
correct_content_length()622     fn correct_content_length() {
623         // Setup an arbitrary data stream
624         let stream_data = b"just some stream data";
625         let stream_len = stream_data.len();
626         let stream_data = stream_data
627             .chunks(3)
628             .map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
629         let the_stream = futures_util::stream::iter(stream_data);
630 
631         let bytes_data = b"some bytes data".to_vec();
632         let bytes_len = bytes_data.len();
633 
634         let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
635         let body_part = Part::bytes(bytes_data);
636 
637         // A simple check to make sure we get the configured body length
638         assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
639 
640         // Make sure it delegates to the underlying body if length is not specified
641         assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
642     }
643 
644     #[test]
header_percent_encoding()645     fn header_percent_encoding() {
646         let name = "start%'\"\r\nßend";
647         let field = Part::text("");
648 
649         assert_eq!(
650             PercentEncoding::PathSegment.encode_headers(name, &field.meta),
651             &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
652         );
653 
654         assert_eq!(
655             PercentEncoding::AttrChar.encode_headers(name, &field.meta),
656             &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
657         );
658     }
659 }
660