Coverage Report

Created: 2026-10-28 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-3.5.0/src/lib.rs
Line
Count
Source
1
//!<div align="center">
2
//!  <!-- Version -->
3
//!  <a href="https://crates.io/crates/ureq">
4
//!    <img src="https://img.shields.io/crates/v/ureq.svg?style=flat-square"
5
//!    alt="Crates.io version" />
6
//!  </a>
7
//!  <!-- Docs -->
8
//!  <a href="https://docs.rs/ureq">
9
//!    <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
10
//!      alt="docs.rs docs" />
11
//!  </a>
12
//!  <!-- Downloads -->
13
//!  <a href="https://crates.io/crates/ureq">
14
//!    <img src="https://img.shields.io/crates/d/ureq.svg?style=flat-square"
15
//!      alt="Crates.io downloads" />
16
//!  </a>
17
//!</div>
18
//!
19
//! A simple, safe HTTP client.
20
//!
21
//! Ureq's first priority is being easy for you to use. It's great for
22
//! anyone who wants a low-overhead HTTP client that just gets the job done. Works
23
//! very well with HTTP APIs. Its features include cookies, JSON, HTTP proxies,
24
//! HTTPS, charset decoding, and is based on the API of the `http` crate.
25
//!
26
//! Ureq is in pure Rust for safety and ease of understanding. It forbids
27
//! `unsafe` code. It uses blocking I/O instead of async I/O, because that keeps
28
//! the API simple and keeps dependencies to a minimum. For TLS, ureq uses
29
//! rustls or native-tls.
30
//!
31
//! See the [changelog] for details of recent releases.
32
//!
33
//! [changelog]: https://github.com/algesten/ureq/blob/main/CHANGELOG.md
34
//!
35
//! # Usage
36
//!
37
//! In its simplest form, ureq looks like this:
38
//!
39
//! ```rust
40
//! let body: String = ureq::get("http://example.com")
41
//!     .header("Example-Header", "header value")
42
//!     .call()?
43
//!     .body_mut()
44
//!     .read_to_string()?;
45
//! # Ok::<(), ureq::Error>(())
46
//! ```
47
//!
48
//! For more involved tasks, you'll want to create an [`Agent`]. An Agent
49
//! holds a connection pool for reuse, and a cookie store if you use the
50
//! **cookies** feature. An Agent can be cheaply cloned due to internal
51
//! [`Arc`] and all clones of an Agent share state among each other. Creating
52
//! an Agent also allows setting options like the TLS configuration.
53
//!
54
//! ```rust
55
//! # fn no_run() -> Result<(), ureq::Error> {
56
//! use ureq::Agent;
57
//! use std::time::Duration;
58
//!
59
//! let mut config = Agent::config_builder()
60
//!     .timeout_global(Some(Duration::from_secs(5)))
61
//!     .build();
62
//!
63
//! let agent: Agent = config.into();
64
//!
65
//! let body: String = agent.get("http://example.com/page")
66
//!     .call()?
67
//!     .body_mut()
68
//!     .read_to_string()?;
69
//!
70
//! // Reuses the connection from previous request.
71
//! let response: String = agent.put("http://example.com/upload")
72
//!     .header("Authorization", "example-token")
73
//!     .send("some body data")?
74
//!     .body_mut()
75
//!     .read_to_string()?;
76
//! # Ok(())}
77
//! ```
78
//!
79
//! ## Timeouts
80
//!
81
//! Configure timeouts on an [`Agent`] or override them per request through
82
//! [`ConfigBuilder`]. Global, per-call, and phase timeouts
83
//! measure total elapsed time. Each network operation uses the shortest applicable
84
//! remaining budget. Per-read and per-write limits restart for each transport
85
//! operation while the total budgets keep running.
86
//!
87
//! | Setting | Budget |
88
//! | --- | --- |
89
//! | `timeout_global` | Entire operation, including redirects and reading the response body. |
90
//! | `timeout_per_call` | One HTTP exchange, including its body; reset for each redirect. |
91
//! | `timeout_resolve` | Local target hostname resolution. |
92
//! | `timeout_connect` | Proxy DNS, socket connection attempts, proxy negotiation, and TLS handshakes. |
93
//! | `timeout_send_request` | Sending all request headers after connection setup. |
94
//! | `timeout_await_100` | Waiting for `100 Continue` before uploading the body. |
95
//! | `timeout_send_body` | Sending the entire request body. |
96
//! | `timeout_recv_response` | Waiting for and receiving complete response headers. |
97
//! | `timeout_recv_body` | Receiving the entire response body, starting after complete response headers. |
98
//! | `timeout_per_read` | Each transport read, subject to the remaining total budgets. |
99
//! | `timeout_per_write` | Each transport write, subject to the remaining total budgets. |
100
//!
101
//! A phase budget stops applying once that phase finishes. For example, a completed
102
//! connection's `timeout_connect` does not limit the later response body transfer.
103
//! A redirect starts new per-call and phase budgets while preserving the global
104
//! budget. See the individual [`ConfigBuilder`] methods for
105
//! protocol coverage and phase boundaries.
106
//!
107
//! Global, per-call, and response body budgets continue after a response is returned
108
//! and while the application consumes its body. Time between body reads counts
109
//! toward these budgets. Timeouts are checked during network I/O; they do not
110
//! interrupt application code or a user-provided request body reader.
111
//!
112
//! `None` disables only that limit. All limits default to `None` except
113
//! `timeout_await_100`, which defaults to one second. When that wait expires, ureq
114
//! proceeds with the upload. Other timeouts are reported as [`Error::Timeout`],
115
//! including through [`std::io::Error`] when using the body's [`std::io::Read`] API.
116
//!
117
//! ## JSON
118
//!
119
//! Ureq supports sending and receiving json, if you enable the **json** feature:
120
//!
121
//! ```rust
122
//! # #[cfg(feature = "json")]
123
//! # fn no_run() -> Result<(), ureq::Error> {
124
//! use serde::{Serialize, Deserialize};
125
//!
126
//! #[derive(Serialize)]
127
//! struct MySendBody {
128
//!    thing: String,
129
//! }
130
//!
131
//! #[derive(Deserialize)]
132
//! struct MyRecvBody {
133
//!    other: String,
134
//! }
135
//!
136
//! let send_body = MySendBody { thing: "yo".to_string() };
137
//!
138
//! // Requires the `json` feature enabled.
139
//! let recv_body = ureq::post("http://example.com/post/ingest")
140
//!     .header("X-My-Header", "Secret")
141
//!     .send_json(&send_body)?
142
//!     .body_mut()
143
//!     .read_json::<MyRecvBody>()?;
144
//! # Ok(())}
145
//! ```
146
//!
147
//! ## Error handling
148
//!
149
//! ureq returns errors via `Result<T, ureq::Error>`. That includes I/O errors,
150
//! protocol errors. By default, also HTTP status code errors (when the
151
//! server responded 4xx or 5xx) results in [`Error`].
152
//!
153
//! This behavior can be turned off via [`http_status_as_error()`]
154
//!
155
//! ```rust
156
//! use ureq::Error;
157
//!
158
//! # fn no_run() -> Result<(), ureq::Error> {
159
//! match ureq::get("http://mypage.example.com/").call() {
160
//!     Ok(response) => { /* it worked */},
161
//!     Err(Error::StatusCode(code)) => {
162
//!         /* the server returned an unexpected status
163
//!            code (such as 400, 500 etc) */
164
//!     }
165
//!     Err(_) => { /* some kind of io/transport/etc error */ }
166
//! }
167
//! # Ok(())}
168
//! ```
169
//!
170
//! # Features
171
//!
172
//! To enable a minimal dependency tree, some features are off by default.
173
//! You can control them when including ureq as a dependency.
174
//!
175
//! `ureq = { version = "3", features = ["socks-proxy", "charset"] }`
176
//!
177
//! The default enabled features are: **rustls** and **gzip**.
178
//!
179
//! * **rustls** enables the rustls TLS implementation. This is the default for the the crate level
180
//!   convenience calls (`ureq::get` etc). It currently uses `ring` as the TLS provider.
181
//! * **native-tls** enables the native tls backend for TLS. Due to the risk of diamond dependencies
182
//!   accidentally switching on an unwanted TLS implementation, `native-tls` is never picked up as
183
//!   a default or used by the crate level convenience calls (`ureq::get` etc) – it must be configured
184
//!   on the agent
185
//! * **platform-verifier** enables verifying the server certificates using a method native to the
186
//!   platform ureq is executing on. See [rustls-platform-verifier] crate
187
//! * **socks-proxy** enables proxy config using the `socks4://`, `socks4a://`, `socks5://`
188
//!   and `socks://` (equal to `socks5://`) prefix
189
//! * **cookies** enables cookies
190
//! * **gzip** enables requests of gzip-compressed responses and decompresses them
191
//! * **brotli** enables requests brotli-compressed responses and decompresses them
192
//! * **charset** enables interpreting the charset part of the Content-Type header
193
//!   (e.g.  `Content-Type: text/plain; charset=iso-8859-1`). Without this, the
194
//!   library defaults to Rust's built in `utf-8`
195
//! * **json** enables JSON sending and receiving via serde_json
196
//! * **multipart** enables multipart/form-data sending via [`unversioned::multipart`]
197
//! * **rustls-webpki-roots** enables the webpki-roots crate for root certificates when using rustls.
198
//! * **native-tls-webpki-roots** enables the webpki-root-certs crate for root certificates when using native-tls.
199
//!
200
//! ### Unstable
201
//!
202
//! These features are unstable and might change in a minor version.
203
//!
204
//! * **rustls-no-provider** Enables rustls, but does not enable webpki and any [`CryptoProvider`] such as `ring`.
205
//!   Root certs and providers other than the default (currently `ring`) are never picked up from feature flags alone.
206
//!   They must be configured on the agent.
207
//!
208
//! * **native-tls-no-default** Enables native-tls, but does not enable webpki.
209
//!   Root certs are never picked up from feature flags alone. They must be configured on the agent.
210
//!
211
//! * **vendored** compiles and statically links to a copy of non-Rust vendors (e.g. OpenSSL from `native-tls`)
212
//!
213
//! # TLS (https)
214
//!
215
//! ## rustls
216
//!
217
//! By default, ureq uses [`rustls` crate] with the `ring` cryptographic provider.
218
//! As of Sep 2024, the `ring` provider has a higher chance of compiling successfully. If the user
219
//! installs another process [default provider], that choice is respected.
220
//!
221
//! ureq does not guarantee to default to ring indefinitely. `rustls` as a feature flag will always
222
//! work, but the specific crypto backend might change in a minor version.
223
//!
224
//! ```
225
//! # #[cfg(feature = "rustls")]
226
//! # {
227
//! // This uses rustls
228
//! ureq::get("https://www.google.com/").call().unwrap();
229
//! # } Ok::<_, ureq::Error>(())
230
//! ```
231
//!
232
//! ### rustls without ring
233
//!
234
//! ureq never changes TLS backend from feature flags alone. It is possible to compile ureq
235
//! without ring, but it requires specific feature flags and configuring the [`Agent`].
236
//!
237
//! Since rustls is not semver 1.x, this requires non-semver-guaranteed API. I.e. ureq might
238
//! change this behavior without a major version bump.
239
//!
240
//! Read more at [`TlsConfigBuilder::unversioned_rustls_crypto_provider`][crate::tls::TlsConfigBuilder::unversioned_rustls_crypto_provider].
241
//!
242
//! ## native-tls
243
//!
244
//! As an alternative, ureq ships with [`native-tls`] as a TLS provider. This must be
245
//! enabled using the **native-tls** feature. Due to the risk of diamond dependencies
246
//! accidentally switching on an unwanted TLS implementation, `native-tls` is never picked
247
//! up as a default or used by the crate level convenience calls (`ureq::get` etc) – it
248
//! must be configured on the agent.
249
//!
250
//! ```
251
//! # #[cfg(feature = "native-tls")]
252
//! # {
253
//! use ureq::config::Config;
254
//! use ureq::tls::{TlsConfig, TlsProvider};
255
//!
256
//! let mut config = Config::builder()
257
//!     .tls_config(
258
//!         TlsConfig::builder()
259
//!             // requires the native-tls feature
260
//!             .provider(TlsProvider::NativeTls)
261
//!             .build()
262
//!     )
263
//!     .build();
264
//!
265
//! let agent = config.new_agent();
266
//!
267
//! agent.get("https://www.google.com/").call().unwrap();
268
//! # } Ok::<_, ureq::Error>(())
269
//! ```
270
//!
271
//! ## Root certificates
272
//!
273
//! ### webpki-roots
274
//!
275
//! By default, ureq uses Mozilla's root certificates via the [webpki-roots] crate. This is a static
276
//! bundle of root certificates that do not update automatically. It also circumvents whatever root
277
//! certificates are installed on the host running ureq, which might be a good or a bad thing depending
278
//! on your perspective. There is also no mechanism for [SCT], [CRL]s or other revocations.
279
//! To maintain a "fresh" list of root certs, you need to bump the ureq dependency from time to time.
280
//!
281
//! The main reason for chosing this as the default is to minimize the number of dependencies. More
282
//! details about this decision can be found at [PR 818].
283
//!
284
//! If your use case for ureq is talking to a limited number of servers with high trust, the
285
//! default setting is likely sufficient. If you use ureq with a high number of servers, or servers
286
//! you don't trust, we recommend using the platform verifier (see below).
287
//!
288
//! ### platform-verifier
289
//!
290
//! The [rustls-platform-verifier] crate provides access to natively checking the certificate via your OS.
291
//! To use this verifier, you need to enable it using feature flag **platform-verifier** as well as
292
//! configure an agent to use it.
293
//!
294
//! ```
295
//! # #[cfg(all(feature = "rustls", feature="platform-verifier"))]
296
//! # {
297
//! use ureq::Agent;
298
//! use ureq::tls::{TlsConfig, RootCerts};
299
//!
300
//! let agent = Agent::config_builder()
301
//!     .tls_config(
302
//!         TlsConfig::builder()
303
//!             .root_certs(RootCerts::PlatformVerifier)
304
//!             .build()
305
//!     )
306
//!     .build()
307
//!     .new_agent();
308
//!
309
//! let response = agent.get("https://httpbin.org/get").call()?;
310
//! # } Ok::<_, ureq::Error>(())
311
//! ```
312
//!
313
//! Setting `RootCerts::PlatformVerifier` together with `TlsProvider::NativeTls` means
314
//! also native-tls will use the OS roots instead of [webpki-roots] crate. Whether that
315
//! results in a config that has CRLs and revocations is up to whatever native-tls links to.
316
//!
317
//! # JSON
318
//!
319
//! By enabling the **json** feature, the library supports serde json.
320
//!
321
//! This is disabled by default.
322
//!
323
//! * [`request.send_json()`] send body as json.
324
//! * [`body.read_json()`] transform response to json.
325
//!
326
//! # Sending body data
327
//!
328
//! HTTP/1.1 has two ways of transfering body data. Either of a known size with
329
//! the `Content-Length` HTTP header, or unknown size with the
330
//! `Transfer-Encoding: chunked` header. ureq supports both and will use the
331
//! appropriate method depending on which body is being sent.
332
//!
333
//! ureq has a [`AsSendBody`] trait that is implemented for many well known types
334
//! of data that we might want to send. The request body can thus be anything
335
//! from a `String` to a `File`, see below.
336
//!
337
//! ## Content-Length
338
//!
339
//! The library will send a `Content-Length` header on requests with bodies of
340
//! known size, in other words, if the body to send is one of:
341
//!
342
//! * `&[u8]`
343
//! * `&[u8; N]`
344
//! * `&str`
345
//! * `String`
346
//! * `&String`
347
//! * `Vec<u8>`
348
//! * `&Vec<u8>)`
349
//! * [`SendBody::from_json()`] (implicitly via [`request.send_json()`])
350
//!
351
//! ## Transfer-Encoding: chunked
352
//!
353
//! ureq will send a `Transfer-Encoding: chunked` header on requests where the body
354
//! is of unknown size. The body is automatically converted to an [`std::io::Read`]
355
//! when the type is one of:
356
//!
357
//! * `File`
358
//! * `&File`
359
//! * `TcpStream`
360
//! * `&TcpStream`
361
//! * `Stdin`
362
//! * `UnixStream` (not on windows)
363
//!
364
//! ### From readers
365
//!
366
//! The chunked method also applies for bodies constructed via:
367
//!
368
//! * [`SendBody::from_reader()`]
369
//! * [`SendBody::from_owned_reader()`]
370
//!
371
//! ## Proxying a response body
372
//!
373
//! As a special case, when ureq sends a [`Body`] from a previous http call, the
374
//! use of `Content-Length` or `chunked` depends on situation. For input such as
375
//! gzip decoding (**gzip** feature) or charset transformation (**charset** feature),
376
//! the output body might not match the input, which means ureq is forced to use
377
//! the `chunked` method.
378
//!
379
//! * `Response<Body>`
380
//!
381
//! ## Sending form data
382
//!
383
//! [`request.send_form()`] provides a way to send `application/x-www-form-urlencoded`
384
//! encoded data. The key/values provided will be URL encoded.
385
//!
386
//! ## Overriding
387
//!
388
//! If you set your own Content-Length or Transfer-Encoding header before
389
//! sending the body, ureq will respect that header by not overriding it,
390
//! and by encoding the body or not, as indicated by the headers you set.
391
//!
392
//! ```
393
//! let resp = ureq::put("https://httpbin.org/put")
394
//!     .header("Transfer-Encoding", "chunked")
395
//!     .send("Hello world")?;
396
//! # Ok::<_, ureq::Error>(())
397
//! ```
398
//!
399
//! # Character encoding
400
//!
401
//! By enabling the **charset** feature, the library supports receiving other
402
//! character sets than `utf-8`.
403
//!
404
//! For [`Body::read_to_string()`] we read the header like:
405
//!
406
//! `Content-Type: text/plain; charset=iso-8859-1`
407
//!
408
//! and if it contains a charset specification, we try to decode the body using that
409
//! encoding. In the absence of, or failing to interpret the charset, we fall back on `utf-8`.
410
//!
411
//! Currently ureq does not provide a way to encode when sending request bodies.
412
//!
413
//! ## Lossy utf-8
414
//!
415
//! When reading text bodies (with a `Content-Type` starting `text/` as in `text/plain`,
416
//! `text/html`, etc), ureq can ensure the body is possible to read as a `String` also if
417
//! it contains characters that are not valid for utf-8. Invalid characters are replaced
418
//! with a question mark `?` (NOT the utf-8 replacement character).
419
//!
420
//! For [`Body::read_to_string()`] this is turned on by default, but it can be disabled
421
//! and conversely for [`Body::as_reader()`] it is not enabled, but can be.
422
//!
423
//! To precisely configure the behavior use [`Body::with_config()`].
424
//!
425
//! # Proxying
426
//!
427
//! ureq supports two kinds of proxies,  [`HTTP`] ([`CONNECT`]), [`SOCKS4`]/[`SOCKS5`],
428
//! the former is always available while the latter must be enabled using the feature
429
//! **socks-proxy**.
430
//!
431
//! Proxies settings are configured on an [`Agent`]. All request sent through the agent will be proxied.
432
//!
433
//! ## Environment Variables
434
//!
435
//! ureq automatically reads proxy configuration from environment variables when creating
436
//! a default [`Agent`]. Proxy variables are checked in order: `ALL_PROXY`, `HTTPS_PROXY`,
437
//! then `HTTP_PROXY` (with lowercase variants).
438
//!
439
//! `NO_PROXY` specifies hosts that bypass the proxy, supporting exact hosts, wildcard
440
//! suffixes (`*.example.com`), dot suffixes (`.example.com`), and match-all (`*`).
441
//!
442
//! ## Example using HTTP
443
//!
444
//! ```rust
445
//! use ureq::{Agent, Proxy};
446
//! # fn no_run() -> std::result::Result<(), ureq::Error> {
447
//! // Configure an http connect proxy.
448
//! let proxy = Proxy::new("http://user:password@cool.proxy:9090")?;
449
//! let agent: Agent = Agent::config_builder()
450
//!     .proxy(Some(proxy))
451
//!     .build()
452
//!     .into();
453
//!
454
//! // This is proxied.
455
//! let resp = agent.get("http://cool.server").call()?;
456
//! # Ok(())}
457
//! # fn main() {}
458
//! ```
459
//!
460
//! ## Example using SOCKS5
461
//!
462
//! ```rust
463
//! use ureq::{Agent, Proxy};
464
//! # #[cfg(feature = "socks-proxy")]
465
//! # fn no_run() -> std::result::Result<(), ureq::Error> {
466
//! // Configure a SOCKS proxy.
467
//! let proxy = Proxy::new("socks5://user:password@cool.proxy:9090")?;
468
//! let agent: Agent = Agent::config_builder()
469
//!     .proxy(Some(proxy))
470
//!     .build()
471
//!     .into();
472
//!
473
//! // This is proxied.
474
//! let resp = agent.get("http://cool.server").call()?;
475
//! # Ok(())}
476
//! ```
477
//!
478
//! # Log levels
479
//!
480
//! ureq uses the log crate. These are the definitions of the log levels, however we
481
//! do not guarantee anything for dependencies such as `http` and `rustls`.
482
//!
483
//! * `ERROR` - nothing
484
//! * `WARN` - if we detect a user configuration problem.
485
//! * `INFO` - nothing
486
//! * `DEBUG` - uri, state changes, transport, resolver and selected request/response headers
487
//! * `TRACE` - wire level debug. NOT REDACTED!
488
//!
489
//! The request/response headers on DEBUG levels are allow-listed to only include headers that
490
//! are considered safe. The code has the [allow list](https://github.com/algesten/ureq/blob/81127cfc38516903330dc1b9c618122372f8dc29/src/util.rs#L184-L198).
491
//!
492
//! # Versioning
493
//!
494
//! ## Semver and `unversioned`
495
//!
496
//! ureq follows semver. From ureq 3.x we strive to have a much closer adherence to semver than 2.x.
497
//! The main mistake in 2.x was to re-export crates that were not yet semver 1.0. In ureq 3.x TLS and
498
//! cookie configuration is shimmed using our own types.
499
//!
500
//! ureq 3.x is trying out two new traits that had no equivalent in 2.x, [`Transport`] and [`Resolver`].
501
//! These allow the user write their own bespoke transports and (DNS name) resolver. The API:s for
502
//! these parts are not yet solidified. They live under the [`unversioned`] module, and do not
503
//! follow semver. See module doc for more info.
504
//!
505
//! ## Breaking changes in dependencies
506
//!
507
//! ureq relies on non-semver 1.x crates such as `rustls` and `native-tls`. Some scenarios, such
508
//! as configuring `rustls` to not use `ring`, a user of ureq might need to interact with these
509
//! crates directly instead of going via ureq's provided API.
510
//!
511
//! Such changes can break when ureq updates dependencies. This is not considered a breaking change
512
//! for ureq and will not be reflected by a major version bump.
513
//!
514
//! We strive to mark ureq's API with the word "unversioned" to identify places where this risk arises.
515
//!
516
//! ## Minimum Supported Rust Version (MSRV)
517
//!
518
//! From time to time we will need to update our minimum supported Rust version (MSRV). This is not
519
//! something we do lightly; our ambition is to be as conservative with MSRV as possible.
520
//!
521
//! * For some dependencies, we will opt for pinning the version of the dep instead
522
//!   of bumping our MSRV.
523
//! * For important dependencies, like the TLS libraries, we cannot hold back our MSRV if they change.
524
//! * We do not consider MSRV changes to be breaking for the purposes of semver.
525
//! * We will not make MSRV changes in patch releases.
526
//! * MSRV changes will get their own minor release, and not be co-mingled with other changes.
527
//!
528
//! [`HTTP`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling#http_tunneling
529
//! [`CONNECT`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT
530
//! [`SOCKS4`]: https://en.wikipedia.org/wiki/SOCKS#SOCKS4
531
//! [`SOCKS5`]: https://en.wikipedia.org/wiki/SOCKS#SOCKS5
532
//! [`rustls` crate]: https://crates.io/crates/rustls
533
//! [default provider]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html#method.install_default
534
//! [`native-tls`]: https://crates.io/crates/native-tls
535
//! [rustls-platform-verifier]: https://crates.io/crates/rustls-platform-verifier
536
//! [webpki-roots]: https://crates.io/crates/webpki-roots
537
//! [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
538
//! [`Agent`]: https://docs.rs/ureq/latest/ureq/struct.Agent.html
539
//! [`ConfigBuilder`]: https://docs.rs/ureq/latest/ureq/config/struct.ConfigBuilder.html
540
//! [`Error`]: https://docs.rs/ureq/latest/ureq/enum.Error.html
541
//! [`Error::Timeout`]: https://docs.rs/ureq/latest/ureq/enum.Error.html#variant.Timeout
542
//! [`http_status_as_error()`]: https://docs.rs/ureq/latest/ureq/config/struct.ConfigBuilder.html#method.http_status_as_error
543
//! [SCT]: https://en.wikipedia.org/wiki/Certificate_Transparency
544
//! [CRL]: https://en.wikipedia.org/wiki/Certificate_revocation_list
545
//! [PR 818]: https://github.com/algesten/ureq/pull/818
546
//! [`request.send_json()`]: https://docs.rs/ureq/latest/ureq/struct.RequestBuilder.html#method.send_json
547
//! [`body.read_json()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.read_json
548
//! [`AsSendBody`]: https://docs.rs/ureq/latest/ureq/trait.AsSendBody.html
549
//! [`SendBody::from_json()`]: https://docs.rs/ureq/latest/ureq/struct.SendBody.html#method.from_json
550
//! [`std::io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
551
//! [`std::io::Error`]: https://doc.rust-lang.org/std/io/struct.Error.html
552
//! [`SendBody::from_reader()`]: https://docs.rs/ureq/latest/ureq/struct.SendBody.html#method.from_reader
553
//! [`SendBody::from_owned_reader()`]: https://docs.rs/ureq/latest/ureq/struct.SendBody.html#method.from_owned_reader
554
//! [`Body`]: https://docs.rs/ureq/latest/ureq/struct.Body.html
555
//! [`request.send_form()`]: https://docs.rs/ureq/latest/ureq/struct.RequestBuilder.html#method.send_form
556
//! [`Body::read_to_string()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.read_to_string
557
//! [`Body::as_reader()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.as_reader
558
//! [`Body::with_config()`]: https://docs.rs/ureq/latest/ureq/struct.Body.html#method.with_config
559
//! [`Transport`]: https://docs.rs/ureq/latest/ureq/unversioned/transport/trait.Transport.html
560
//! [`Resolver`]: https://docs.rs/ureq/latest/ureq/unversioned/resolver/trait.Resolver.html
561
//! [`unversioned`]: https://docs.rs/ureq/latest/ureq/unversioned/index.html
562
//! [`CryptoProvider`]: https://docs.rs/rustls/latest/rustls/crypto/struct.CryptoProvider.html
563
//! [`unversioned::multipart`]: https://docs.rs/ureq/latest/ureq/unversioned/multipart/index.html
564
565
#![forbid(unsafe_code)]
566
#![warn(clippy::all)]
567
#![deny(missing_docs)]
568
// I don't think elided lifetimes help in understanding the code.
569
#![allow(clippy::needless_lifetimes)]
570
// Since you can't use inlined args for all cases, using it means the
571
// code will have a mix of inlined and not inlined. Code should be
572
// uniform, thus this lint is misguided.
573
#![allow(clippy::uninlined_format_args)]
574
// mismatched_lifetime_syntaxes was introduced after our MSRV.
575
#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
576
577
#[macro_use]
578
extern crate log;
579
580
use std::convert::TryFrom;
581
582
/// Re-exported http-crate.
583
pub use ureq_proto::http;
584
585
pub use body::{Body, BodyBuilder, BodyReader, BodyWithConfig};
586
use http::Method;
587
use http::{Request, Response, Uri};
588
pub use proxy::{Proxy, ProxyBuilder, ProxyProtocol};
589
pub use request::RequestBuilder;
590
use request::{WithBody, WithoutBody};
591
pub use request_ext::RequestExt;
592
pub use response::ResponseExt;
593
pub use send_body::AsSendBody;
594
595
mod agent;
596
mod body;
597
pub mod config;
598
mod error;
599
mod pool;
600
mod proxy;
601
mod query;
602
mod request;
603
mod response;
604
mod run;
605
mod send_body;
606
mod timings;
607
mod util;
608
609
pub mod unversioned;
610
use unversioned::resolver;
611
use unversioned::transport;
612
613
pub mod middleware;
614
615
#[cfg(feature = "_tls")]
616
pub mod tls;
617
618
#[cfg(feature = "cookies")]
619
mod cookies;
620
mod request_ext;
621
622
#[cfg(feature = "cookies")]
623
pub use cookies::{Cookie, CookieJar};
624
625
pub use agent::Agent;
626
pub use error::Error;
627
pub use send_body::SendBody;
628
pub use timings::Timeout;
629
630
/// Typestate variables.
631
pub mod typestate {
632
    pub use super::request::WithBody;
633
    pub use super::request::WithoutBody;
634
635
    pub use super::config::typestate::AgentScope;
636
    pub use super::config::typestate::HttpCrateScope;
637
    pub use super::config::typestate::RequestScope;
638
}
639
640
/// Run a [`http::Request<impl AsSendBody>`].
641
0
pub fn run(request: Request<impl AsSendBody>) -> Result<Response<Body>, Error> {
642
0
    let agent = Agent::new_with_defaults();
643
0
    agent.run(request)
644
0
}
645
646
/// A new [Agent] with default configuration
647
///
648
/// Agents are used to hold configuration and keep state between requests.
649
0
pub fn agent() -> Agent {
650
0
    Agent::new_with_defaults()
651
0
}
652
653
/// Make a GET request.
654
///
655
/// Run on a use-once [`Agent`].
656
#[must_use]
657
0
pub fn get<T>(uri: T) -> RequestBuilder<WithoutBody>
658
0
where
659
0
    Uri: TryFrom<T>,
