wreq-rb 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1922 -397
  3. data/LICENSE +203 -0
  4. data/README.md +19 -15
  5. data/ext/wreq_rb/Cargo.toml +4 -6
  6. data/ext/wreq_rb/src/client.rs +41 -48
  7. data/lib/wreq-rb/version.rb +1 -1
  8. data/patches/0001-add-transfer-size-tracking.patch +76 -67
  9. data/vendor/wreq/Cargo.toml +119 -71
  10. data/vendor/wreq/README.md +25 -20
  11. data/vendor/wreq/bench/http1.rs +25 -0
  12. data/vendor/wreq/bench/http1_over_tls.rs +25 -0
  13. data/vendor/wreq/bench/http2.rs +25 -0
  14. data/vendor/wreq/bench/http2_over_tls.rs +25 -0
  15. data/vendor/wreq/bench/support/bench.rs +91 -0
  16. data/vendor/wreq/bench/support/client.rs +217 -0
  17. data/vendor/wreq/bench/support/server.rs +188 -0
  18. data/vendor/wreq/bench/support.rs +56 -0
  19. data/vendor/wreq/examples/cert_store.rs +4 -4
  20. data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
  21. data/vendor/wreq/examples/http2_websocket.rs +2 -2
  22. data/vendor/wreq/examples/keylog.rs +3 -3
  23. data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
  24. data/vendor/wreq/examples/rt.rs +23 -0
  25. data/vendor/wreq/src/client/body.rs +23 -61
  26. data/vendor/wreq/src/client/emulate.rs +119 -0
  27. data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
  28. data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
  29. data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
  30. data/vendor/wreq/src/client/layer/config.rs +27 -6
  31. data/vendor/wreq/src/client/layer/decoder.rs +9 -4
  32. data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
  33. data/vendor/wreq/src/client/layer/redirect.rs +4 -5
  34. data/vendor/wreq/src/client/layer/retry.rs +8 -5
  35. data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
  36. data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
  37. data/vendor/wreq/src/client/layer/timeout.rs +24 -74
  38. data/vendor/wreq/src/client/layer.rs +1 -2
  39. data/vendor/wreq/src/client/multipart.rs +137 -154
  40. data/vendor/wreq/src/client/request.rs +202 -118
  41. data/vendor/wreq/src/client/response.rs +46 -45
  42. data/vendor/wreq/src/client/upgrade.rs +15 -0
  43. data/vendor/wreq/src/client/ws.rs +73 -25
  44. data/vendor/wreq/src/client.rs +1655 -17
  45. data/vendor/wreq/src/config.rs +11 -11
  46. data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
  47. data/vendor/wreq/src/conn/descriptor.rs +143 -0
  48. data/vendor/wreq/src/conn/http.rs +484 -0
  49. data/vendor/wreq/src/conn/net/io.rs +75 -0
  50. data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
  51. data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
  52. data/vendor/wreq/src/conn/net/tcp.rs +561 -0
  53. data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
  54. data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
  55. data/vendor/wreq/src/conn/net/uds.rs +11 -0
  56. data/vendor/wreq/src/conn/net.rs +130 -0
  57. data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
  58. data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
  59. data/vendor/wreq/src/conn/tls_info.rs +47 -0
  60. data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
  61. data/vendor/wreq/src/cookie.rs +302 -142
  62. data/vendor/wreq/src/dns/gai/compio.rs +77 -0
  63. data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
  64. data/vendor/wreq/src/dns/gai.rs +14 -164
  65. data/vendor/wreq/src/dns/hickory.rs +16 -23
  66. data/vendor/wreq/src/dns/resolve.rs +7 -41
  67. data/vendor/wreq/src/dns.rs +90 -7
  68. data/vendor/wreq/src/error.rs +57 -31
  69. data/vendor/wreq/src/ext.rs +25 -0
  70. data/vendor/wreq/src/group.rs +211 -0
  71. data/vendor/wreq/src/header.rs +100 -112
  72. data/vendor/wreq/src/lib.rs +124 -73
  73. data/vendor/wreq/src/proxy.rs +6 -20
  74. data/vendor/wreq/src/redirect.rs +1 -1
  75. data/vendor/wreq/src/rt.rs +208 -0
  76. data/vendor/wreq/src/sync.rs +97 -98
  77. data/vendor/wreq/src/tls/compress.rs +124 -0
  78. data/vendor/wreq/src/tls/conn/ext.rs +54 -45
  79. data/vendor/wreq/src/tls/conn/service.rs +14 -18
  80. data/vendor/wreq/src/tls/conn.rs +169 -241
  81. data/vendor/wreq/src/tls/keylog.rs +68 -5
  82. data/vendor/wreq/src/tls/session.rs +205 -0
  83. data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
  84. data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
  85. data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
  86. data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
  87. data/vendor/wreq/src/tls.rs +489 -25
  88. data/vendor/wreq/src/trace.rs +0 -12
  89. data/vendor/wreq/src/util.rs +1 -1
  90. data/vendor/wreq/tests/badssl.rs +10 -10
  91. data/vendor/wreq/tests/client.rs +3 -9
  92. data/vendor/wreq/tests/cookie.rs +6 -8
  93. data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
  94. data/vendor/wreq/tests/multipart.rs +43 -1
  95. data/vendor/wreq/tests/proxy.rs +1 -1
  96. data/vendor/wreq/tests/support/layer.rs +1 -0
  97. metadata +49 -71
  98. data/patches/0002-add-cancel-connections.patch +0 -181
  99. data/vendor/wreq/src/client/conn/conn.rs +0 -231
  100. data/vendor/wreq/src/client/conn/http.rs +0 -1023
  101. data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
  102. data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
  103. data/vendor/wreq/src/client/core/body/length.rs +0 -118
  104. data/vendor/wreq/src/client/core/body.rs +0 -34
  105. data/vendor/wreq/src/client/core/common/buf.rs +0 -149
  106. data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
  107. data/vendor/wreq/src/client/core/common/watch.rs +0 -76
  108. data/vendor/wreq/src/client/core/common.rs +0 -3
  109. data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
  110. data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
  111. data/vendor/wreq/src/client/core/conn.rs +0 -11
  112. data/vendor/wreq/src/client/core/dispatch.rs +0 -299
  113. data/vendor/wreq/src/client/core/error.rs +0 -435
  114. data/vendor/wreq/src/client/core/ext.rs +0 -201
  115. data/vendor/wreq/src/client/core/http1.rs +0 -178
  116. data/vendor/wreq/src/client/core/http2.rs +0 -483
  117. data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
  118. data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
  119. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
  120. data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
  121. data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
  122. data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
  123. data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
  124. data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
  125. data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
  126. data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
  127. data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
  128. data/vendor/wreq/src/client/core/proto.rs +0 -58
  129. data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
  130. data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
  131. data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
  132. data/vendor/wreq/src/client/core/rt.rs +0 -25
  133. data/vendor/wreq/src/client/core/upgrade.rs +0 -267
  134. data/vendor/wreq/src/client/core.rs +0 -16
  135. data/vendor/wreq/src/client/emulation.rs +0 -161
  136. data/vendor/wreq/src/client/http/client/error.rs +0 -142
  137. data/vendor/wreq/src/client/http/client/exec.rs +0 -29
  138. data/vendor/wreq/src/client/http/client/extra.rs +0 -77
  139. data/vendor/wreq/src/client/http/client/util.rs +0 -104
  140. data/vendor/wreq/src/client/http.rs +0 -1629
  141. data/vendor/wreq/src/client/layer/config/options.rs +0 -156
  142. data/vendor/wreq/src/client/layer/cookie.rs +0 -161
  143. data/vendor/wreq/src/hash.rs +0 -143
  144. data/vendor/wreq/src/tls/conn/cache.rs +0 -123
  145. data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
  146. data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
  147. data/vendor/wreq/src/tls/options.rs +0 -464
  148. /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
  149. /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
  150. /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
