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.
- checksums.yaml +4 -4
- data/Cargo.lock +1922 -397
- data/LICENSE +203 -0
- data/README.md +19 -15
- data/ext/wreq_rb/Cargo.toml +4 -6
- data/ext/wreq_rb/src/client.rs +41 -48
- data/lib/wreq-rb/version.rb +1 -1
- data/patches/0001-add-transfer-size-tracking.patch +76 -67
- data/vendor/wreq/Cargo.toml +119 -71
- data/vendor/wreq/README.md +25 -20
- data/vendor/wreq/bench/http1.rs +25 -0
- data/vendor/wreq/bench/http1_over_tls.rs +25 -0
- data/vendor/wreq/bench/http2.rs +25 -0
- data/vendor/wreq/bench/http2_over_tls.rs +25 -0
- data/vendor/wreq/bench/support/bench.rs +91 -0
- data/vendor/wreq/bench/support/client.rs +217 -0
- data/vendor/wreq/bench/support/server.rs +188 -0
- data/vendor/wreq/bench/support.rs +56 -0
- data/vendor/wreq/examples/cert_store.rs +4 -4
- data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
- data/vendor/wreq/examples/http2_websocket.rs +2 -2
- data/vendor/wreq/examples/keylog.rs +3 -3
- data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
- data/vendor/wreq/examples/rt.rs +23 -0
- data/vendor/wreq/src/client/body.rs +23 -61
- data/vendor/wreq/src/client/emulate.rs +119 -0
- data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
- data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
- data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
- data/vendor/wreq/src/client/layer/config.rs +27 -6
- data/vendor/wreq/src/client/layer/decoder.rs +9 -4
- data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
- data/vendor/wreq/src/client/layer/redirect.rs +4 -5
- data/vendor/wreq/src/client/layer/retry.rs +8 -5
- data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
- data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
- data/vendor/wreq/src/client/layer/timeout.rs +24 -74
- data/vendor/wreq/src/client/layer.rs +1 -2
- data/vendor/wreq/src/client/multipart.rs +137 -154
- data/vendor/wreq/src/client/request.rs +202 -118
- data/vendor/wreq/src/client/response.rs +46 -45
- data/vendor/wreq/src/client/upgrade.rs +15 -0
- data/vendor/wreq/src/client/ws.rs +73 -25
- data/vendor/wreq/src/client.rs +1655 -17
- data/vendor/wreq/src/config.rs +11 -11
- data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
- data/vendor/wreq/src/conn/descriptor.rs +143 -0
- data/vendor/wreq/src/conn/http.rs +484 -0
- data/vendor/wreq/src/conn/net/io.rs +75 -0
- data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
- data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
- data/vendor/wreq/src/conn/net/tcp.rs +561 -0
- data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
- data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
- data/vendor/wreq/src/conn/net/uds.rs +11 -0
- data/vendor/wreq/src/conn/net.rs +130 -0
- data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
- data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
- data/vendor/wreq/src/conn/tls_info.rs +47 -0
- data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
- data/vendor/wreq/src/cookie.rs +302 -142
- data/vendor/wreq/src/dns/gai/compio.rs +77 -0
- data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
- data/vendor/wreq/src/dns/gai.rs +14 -164
- data/vendor/wreq/src/dns/hickory.rs +16 -23
- data/vendor/wreq/src/dns/resolve.rs +7 -41
- data/vendor/wreq/src/dns.rs +90 -7
- data/vendor/wreq/src/error.rs +57 -31
- data/vendor/wreq/src/ext.rs +25 -0
- data/vendor/wreq/src/group.rs +211 -0
- data/vendor/wreq/src/header.rs +100 -112
- data/vendor/wreq/src/lib.rs +124 -73
- data/vendor/wreq/src/proxy.rs +6 -20
- data/vendor/wreq/src/redirect.rs +1 -1
- data/vendor/wreq/src/rt.rs +208 -0
- data/vendor/wreq/src/sync.rs +97 -98
- data/vendor/wreq/src/tls/compress.rs +124 -0
- data/vendor/wreq/src/tls/conn/ext.rs +54 -45
- data/vendor/wreq/src/tls/conn/service.rs +14 -18
- data/vendor/wreq/src/tls/conn.rs +169 -241
- data/vendor/wreq/src/tls/keylog.rs +68 -5
- data/vendor/wreq/src/tls/session.rs +205 -0
- data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
- data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
- data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
- data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
- data/vendor/wreq/src/tls.rs +489 -25
- data/vendor/wreq/src/trace.rs +0 -12
- data/vendor/wreq/src/util.rs +1 -1
- data/vendor/wreq/tests/badssl.rs +10 -10
- data/vendor/wreq/tests/client.rs +3 -9
- data/vendor/wreq/tests/cookie.rs +6 -8
- data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
- data/vendor/wreq/tests/multipart.rs +43 -1
- data/vendor/wreq/tests/proxy.rs +1 -1
- data/vendor/wreq/tests/support/layer.rs +1 -0
- metadata +49 -71
- data/patches/0002-add-cancel-connections.patch +0 -181
- data/vendor/wreq/src/client/conn/conn.rs +0 -231
- data/vendor/wreq/src/client/conn/http.rs +0 -1023
- data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
- data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
- data/vendor/wreq/src/client/core/body/length.rs +0 -118
- data/vendor/wreq/src/client/core/body.rs +0 -34
- data/vendor/wreq/src/client/core/common/buf.rs +0 -149
- data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
- data/vendor/wreq/src/client/core/common/watch.rs +0 -76
- data/vendor/wreq/src/client/core/common.rs +0 -3
- data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
- data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
- data/vendor/wreq/src/client/core/conn.rs +0 -11
- data/vendor/wreq/src/client/core/dispatch.rs +0 -299
- data/vendor/wreq/src/client/core/error.rs +0 -435
- data/vendor/wreq/src/client/core/ext.rs +0 -201
- data/vendor/wreq/src/client/core/http1.rs +0 -178
- data/vendor/wreq/src/client/core/http2.rs +0 -483
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
- data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
- data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
- data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
- data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
- data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
- data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
- data/vendor/wreq/src/client/core/proto.rs +0 -58
- data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
- data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
- data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
- data/vendor/wreq/src/client/core/rt.rs +0 -25
- data/vendor/wreq/src/client/core/upgrade.rs +0 -267
- data/vendor/wreq/src/client/core.rs +0 -16
- data/vendor/wreq/src/client/emulation.rs +0 -161
- data/vendor/wreq/src/client/http/client/error.rs +0 -142
- data/vendor/wreq/src/client/http/client/exec.rs +0 -29
- data/vendor/wreq/src/client/http/client/extra.rs +0 -77
- data/vendor/wreq/src/client/http/client/util.rs +0 -104
- data/vendor/wreq/src/client/http.rs +0 -1629
- data/vendor/wreq/src/client/layer/config/options.rs +0 -156
- data/vendor/wreq/src/client/layer/cookie.rs +0 -161
- data/vendor/wreq/src/hash.rs +0 -143
- data/vendor/wreq/src/tls/conn/cache.rs +0 -123
- data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
- data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
- data/vendor/wreq/src/tls/options.rs +0 -464
- /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
- /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
+
F: FnOnce(PartMetadata) -> PartMetadata,
|
|
364
366
|
{
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
385
|
+
fn mime(mut self, mime: Mime) -> Self {
|
|
440
386
|
self.mime = Some(mime);
|
|
441
387
|
self
|
|
442
388
|
}
|
|
443
389
|
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
436
|
+
enum PercentEncoding {
|
|
491
437
|
PathSegment,
|
|
492
438
|
AttrChar,
|
|
493
439
|
NoOp,
|
|
494
440
|
}
|
|
495
441
|
|
|
496
442
|
impl PercentEncoding {
|
|
497
|
-
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|