660
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
661
{
662
0
    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::GET, uri)
663
0
}
664
665
/// Make a POST request.
666
///
667
/// Run on a use-once [`Agent`].
668
#[must_use]
669
0
pub fn post<T>(uri: T) -> RequestBuilder<WithBody>
670
0
where
671
0
    Uri: TryFrom<T>,
672
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
673
{
674
0
    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::POST, uri)
675
0
}
676
677
/// Make a PUT request.
678
///
679
/// Run on a use-once [`Agent`].
680
#[must_use]
681
0
pub fn put<T>(uri: T) -> RequestBuilder<WithBody>
682
0
where
683
0
    Uri: TryFrom<T>,
684
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
685
{
686
0
    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::PUT, uri)
687
0
}
688
689
/// Make a DELETE request.
690
///
691
/// Run on a use-once [`Agent`].
692
#[must_use]
693
0
pub fn delete<T>(uri: T) -> RequestBuilder<WithoutBody>
694
0
where
695
0
    Uri: TryFrom<T>,
696
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
697
{
698
0
    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::DELETE, uri)
699
0
}
700
701
/// Make a HEAD request.
702
///
703
/// Run on a use-once [`Agent`].
704
#[must_use]
705
0
pub fn head<T>(uri: T) -> RequestBuilder<WithoutBody>
706
0
where
707
0
    Uri: TryFrom<T>,