@@ -4,19 +4,22 @@ use std::{borrow::Cow, pin::Pin};
4
4
 
5
5
  use bytes::Bytes;
6
6
  use futures_util::{Stream, StreamExt, future, stream};
7
+ use http::header::HeaderMap;
7
8
  use http_body_util::BodyExt;
8
9
  use mime_guess::Mime;
9
10
  use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
10
- #[cfg(feature = "stream")]
11
+ #[cfg(all(feature = "tokio-rt", feature = "stream"))]
11
12
  use {std::io, std::path::Path, tokio::fs::File};
12
13
 
13
14
  use super::Body;
14
- use crate::header::HeaderMap;
15
15
 
16
16
  /// An async multipart/form-data request.
17
17
  #[derive(Debug)]
18
18
  pub struct Form {
19
- inner: FormParts<Part>,
19
+ boundary: Cow<'static, str>,
20
+ computed_headers: Vec<Vec<u8>>,
21
+ fields: Vec<(Cow<'static, str>, Part)>,
22
+ percent_encoding: PercentEncoding,
20
23
  }
21
24
 
22
25
  /// A field in a multipart form.
@@ -28,23 +31,10 @@ pub struct Part {
28
31
  }
29
32
 
30
33
  #[derive(Debug)]
31
- pub(crate) struct FormParts<P> {
32
- pub(crate) boundary: String,
33
- pub(crate) computed_headers: Vec<Vec<u8>>,
34
- pub(crate) fields: Vec<(Cow<'static, str>, P)>,
35
- pub(crate) percent_encoding: PercentEncoding,
36
- }
37
-
38
- #[derive(Debug)]
39
- pub(crate) struct PartMetadata {
34
+ struct PartMetadata {
40
35
  mime: Option<Mime>,
41
36
  file_name: Option<Cow<'static, str>>,
42
- pub(crate) headers: HeaderMap,
43
- }
44
-
45
- pub(crate) trait PartProps {
46
- fn value_len(&self) -> Option<u64>;
47
- fn metadata(&self) -> &PartMetadata;
37
+ headers: HeaderMap,
48
38
  }
49
39
 
50
40
  // ===== impl Form =====
@@ -58,15 +48,29 @@ impl Default for Form {
58
48
  impl Form {
59
49
  /// Creates a new async Form without any content.
60
50
  pub fn new() -> Form {
51
+ Form::with_boundary(gen_boundary())
52
+ }
53
+
54
+ /// Creates a new async Form with a custom boundary.
55
+ ///
56
+ /// **Setting a custom boundary incurs significant risk of generating
57
+ /// corrupted bodies.** Only use this if you need it and you understand the
58
+ /// risk!
59
+ pub fn with_boundary<S>(boundary: S) -> Form
60
+ where
61
+ S: Into<Cow<'static, str>>,
62
+ {
61
63
  Form {
62
- inner: FormParts::new(),
64
+ boundary: boundary.into(),
65
+ computed_headers: Vec::new(),
66
+ fields: Vec::new(),
67
+ percent_encoding: PercentEncoding::PathSegment,
63
68
  }
64
69
  }
65
70
 
66
71
  /// Get the boundary that this form will use.
67
- #[inline]
68
72
  pub fn boundary(&self) -> &str {
69
- self.inner.boundary()
73
+ &self.boundary
70
74
  }
71
75
 
72
76
  /// Add a data field with supplied name and value.
@@ -104,8 +108,8 @@ impl Form {
104
108
  /// # Errors
105
109
  ///
106
110
  /// Errors when the file cannot be opened.
107
- #[cfg(feature = "stream")]
108
- #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
111
+ #[cfg(all(feature = "tokio-rt", feature = "stream"))]
112
+ #[cfg_attr(docsrs, doc(cfg(all(feature = "tokio-rt", feature = "stream"))))]
109
113
  pub async fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
110
114
  where
111
115
  T: Into<Cow<'static, str>>,
@@ -115,31 +119,35 @@ impl Form {
115
119
  }
116
120
 
117
121
  /// Adds a customized Part.
118
- pub fn part<T>(self, name: T, part: Part) -> Form
122
+ pub fn part<T>(mut self, name: T, part: Part) -> Form
119
123
  where
120
124
  T: Into<Cow<'static, str>>,
121
125
  {
122
- self.with_inner(move |inner| inner.part(name, part))
126
+ self.fields.push((name.into(), part));
127
+ self
123
128
  }
124
129
 
125
130
  /// Configure this `Form` to percent-encode using the `path-segment` rules.
126
- pub fn percent_encode_path_segment(self) -> Form {
127
- self.with_inner(|inner| inner.percent_encode_path_segment())
131
+ pub fn percent_encode_path_segment(mut self) -> Form {
132
+ self.percent_encoding = PercentEncoding::PathSegment;
133
+ self
128
134
  }
129
135
 
130
136
  /// Configure this `Form` to percent-encode using the `attr-char` rules.
131
- pub fn percent_encode_attr_chars(self) -> Form {
132
- self.with_inner(|inner| inner.percent_encode_attr_chars())
137
+ pub fn percent_encode_attr_chars(mut self) -> Form {
138
+ self.percent_encoding = PercentEncoding::AttrChar;
139
+ self
133
140
  }
134
141
 
135
142
  /// Configure this `Form` to skip percent-encoding
136
- pub fn percent_encode_noop(self) -> Form {
137
- self.with_inner(|inner| inner.percent_encode_noop())
143
+ pub fn percent_encode_noop(mut self) -> Form {
144
+ self.percent_encoding = PercentEncoding::NoOp;
145
+ self
138
146
  }
139
147
 
140
148
  /// Consume this instance and transform into an instance of Body for use in a request.
141
149
  pub(crate) fn stream(self) -> Body {
142
- if self.inner.fields.is_empty() {
150
+ if self.fields.is_empty() {
143
151
  return Body::empty();
144
152
  }
145
153
 
@@ -148,7 +156,7 @@ impl Form {
148
156
 
149
157
  /// Produce a stream of the bytes in this `Form`, consuming it.
150
158
  pub fn into_stream(mut self) -> impl Stream<Item = Result<Bytes, crate::Error>> + Send + Sync {
151
- if self.inner.fields.is_empty() {
159
+ if self.fields.is_empty() {
152
160
  let empty_stream: Pin<
153
161
  Box<dyn Stream<Item = Result<Bytes, crate::Error>> + Send + Sync>,
154
162
  > = Box::pin(futures_util::stream::empty());
@@ -156,11 +164,11 @@ impl Form {
156
164
  }
157
165
 
158
166
  // create initial part to init reduce chain
159
- let (name, part) = self.inner.fields.remove(0);
167
+ let (name, part) = self.fields.remove(0);
160
168
  let start = Box::pin(self.part_stream(name, part))
161
169
  as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
162
170
 
163
- let fields = self.inner.take_fields();
171
+ let fields = self.take_fields();
164
172
  // for each field, chain an additional stream
165
173
  let stream = fields.into_iter().fold(start, |memo, (name, part)| {
166
174
  let part_stream = self.part_stream(name, part);
@@ -169,7 +177,7 @@ impl Form {
169
177
  });
170
178
  // append special ending boundary
171
179
  let last = stream::once(future::ready(Ok(
172
- format!("--{}--\r\n", self.boundary()).into()
180
+ format!("--{}--\r\n", self.boundary).into()
173
181
  )));
174
182
  Box::pin(stream.chain(last))
175
183
  }
@@ -184,13 +192,10 @@ impl Form {
184
192
  T: Into<Cow<'static, str>>,
185
193
  {
186
194
  // start with boundary
187
- let boundary = stream::once(future::ready(Ok(
188
- format!("--{}\r\n", self.boundary()).into()
189
- )));
195
+ let boundary = stream::once(future::ready(Ok(format!("--{}\r\n", self.boundary).into())));
190
196
  // append headers
191
197
  let header = stream::once(future::ready(Ok({
192
198
  let mut h = self
193
- .inner
194
199
  .percent_encoding
195
200
  .encode_headers(&name.into(), &part.meta);
196
201
  h.extend_from_slice(b"\r\n\r\n");
@@ -203,17 +208,44 @@ impl Form {
203
208
  .chain(stream::once(future::ready(Ok("\r\n".into()))))
204
209
  }
205
210
 
211
+ // If predictable, computes the length the request will have
212
+ // The length should be predictable if only String and file fields have been added,
213
+ // but not if a generic reader has been added;
206
214
  pub(crate) fn compute_length(&mut self) -> Option<u64> {
207
- self.inner.compute_length()
215
+ let mut length = 0u64;
216
+ for (name, field) in self.fields.iter() {
217
+ match field.value_len() {
218
+ Some(value_length) => {
219
+ // We are constructing the header just to get its length. To not have to
220
+ // construct it again when the request is sent we cache these headers.
221
+ let header = self.percent_encoding.encode_headers(name, field.metadata());
222
+ let header_length = header.len();
223
+ self.computed_headers.push(header);
224
+ // The additions mimic the format string out of which the field is constructed
225
+ // in Reader. Not the cleanest solution because if that format string is
226
+ // ever changed then this formula needs to be changed too which is not an
227
+ // obvious dependency in the code.
228
+ length += 2
229
+ + self.boundary.len() as u64
230
+ + 2
231
+ + header_length as u64
232
+ + 4
233
+ + value_length
234
+ + 2
235
+ }
236
+ _ => return None,
237
+ }
238
+ }
239
+ // If there is at least one field there is a special boundary for the very last field.
240
+ if !self.fields.is_empty() {
241
+ length += 2 + self.boundary.len() as u64 + 4
242
+ }
243
+ Some(length)
208
244
  }
209
245
 
210
- fn with_inner<F>(self, func: F) -> Self
211
- where
212
- F: FnOnce(FormParts<Part>) -> FormParts<Part>,
213
- {
214
- Form {
215
- inner: func(self.inner),
216
- }
246
+ /// Take the fields vector of this instance, replacing with an empty vector.
247
+ fn take_fields(&mut self) -> Vec<(Cow<'static, str>, Part)> {
248
+ std::mem::take(&mut self.fields)
217
249
  }
218
250
  }
219
251
 
@@ -261,8 +293,8 @@ impl Part {
261
293
  /// # Errors
262
294
  ///
263
295
  /// Errors when the file cannot be opened.
264
- #[cfg(feature = "stream")]
265
- #[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
296
+ #[cfg(all(feature = "tokio-rt", feature = "stream"))]
297
+ #[cfg_attr(docsrs, doc(cfg(all(feature = "tokio-rt", feature = "stream"))))]
266
298
  pub async fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
267
299
  let path = path.as_ref();
268
300
  let file_name = path
@@ -316,18 +348,6 @@ impl Part {
316
348
  self.with_inner(move |inner| inner.headers(headers))
317
349
  }
318
350
 
319
- fn with_inner<F>(self, func: F) -> Self
320
- where
321
- F: FnOnce(PartMetadata) -> PartMetadata,
322
- {
323
- Part {
324
- meta: func(self.meta),
325
- ..self
326
- }
327
- }
328
- }
329
-
330
- impl PartProps for Part {
331
351
  fn value_len(&self) -> Option<u64> {
332
352
  if self.body_length.is_some() {
333
353
  self.body_length
@@ -339,96 +359,22 @@ impl PartProps for Part {
339
359
  fn metadata(&self) -> &PartMetadata {
340
360
  &self.meta
341
361
  }
342
- }
343
-
344
- // ===== impl FormParts =====
345
-
346
- impl<P: PartProps> FormParts<P> {
347
- pub(crate) fn new() -> Self {
348
- FormParts {
349
- boundary: gen_boundary(),
350
- computed_headers: Vec::new(),
351
- fields: Vec::new(),
352
- percent_encoding: PercentEncoding::PathSegment,
353
- }
354
- }
355
362
 
356
- pub(crate) fn boundary(&self) -> &str {
357
- &self.boundary
358
- }
359
-
360
- /// Adds a customized Part.
361
- pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
363
+ fn with_inner<F>(self, func: F) -> Self
362
364
  where
363
- T: Into<Cow<'static, str>>,
365
+ F: FnOnce(PartMetadata) -> PartMetadata,
364
366
  {
365
- self.fields.push((name.into(), part));
366
- self
367
- }
368
-
369
- /// Configure this `Form` to percent-encode using the `path-segment` rules.
370
- pub(crate) fn percent_encode_path_segment(mut self) -> Self {
371
- self.percent_encoding = PercentEncoding::PathSegment;
372
- self
373
- }
374
-
375
- /// Configure this `Form` to percent-encode using the `attr-char` rules.
376
- pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
377
- self.percent_encoding = PercentEncoding::AttrChar;
378
- self
379
- }
380
-
381
- /// Configure this `Form` to skip percent-encoding
382
- pub(crate) fn percent_encode_noop(mut self) -> Self {
383
- self.percent_encoding = PercentEncoding::NoOp;
384
- self
385
- }
386
-
387
- // If predictable, computes the length the request will have
388
- // The length should be predictable if only String and file fields have been added,
389
- // but not if a generic reader has been added;
390
- pub(crate) fn compute_length(&mut self) -> Option<u64> {
391
- let mut length = 0u64;
392
- for (name, field) in self.fields.iter() {
393
- match field.value_len() {
394
- Some(value_length) => {
395
- // We are constructing the header just to get its length. To not have to
396
- // construct it again when the request is sent we cache these headers.
397
- let header = self.percent_encoding.encode_headers(name, field.metadata());
398
- let header_length = header.len();
399
- self.computed_headers.push(header);
400
- // The additions mimic the format string out of which the field is constructed
401
- // in Reader. Not the cleanest solution because if that format string is
402
- // ever changed then this formula needs to be changed too which is not an
403
- // obvious dependency in the code.
404
- length += 2
405
- + self.boundary().len() as u64
406
- + 2
407
- + header_length as u64
408
- + 4
409
- + value_length
410
- + 2
411
- }
412
- _ => return None,
413
- }
414
- }
415
- // If there is at least one field there is a special boundary for the very last field.
416
- if !self.fields.is_empty() {
417
- length += 2 + self.boundary().len() as u64 + 4
367
+ Part {
368
+ meta: func(self.meta),
369
+ ..self
418
370
  }
419
- Some(length)
420
- }
421
-
422
- /// Take the fields vector of this instance, replacing with an empty vector.
423
- fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
424
- std::mem::take(&mut self.fields)
425
371
  }
426
372
  }
427
373
 
428
374
  // ===== impl PartMetadata =====
429
375
 
430
376
  impl PartMetadata {
431
- pub(crate) fn new() -> Self {
377
+ fn new() -> Self {
432
378
  PartMetadata {
433
379
  mime: None,
434
380
  file_name: None,
@@ -436,12 +382,12 @@ impl PartMetadata {
436
382
  }
437
383
  }
438
384
 
439
- pub(crate) fn mime(mut self, mime: Mime) -> Self {
385
+ fn mime(mut self, mime: Mime) -> Self {
440
386
  self.mime = Some(mime);
441
387
  self
442
388
  }
443
389
 
444
- pub(crate) fn file_name<T>(mut self, filename: T) -> Self
390
+ fn file_name<T>(mut self, filename: T) -> Self
445
391
  where
446
392
  T: Into<Cow<'static, str>>,
447
393
  {
@@ -449,7 +395,7 @@ impl PartMetadata {
449
395
  self
450
396
  }
451
397
 
452
- pub(crate) fn headers<T>(mut self, headers: T) -> Self
398
+ fn headers<T>(mut self, headers: T) -> Self
453
399
  where
454
400
  T: Into<HeaderMap>,
455
401
  {
@@ -487,14 +433,14 @@ const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
487
433
  .remove(b'~');
488
434
 
489
435
  #[derive(Debug)]
490
- pub(crate) enum PercentEncoding {
436
+ enum PercentEncoding {
491
437
  PathSegment,
492
438
  AttrChar,
493
439
  NoOp,
494
440
  }
495
441
 
496
442
  impl PercentEncoding {
497
- pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
443
+ fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
498
444
  let mut buf = Vec::new();
499
445
  buf.extend_from_slice(b"Content-Disposition: form-data; ");
500
446
 
@@ -550,15 +496,45 @@ impl PercentEncoding {
550
496
  }
551
497
  }
552
498
 
499
+ /// See chromium's implementation: <https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/network/form_data_encoder.cc>
553
500
  fn gen_boundary() -> String {
554
501
  use crate::util::fast_random as random;
555
502
 
556
- let a = random();
557
- let b = random();
558
- let c = random();
559
- let d = random();
503
+ const PREFIX: &[u8; 22] = b"----WebKitFormBoundary";
504
+
505
+ // The RFC 2046 spec says the alphanumeric characters plus the
506
+ // following characters are legal for boundaries: '()+_,-./:=?
507
+ // However the following characters, though legal, cause some sites
508
+ // to fail: (),./:=+
509
+ // Note that our algorithm makes it twice as much likely for 'A' or 'B'
510
+ // to appear in the boundary string, because 0x41 and 0x42 are present in
511
+ // the below array twice.
512
+ const ALPHA_NUMERIC_ENCODING_MAP: [u8; 64] = [
513
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
514
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64,
515
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73,
516
+ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
517
+ 0x38, 0x39, 0x41, 0x42,
518
+ ];
519
+
520
+ // Pre-allocate a buffer for the boundary string. The final length will be 22 (prefix) + 16
521
+ // (random chars) = 38.
522
+ let mut boundary = Vec::with_capacity(38);
523
+ // Start with an informative prefix.
524
+ boundary.extend_from_slice(PREFIX);
525
+
526
+ // Append 16 random 7bit ascii AlphaNumeric characters.
527
+ for _ in 0..2 {
528
+ let mut randomness = random();
529
+ for _ in 0..8 {
530
+ let index = (randomness & 0x3F) as usize;
531
+ boundary.push(ALPHA_NUMERIC_ENCODING_MAP[index]);
532
+ randomness >>= 6;
533
+ }
534
+ }
560
535
 
561
- format!("{a:016x}-{b:016x}-{c:016x}-{d:016x}")
536
+ assert_eq!(boundary.len(), 38);
537
+ String::from_utf8(boundary).expect("Invalid UTF-8 generated")
562
538
  }
563
539
 
564
540
  #[cfg(test)]
@@ -610,7 +586,7 @@ mod tests {
610
586
  ))))),
611
587
  )
612
588
  .part("key3", Part::text("value3").file_name("filename"));
613
- form.inner.boundary = "boundary".to_string();
589
+ form.boundary = "boundary".into();
614
590
  let expected = "--boundary\r\n\
615
591
  Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
616
592
  part1\r\n\
@@ -651,7 +627,7 @@ mod tests {
651
627
  headers.insert("Hdr3", "/a/b/c".parse().unwrap());
652
628
  part = part.headers(headers);
653
629
  let mut form = Form::new().part("key2", part);
654
- form.inner.boundary = "boundary".to_string();
630
+ form.boundary = "boundary".into();
655
631
  let expected = "--boundary\r\n\
656
632
  Content-Disposition: form-data; name=\"key2\"\r\n\
657
633
  Content-Type: image/bmp\r\n\
@@ -714,4 +690,11 @@ mod tests {
714
690
  &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
715
691
  );
716
692
  }
693
+
694
+ #[test]
695
+ fn custom_boundary_is_applied() {
696
+ let form = Form::with_boundary("----WebKitFormBoundary0123456789");
697
+
698
+ assert_eq!(form.boundary(), "----WebKitFormBoundary0123456789");
699
+ }
717
700
  }