Coverage Report

Created: 2026-03-14 06:47

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