708
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
709
{
710
0
    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::HEAD, uri)
711
0
}
712
713
/// Make an OPTIONS request.
714
///
715
/// Run on a use-once [`Agent`].
716
#[must_use]
717
0
pub fn options<T>(uri: T) -> RequestBuilder<WithoutBody>
718
0
where
719
0
    Uri: TryFrom<T>,
720
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
721
{
722
0
    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::OPTIONS, uri)
723
0
}
724
725
/// Make a CONNECT request.
726
///
727
/// Run on a use-once [`Agent`].
728
#[must_use]
729
0
pub fn connect<T>(uri: T) -> RequestBuilder<WithoutBody>
730
0
where
731
0
    Uri: TryFrom<T>,
732
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
733
{
734
0
    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::CONNECT, uri)
735
0
}
736
737
/// Make a PATCH request.
738
///
739
/// Run on a use-once [`Agent`].
740
#[must_use]
741
0
pub fn patch<T>(uri: T) -> RequestBuilder<WithBody>
742
0
where
743
0
    Uri: TryFrom<T>,
744
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
745
{
746
0
    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::PATCH, uri)
747
0
}
748
749
/// Make a TRACE request.
750
///
751
/// Run on a use-once [`Agent`].
752
#[must_use]
753
0
pub fn trace<T>(uri: T) -> RequestBuilder<WithoutBody>
754
0
where
755
0
    Uri: TryFrom<T>,
756
0
    <Uri as TryFrom<T>>::Error: Into<http::Error>,
757
{
758
0
    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::TRACE, uri)
759
0
}
760
761
#[cfg(test)]
762
pub(crate) mod test {
763
    use std::{io, sync::OnceLock};
764
765
    use assert_no_alloc::AllocDisabler;
766
    use config::{Config, ConfigBuilder};
767
    use typestate::AgentScope;
768
769
    use super::*;
770
771
    #[global_allocator]
772
    // Some tests checks that we are not allocating
773
    static A: AllocDisabler = AllocDisabler;
774
775
    pub fn init_test_log() {
776
        static INIT_LOG: OnceLock<()> = OnceLock::new();
777
        INIT_LOG.get_or_init(env_logger::init);
778
    }
779
780
    #[test]
781
    fn connect_http_google() {
782
        init_test_log();
783
        let agent = Agent::new_with_defaults();
784
785
        let res = agent.get("http://www.google.com/").call().unwrap();
786
        assert_eq!(
787
            "text/html;charset=ISO-8859-1",
788
            res.headers()
789
                .get("content-type")
790
                .unwrap()
791
                .to_str()
792
                .unwrap()
793
                .replace("; ", ";")
794
        );
795
        assert_eq!(res.body().mime_type(), Some("text/html"));
796
    }
797
798
    #[test]
799
    #[cfg(feature = "rustls")]
800
    fn connect_https_google_rustls() {
801
        init_test_log();
802
        use config::Config;
803
804
        use crate::tls::{TlsConfig, TlsProvider};
805
806
        let agent: Agent = Config::builder()
807
            .tls_config(TlsConfig::builder().provider(TlsProvider::Rustls).build())
808
            .build()
809
            .into();
810
811
        let res = agent.get("https://www.google.com/").call().unwrap();
812
        assert_eq!(
813
            "text/html;charset=ISO-8859-1",
814
            res.headers()
815
                .get("content-type")
816
                .unwrap()
817
                .to_str()
818
                .unwrap()
819
                .replace("; ", ";")
820
        );
821
        assert_eq!(res.body().mime_type(), Some("text/html"));
822
    }
823
824
    #[test]
825
    #[cfg(feature = "native-tls")]
826
    fn connect_https_google_native_tls_simple() {
827
        init_test_log();
828
        use config::Config;
829
830
        use crate::tls::{TlsConfig, TlsProvider};
831
832
        let agent: Agent = Config::builder()
833
            .tls_config(
834
                TlsConfig::builder()
835
                    .provider(TlsProvider::NativeTls)
836
                    .build(),
837
            )
838
            .build()
839
            .into();
840
841
        let mut res = agent.get("https://www.google.com/").call().unwrap();
842
843
        assert_eq!(
844
            "text/html;charset=ISO-8859-1",
845
            res.headers()
846
                .get("content-type")
847
                .unwrap()
848
                .to_str()
849
                .unwrap()
850
                .replace("; ", ";")
851
        );
852
        assert_eq!(res.body().mime_type(), Some("text/html"));
853
        res.body_mut().read_to_string().unwrap();
854
    }
855
856
    #[test]
857
    #[cfg(feature = "rustls")]
858
    fn connect_https_google_rustls_webpki() {
859
        init_test_log();
860
        use crate::tls::{RootCerts, TlsConfig, TlsProvider};
861
        use config::Config;
862
863
        let agent: Agent = Config::builder()
864
            .tls_config(
865
                TlsConfig::builder()
866
                    .provider(TlsProvider::Rustls)
867
                    .root_certs(RootCerts::WebPki)
868
                    .build(),
869
            )
870
            .build()
871
            .into();
872
873
        agent.get("https://www.google.com/").call().unwrap();
874
    }
875
876
    #[test]
877
    #[cfg(feature = "native-tls")]
878
    fn connect_https_google_native_tls_webpki() {
879
        init_test_log();
880
        use crate::tls::{RootCerts, TlsConfig, TlsProvider};
881
        use config::Config;
882
883
        let agent: Agent = Config::builder()
884
            .tls_config(
885
                TlsConfig::builder()
886
                    .provider(TlsProvider::NativeTls)
887
                    .root_certs(RootCerts::WebPki)
888
                    .build(),
889
            )
890
            .build()
891
            .into();
892
893
        agent.get("https://www.google.com/").call().unwrap();
894
    }
895
896
    #[test]
897
    #[cfg(feature = "rustls")]
898
    fn connect_https_google_noverif() {
899
        init_test_log();
900
        use crate::tls::{TlsConfig, TlsProvider};
901
902
        let agent: Agent = Config::builder()
903
            .tls_config(
904
                TlsConfig::builder()
905
                    .provider(TlsProvider::Rustls)
906
                    .disable_verification(true)
907
                    .build(),
908
            )
909
            .build()
910
            .into();
911
912
        let res = agent.get("https://www.google.com/").call().unwrap();
913
        assert_eq!(
914
            "text/html;charset=ISO-8859-1",
915
            res.headers()
916
                .get("content-type")
917
                .unwrap()
918
                .to_str()
919
                .unwrap()
920
                .replace("; ", ";")
921
        );
922
        assert_eq!(res.body().mime_type(), Some("text/html"));
923
    }
924
925
    #[test]
926
    fn simple_put_content_len() {
927
        init_test_log();
928
        let mut res = put("http://httpbin.org/put").send(&[0_u8; 100]).unwrap();
929
        res.body_mut().read_to_string().unwrap();
930
    }
931
932
    #[test]
933
    fn simple_put_chunked() {
934
        init_test_log();
935
        let mut res = put("http://httpbin.org/put")
936
            // override default behavior
937
            .header("transfer-encoding", "chunked")
938
            .send(&[0_u8; 100])
939
            .unwrap();
940
        res.body_mut().read_to_string().unwrap();
941
    }
942
943
    #[test]
944
    fn simple_get() {
945
        init_test_log();
946
        let mut res = get("http://httpbin.org/get").call().unwrap();
947
        res.body_mut().read_to_string().unwrap();
948
    }
949
950
    #[test]
951
    fn query_no_slash() {
952
        init_test_log();
953
        let mut res = get("http://httpbin.org?query=foo").call().unwrap();
954
        res.body_mut().read_to_string().unwrap();
955
    }
956
957
    #[test]
958
    fn simple_head() {
959
        init_test_log();
960
        let mut res = head("http://httpbin.org/get").call().unwrap();
961
        res.body_mut().read_to_string().unwrap();
962
    }
963
964
    #[test]
965
    fn redirect_no_follow() {
966
        init_test_log();
967
        let agent: Agent = Config::builder().max_redirects(0).build().into();
968
        let mut res = agent
969
            .get("http://httpbin.org/redirect-to?url=%2Fget")
970
            .call()
971
            .unwrap();
972
        let txt = res.body_mut().read_to_string().unwrap();
973
        #[cfg(feature = "_test")]
974
        assert_eq!(txt, "You've been redirected");
975
        #[cfg(not(feature = "_test"))]
976
        assert_eq!(txt, "");
977
    }
978
979
    #[test]
980
    fn test_send_form_content_type() {
981
        init_test_log();
982
983
        // These tests verify that the methods work correctly with the test infrastructure
984
        // The actual Content-Type verification happens at the transport level
985
        let form_data = [("key1", "value1"), ("key2", "value2")];
986
        let mut res = post("http://httpbin.org/post")
987
            .header("x-verify-content-type", "application/x-www-form-urlencoded")
988
            .send_form(form_data)
989
            .unwrap();
990
991
        let _txt = res.body_mut().read_to_string().unwrap();
992
    }
993
994
    #[test]
995
    #[cfg(feature = "json")]
996
    fn test_send_json_content_type() {
997
        use serde_json::json;
998
999
        init_test_log();
1000
1001
        let data = json!({"key": "value"});
1002
        let mut res = post("http://httpbin.org/post")
1003
            .header("x-verify-content-type", "application/json; charset=utf-8")
1004
            .send_json(&data)
1005
            .unwrap();
1006
1007
        let _txt = res.body_mut().read_to_string().unwrap();
1008
    }
1009
1010
    #[test]
1011
    #[cfg(feature = "multipart")]
1012
    fn test_send_multipart_content_type() {
1013
        use crate::unversioned::multipart::Form;
1014
1015
        init_test_log();
1016
1017
        let form = Form::new()
1018
            .text("field1", "value1")
1019
            .text("field2", "value2");
1020
1021
        let mut res = post("http://httpbin.org/post")
1022
            .header("x-verify-content-type", "multipart/form-data")
1023
            .send(form)
1024
            .unwrap();
1025
1026
        let _txt = res.body_mut().read_to_string().unwrap();
1027
    }
1028
1029
    #[test]
1030
    fn redirect_max_with_error() {
1031
        init_test_log();
1032
        let agent: Agent = Config::builder().max_redirects(3).build().into();
1033
        let res = agent
1034
            .get(
1035
                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3F\
1036
                url%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D",
1037
            )
1038
            .call();
1039
        let err = res.unwrap_err();
1040
        assert_eq!(err.to_string(), "too many redirects");
1041
    }
1042
1043
    #[test]
1044
    fn redirect_max_without_error() {
1045
        init_test_log();
1046
        let agent: Agent = Config::builder()
1047
            .max_redirects(3)
1048
            .max_redirects_will_error(false)
1049
            .build()
1050
            .into();
1051
        let res = agent
1052
            .get(
1053
                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3F\
1054
                url%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D",
1055
            )
1056
            .call()
1057
            .unwrap();
1058
        assert_eq!(res.status(), 302);
1059
    }
1060
1061
    #[test]
1062
    fn redirect_follow() {
1063
        init_test_log();
1064
        let res = get("http://httpbin.org/redirect-to?url=%2Fget")
1065
            .call()
1066
            .unwrap();
1067
        let response_uri = res.get_uri();
1068
        assert_eq!(response_uri.path(), "/get")
1069
    }
1070
1071
    #[test]
1072
    #[cfg(feature = "_test")]
1073
    fn post_redirect_to_get_removes_content_length() {
1074
        // Regression test for issue #1135
1075
        // When a POST with body redirects to GET, Content-Length should not be sent
1076
        use crate::transport::{set_handler, set_handler_cb};
1077
        init_test_log();
1078
1079
        // POST endpoint that redirects to GET
1080
        set_handler(
1081
            "/post-redirect",
1082
            301,
1083
            &[("Location", "http://example.com/get")],
1084
            &[],
1085
        );
1086
1087
        // GET endpoint - verifies Content-Length header is NOT present
1088
        set_handler_cb("/get", 200, &[], b"success", |req| {
1089
            // Assert that Content-Length is not present on the GET request
1090
            assert!(
1091
                req.headers().get("content-length").is_none(),
1092
                "Content-Length header should not be present on GET request after redirect"
1093
            );
1094
        });
1095
1096
        // POST with body that redirects to GET
1097
        let mut res = post("http://example.org/post-redirect")
1098
            .send("test body")
1099
            .unwrap();
1100
1101
        assert_eq!(res.status(), 200);
1102
        let body = res.body_mut().read_to_string().unwrap();
1103
        assert_eq!(body, "success");
1104
    }
1105
1106
    #[test]
1107
    fn redirect_history_none() {
1108
        init_test_log();
1109
        let res = get("http://httpbin.org/redirect-to?url=%2Fget")
1110
            .call()
1111
            .unwrap();
1112
        let redirect_history = res.get_redirect_history();
1113
        assert_eq!(redirect_history, None)
1114
    }
1115
1116
    #[test]
1117
    fn redirect_history_some() {
1118
        init_test_log();
1119
        let agent: Agent = Config::builder()
1120
            .max_redirects(3)
1121
            .max_redirects_will_error(false)
1122
            .save_redirect_history(true)
1123
            .build()
1124
            .into();
1125
        let res = agent
1126
            .get("http://httpbin.org/redirect-to?url=%2Fget")
1127
            .call()
1128
            .unwrap();
1129
        let redirect_history = res.get_redirect_history();
1130
        assert_eq!(
1131
            redirect_history,
1132
            Some(
1133
                vec![
1134
                    "http://httpbin.org/redirect-to?url=%2Fget".parse().unwrap(),
1135
                    "http://httpbin.org/get".parse().unwrap()
1136
                ]
1137
                .as_ref()
1138
            )
1139
        );
1140
        let res = agent
1141
            .get(
1142
                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3F\
1143
                url%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D",
1144
            )
1145
            .call()
1146
            .unwrap();
1147
        let redirect_history = res.get_redirect_history();
1148
        assert_eq!(
1149
            redirect_history,
1150
            Some(vec![
1151
                "http://httpbin.org/redirect-to?url=%2Fredirect-to%3Furl%3D%2Fredirect-to%3Furl%3D%252Fredirect-to%253Furl%253D".parse().unwrap(),
1152
                "http://httpbin.org/redirect-to?url=/redirect-to?url=%2Fredirect-to%3Furl%3D".parse().unwrap(),
1153
                "http://httpbin.org/redirect-to?url=/redirect-to?url=".parse().unwrap(),
1154
                "http://httpbin.org/redirect-to?url=".parse().unwrap(),
1155
            ].as_ref())
1156
        );
1157
        let res = agent.get("https://www.google.com/").call().unwrap();
1158
        let redirect_history = res.get_redirect_history();
1159
        assert_eq!(
1160
            redirect_history,
1161
            Some(vec!["https://www.google.com/".parse().unwrap()].as_ref())
1162
        );
1163
    }
1164
1165
    #[test]
1166
    fn connect_https_invalid_name() {
1167
        let result = get("https://example.com{REQUEST_URI}/").call();
1168
        let err = result.unwrap_err();
1169
        assert!(matches!(err, Error::Http(_)));
1170
        assert_eq!(err.to_string(), "http: invalid uri character");
1171
    }
1172
1173
    #[test]
1174
    fn post_big_body_chunked() {
1175
        init_test_log();
1176
        // https://github.com/algesten/ureq/issues/879
1177
        let mut data = io::Cursor::new(vec![42; 153_600]);
1178
        post("http://httpbin.org/post")
1179
            .content_type("application/octet-stream")
1180
            .send(SendBody::from_reader(&mut data))
1181
            .expect("to send correctly");
1182
    }
1183
1184
    #[test]
1185
    #[cfg(not(feature = "_test"))]
1186
    fn post_array_body_sends_content_length() {
1187
        init_test_log();
1188
        let mut response = post("http://httpbin.org/post")
1189
            .content_type("application/octet-stream")
1190
            .send(vec![42; 123])
1191
            .expect("to send correctly");
1192
1193
        let ret = response.body_mut().read_to_string().unwrap();
1194
        assert!(ret.contains("\"Content-Length\": \"123\""));
1195
    }
1196
1197
    #[test]
1198
    #[cfg(not(feature = "_test"))]
1199
    fn post_file_sends_file_length() {
1200
        init_test_log();
1201
1202
        let bytes = include_bytes!("../LICENSE-MIT");
1203
        let file = std::fs::File::open("LICENSE-MIT").unwrap();
1204
1205
        let mut response = post("http://httpbin.org/post")
1206
            .content_type("application/octet-stream")
1207
            .send(file)
1208
            .expect("to send correctly");
1209
1210
        let ret = response.body_mut().read_to_string().unwrap();
1211
        assert!(ret.contains(&format!("\"Content-Length\": \"{}\"", bytes.len())));
1212
    }
1213
1214
    #[test]
1215
    #[cfg(not(feature = "_test"))]
1216
    fn username_password_from_uri() {
1217
        init_test_log();
1218
        let mut res = get("https://martin:secret@httpbin.org/get").call().unwrap();
1219
        let body = res.body_mut().read_to_string().unwrap();
1220
        assert!(body.contains("Basic bWFydGluOnNlY3JldA=="));
1221
    }
1222
1223
    #[test]
1224
    #[cfg(all(feature = "cookies", feature = "_test"))]
1225
    fn store_response_cookies() {
1226
        let agent = Agent::new_with_defaults();
1227
        let _ = agent.get("https://www.google.com").call().unwrap();
1228
1229
        let mut all: Vec<_> = agent
1230
            .cookie_jar_lock()
1231
            .iter()
1232
            .map(|c| c.name().to_string())
1233
            .collect();
1234
1235
        all.sort();
1236
1237
        assert_eq!(all, ["AEC", "__Secure-ENID"])
1238
    }
1239
1240
    #[test]
1241
    #[cfg(all(feature = "cookies", feature = "_test"))]
1242
    fn send_request_cookies() {
1243
        init_test_log();
1244
1245
        let agent = Agent::new_with_defaults();
1246
        let uri = Uri::from_static("http://cookie.test/cookie-test");
1247
        let uri2 = Uri::from_static("http://cookie2.test/cookie-test");
1248
1249
        let mut jar = agent.cookie_jar_lock();
1250
        jar.insert(Cookie::parse("a=1", &uri).unwrap(), &uri)
1251
            .unwrap();
1252
        jar.insert(Cookie::parse("b=2", &uri).unwrap(), &uri)
1253
            .unwrap();
1254
        jar.insert(Cookie::parse("c=3", &uri2).unwrap(), &uri2)
1255
            .unwrap();
1256
1257
        jar.release();
1258
1259
        let _ = agent.get("http://cookie.test/cookie-test").call().unwrap();
1260
    }
1261
1262
    #[test]
1263
    #[cfg(all(feature = "_test", not(feature = "cookies")))]
1264
    fn partial_redirect_when_following() {
1265
        init_test_log();
1266
        // this should work because we follow the redirect and go to /get
1267
        get("http://my-host.com/partial-redirect").call().unwrap();
1268
    }
1269
1270
    #[test]
1271
    #[cfg(feature = "_test")]
1272
    fn partial_redirect_when_not_following() {
1273
        init_test_log();
1274
        // this should fail because we are not following redirects, and the
1275
        // response is partial before the server is hanging up
1276
        get("http://my-host.com/partial-redirect")
1277
            .config()
1278
            .max_redirects(0)
1279
            .build()
1280
            .call()
1281
            .unwrap_err();
1282
    }
1283
1284
    #[test]
1285
    #[cfg(feature = "_test")]
1286
    fn http_connect_proxy() {
1287
        init_test_log();
1288
1289
        let proxy = Proxy::new("http://my_proxy:1234/connect-proxy").unwrap();
1290
1291
        let agent = Agent::config_builder()
1292
            .proxy(Some(proxy))
1293
            .build()
1294
            .new_agent();
1295
1296
        let mut res = agent.get("http://httpbin.org/get").call().unwrap();
1297
        res.body_mut().read_to_string().unwrap();
1298
    }
1299
1300
    #[test]
1301
    #[cfg(all(feature = "_test", feature = "_ring"))]
1302
    fn https_connect_proxy_to_https_target() {
1303
        init_test_log();
1304
1305
        for provider in [
1306
            tls::TlsProvider::Rustls,
1307
            #[cfg(feature = "native-tls-no-default")]
1308
            tls::TlsProvider::NativeTls,
1309
        ] {
1310
            let proxy = Proxy::new("https://proxy.test/https-connect-proxy").unwrap();
1311
            let tls = tls::TlsConfig::builder()
1312
                .provider(provider)
1313
                .disable_verification(true)
1314
                .build();
1315
            let agent = Agent::config_builder()
1316
                .proxy(Some(proxy))
1317
                .tls_config(tls)
1318
                .build()
1319
                .new_agent();
1320
1321
            let mut response = agent
1322
                .get("https://example.com/through-https-proxy")
1323
                .call()
1324
                .unwrap();
1325
1326
            assert_eq!(response.body_mut().read_to_string().unwrap(), "ok");
1327
        }
1328
    }
1329
1330
    #[test]
1331
    fn ensure_reasonable_stack_sizes() {
1332
        macro_rules! ensure {
1333
            ($type:ty, $size:tt) => {
1334
                let sz = std::mem::size_of::<$type>();
1335
                // println!("{}: {}", stringify!($type), sz);
1336
                assert!(
1337
                    sz <= $size,
1338
                    "Stack size of {} is too big {} > {}",
1339
                    stringify!($type),
1340
                    sz,
1341
                    $size
1342
                );
1343
            };
1344
        }
1345
1346
        ensure!(RequestBuilder<WithoutBody>, 400); // 304
1347
        ensure!(Agent, 100); // 32
1348
        ensure!(Config, 400); // 320
1349
        ensure!(ConfigBuilder<AgentScope>, 400); // 320
1350
        ensure!(Response<Body>, 250); // 136
1351
        ensure!(Body, 50); // 24
1352
    }
1353
1354
    #[test]
1355
    #[cfg(feature = "_test")]
1356
    fn limit_max_response_header_size() {
1357
        init_test_log();
1358
        let err = get("http://httpbin.org/get")
1359
            .config()
1360
            .max_response_header_size(5)
1361
            .build()
1362
            .call()
1363
            .unwrap_err();
1364
        assert!(matches!(err, Error::LargeResponseHeader(65, 5)));
1365
    }
1366
1367
    #[test]
1368
    #[cfg(feature = "_test")]
1369
    fn propfind_with_body() {
1370
        init_test_log();
1371
1372
        // https://github.com/algesten/ureq/issues/1034
1373
        let request = http::Request::builder()
1374
            .method("PROPFIND")
1375
            .uri("https://www.google.com/")
1376
            .body("Some really cool body")
1377
            .unwrap();
1378
1379
        let _ = Agent::config_builder()
1380
            .allow_non_standard_methods(true)
1381
            .build()
1382
            .new_agent()
1383
            .run(request)
1384
            .unwrap();
1385
    }
1386
1387
    #[test]
1388
    #[cfg(feature = "_test")]
1389
    fn non_standard_method() {
1390
        init_test_log();
1391
        let method = Method::from_bytes(b"FNORD").unwrap();
1392
1393
        let req = Request::builder()
1394
            .method(method)
1395
            .uri("http://httpbin.org/fnord")
1396
            .body(())
1397
            .unwrap();
1398
1399
        let agent = Agent::new_with_defaults();
1400
1401
        let req = agent
1402
            .configure_request(req)
1403
            .allow_non_standard_methods(true)
1404
            .build();
1405
1406
        agent.run(req).unwrap();
1407
    }
1408
1409
    #[test]
1410
    #[cfg(feature = "_test")]
1411
    fn chunk_abort() {
1412
        init_test_log();
1413
        let mut res = get("http://my-fine-server/1chunk-abort").call().unwrap();
1414
        let body = res.body_mut().read_to_string().unwrap();
1415
        assert_eq!(body, "OK");
1416
        let mut res = get("http://my-fine-server/2chunk-abort").call().unwrap();
1417
        let body = res.body_mut().read_to_string().unwrap();
1418
        assert_eq!(body, "OK");
1419
        let mut res = get("http://my-fine-server/3chunk-abort").call().unwrap();
1420
        let body = res.body_mut().read_to_string().unwrap();
1421
        assert_eq!(body, "OK");
1422
        let mut res = get("http://my-fine-server/4chunk-abort").call().unwrap();
1423
        let body = res.body_mut().read_to_string().unwrap();
1424
        assert_eq!(body, "OK");
1425
    }
1426
1427
    // This doesn't need to run, just compile.
1428
    fn _ensure_send_sync() {
1429
        fn is_send(_t: impl Send) {}
1430
        fn is_sync(_t: impl Sync) {}
1431
1432
        // Agent
1433
        is_send(Agent::new_with_defaults());
1434
        is_sync(Agent::new_with_defaults());
1435
1436
        // ResponseBuilder
1437
        is_send(get("https://example.test"));
1438
        is_sync(get("https://example.test"));
1439
1440
        let data = vec![0_u8, 1, 2, 3, 4];
1441
1442
        // Response<Body> via ResponseBuilder
1443
        is_send(post("https://example.test").send(&data));
1444
        is_sync(post("https://example.test").send(&data));
1445
1446
        // Request<impl AsBody>
1447
        is_send(Request::post("https://yaz").body(&data).unwrap());
1448
        is_sync(Request::post("https://yaz").body(&data).unwrap());
1449
1450
        // Response<Body> via Agent::run
1451
        is_send(run(Request::post("https://yaz").body(&data).unwrap()));
1452
        is_sync(run(Request::post("https://yaz").body(&data).unwrap()));
1453
1454
        // Response<BodyReader<'a>>
1455
        let mut response = post("https://yaz").send(&data).unwrap();
1456
        let shared_reader = response.body_mut().as_reader();
1457
        is_send(shared_reader);
1458
        let shared_reader = response.body_mut().as_reader();
1459
        is_sync(shared_reader);
1460
1461
        // Response<BodyReader<'static>>
1462
        let response = post("https://yaz").send(&data).unwrap();
1463
        let owned_reader = response.into_parts().1.into_reader();
1464
        is_send(owned_reader);
1465
        let response = post("https://yaz").send(&data).unwrap();
1466
        let owned_reader = response.into_parts().1.into_reader();
1467
        is_sync(owned_reader);
1468
1469
        let err = Error::HostNotFound;
1470
        is_send(err);
1471
        let err = Error::HostNotFound;
1472
        is_sync(err);
1473
    }
1474
}
1475
1476
#[cfg(test)]
1477
mod io_timeout_tests;
1478
1479
#[cfg(test)]
1480
mod shutdown_tests;