Skip to main content

ureq/
lib.rs

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//! ## 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 disabled 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]
536extern crate log;
537
538use std::convert::TryFrom;
539
540/// Re-exported http-crate.
541pub use ureq_proto::http;
542
543pub use body::{Body, BodyBuilder, BodyReader, BodyWithConfig};
544use http::Method;
545use http::{Request, Response, Uri};
546pub use proxy::{Proxy, ProxyBuilder, ProxyProtocol};
547pub use request::RequestBuilder;
548use request::{WithBody, WithoutBody};
549pub use request_ext::RequestExt;
550pub use response::ResponseExt;
551pub use send_body::AsSendBody;
552
553mod agent;
554mod body;
555pub mod config;
556mod error;
557mod pool;
558mod proxy;
559mod query;
560mod request;
561mod response;
562mod run;
563mod send_body;
564mod timings;
565mod util;
566
567pub mod unversioned;
568use unversioned::resolver;
569use unversioned::transport;
570
571pub mod middleware;
572
573#[cfg(feature = "_tls")]
574pub mod tls;
575
576#[cfg(feature = "cookies")]
577mod cookies;
578mod request_ext;
579
580#[cfg(feature = "cookies")]
581pub use cookies::{Cookie, CookieJar};
582
583pub use agent::Agent;
584pub use error::Error;
585pub use send_body::SendBody;
586pub use timings::Timeout;
587
588/// Typestate variables.
589pub 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>`].
599pub fn run(request: Request<impl AsSendBody>) -> Result<Response<Body>, Error> {
600    let agent = Agent::new_with_defaults();
601    agent.run(request)
602}
603
604/// A new [Agent] with default configuration
605///
606/// Agents are used to hold configuration and keep state between requests.
607pub fn agent() -> Agent {
608    Agent::new_with_defaults()
609}
610
611/// Make a GET request.
612///
613/// Run on a use-once [`Agent`].
614#[must_use]
615pub fn get<T>(uri: T) -> RequestBuilder<WithoutBody>
616where
617    Uri: TryFrom<T>,
618    <Uri as TryFrom<T>>::Error: Into<http::Error>,
619{
620    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::GET, uri)
621}
622
623/// Make a POST request.
624///
625/// Run on a use-once [`Agent`].
626#[must_use]
627pub fn post<T>(uri: T) -> RequestBuilder<WithBody>
628where
629    Uri: TryFrom<T>,
630    <Uri as TryFrom<T>>::Error: Into<http::Error>,
631{
632    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::POST, uri)
633}
634
635/// Make a PUT request.
636///
637/// Run on a use-once [`Agent`].
638#[must_use]
639pub fn put<T>(uri: T) -> RequestBuilder<WithBody>
640where
641    Uri: TryFrom<T>,
642    <Uri as TryFrom<T>>::Error: Into<http::Error>,
643{
644    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::PUT, uri)
645}
646
647/// Make a DELETE request.
648///
649/// Run on a use-once [`Agent`].
650#[must_use]
651pub fn delete<T>(uri: T) -> RequestBuilder<WithoutBody>
652where
653    Uri: TryFrom<T>,
654    <Uri as TryFrom<T>>::Error: Into<http::Error>,
655{
656    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::DELETE, uri)
657}
658
659/// Make a HEAD request.
660///
661/// Run on a use-once [`Agent`].
662#[must_use]
663pub fn head<T>(uri: T) -> RequestBuilder<WithoutBody>
664where
665    Uri: TryFrom<T>,
666    <Uri as TryFrom<T>>::Error: Into<http::Error>,
667{
668    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::HEAD, uri)
669}
670
671/// Make an OPTIONS request.
672///
673/// Run on a use-once [`Agent`].
674#[must_use]
675pub fn options<T>(uri: T) -> RequestBuilder<WithoutBody>
676where
677    Uri: TryFrom<T>,
678    <Uri as TryFrom<T>>::Error: Into<http::Error>,
679{
680    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::OPTIONS, uri)
681}
682
683/// Make a CONNECT request.
684///
685/// Run on a use-once [`Agent`].
686#[must_use]
687pub fn connect<T>(uri: T) -> RequestBuilder<WithoutBody>
688where
689    Uri: TryFrom<T>,
690    <Uri as TryFrom<T>>::Error: Into<http::Error>,
691{
692    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::CONNECT, uri)
693}
694
695/// Make a PATCH request.
696///
697/// Run on a use-once [`Agent`].
698#[must_use]
699pub fn patch<T>(uri: T) -> RequestBuilder<WithBody>
700where
701    Uri: TryFrom<T>,
702    <Uri as TryFrom<T>>::Error: Into<http::Error>,
703{
704    RequestBuilder::<WithBody>::new(Agent::new_with_defaults(), Method::PATCH, uri)
705}
706
707/// Make a TRACE request.
708///
709/// Run on a use-once [`Agent`].
710#[must_use]
711pub fn trace<T>(uri: T) -> RequestBuilder<WithoutBody>
712where
713    Uri: TryFrom<T>,
714    <Uri as TryFrom<T>>::Error: Into<http::Error>,
715{
716    RequestBuilder::<WithoutBody>::new(Agent::new_with_defaults(), Method::TRACE, uri)
717}
718
719#[cfg(test)]
720pub(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    #[cfg(all(feature = "_test", feature = "_ring"))]
1260    fn https_connect_proxy_to_https_target() {
1261        init_test_log();
1262
1263        let proxy = Proxy::new("https://proxy.test/https-connect-proxy").unwrap();
1264        let tls = tls::TlsConfig::builder().disable_verification(true).build();
1265        let agent = Agent::config_builder()
1266            .proxy(Some(proxy))
1267            .tls_config(tls)
1268            .build()
1269            .new_agent();
1270
1271        let mut response = agent
1272            .get("https://example.com/through-https-proxy")
1273            .call()
1274            .unwrap();
1275
1276        assert_eq!(response.body_mut().read_to_string().unwrap(), "ok");
1277    }
1278
1279    #[test]
1280    fn ensure_reasonable_stack_sizes() {
1281        macro_rules! ensure {
1282            ($type:ty, $size:tt) => {
1283                let sz = std::mem::size_of::<$type>();
1284                // println!("{}: {}", stringify!($type), sz);
1285                assert!(
1286                    sz <= $size,
1287                    "Stack size of {} is too big {} > {}",
1288                    stringify!($type),
1289                    sz,
1290                    $size
1291                );
1292            };
1293        }
1294
1295        ensure!(RequestBuilder<WithoutBody>, 400); // 304
1296        ensure!(Agent, 100); // 32
1297        ensure!(Config, 400); // 320
1298        ensure!(ConfigBuilder<AgentScope>, 400); // 320
1299        ensure!(Response<Body>, 250); // 136
1300        ensure!(Body, 50); // 24
1301    }
1302
1303    #[test]
1304    #[cfg(feature = "_test")]
1305    fn limit_max_response_header_size() {
1306        init_test_log();
1307        let err = get("http://httpbin.org/get")
1308            .config()
1309            .max_response_header_size(5)
1310            .build()
1311            .call()
1312            .unwrap_err();
1313        assert!(matches!(err, Error::LargeResponseHeader(65, 5)));
1314    }
1315
1316    #[test]
1317    #[cfg(feature = "_test")]
1318    fn propfind_with_body() {
1319        init_test_log();
1320
1321        // https://github.com/algesten/ureq/issues/1034
1322        let request = http::Request::builder()
1323            .method("PROPFIND")
1324            .uri("https://www.google.com/")
1325            .body("Some really cool body")
1326            .unwrap();
1327
1328        let _ = Agent::config_builder()
1329            .allow_non_standard_methods(true)
1330            .build()
1331            .new_agent()
1332            .run(request)
1333            .unwrap();
1334    }
1335
1336    #[test]
1337    #[cfg(feature = "_test")]
1338    fn non_standard_method() {
1339        init_test_log();
1340        let method = Method::from_bytes(b"FNORD").unwrap();
1341
1342        let req = Request::builder()
1343            .method(method)
1344            .uri("http://httpbin.org/fnord")
1345            .body(())
1346            .unwrap();
1347
1348        let agent = Agent::new_with_defaults();
1349
1350        let req = agent
1351            .configure_request(req)
1352            .allow_non_standard_methods(true)
1353            .build();
1354
1355        agent.run(req).unwrap();
1356    }
1357
1358    #[test]
1359    #[cfg(feature = "_test")]
1360    fn chunk_abort() {
1361        init_test_log();
1362        let mut res = get("http://my-fine-server/1chunk-abort").call().unwrap();
1363        let body = res.body_mut().read_to_string().unwrap();
1364        assert_eq!(body, "OK");
1365        let mut res = get("http://my-fine-server/2chunk-abort").call().unwrap();
1366        let body = res.body_mut().read_to_string().unwrap();
1367        assert_eq!(body, "OK");
1368        let mut res = get("http://my-fine-server/3chunk-abort").call().unwrap();
1369        let body = res.body_mut().read_to_string().unwrap();
1370        assert_eq!(body, "OK");
1371        let mut res = get("http://my-fine-server/4chunk-abort").call().unwrap();
1372        let body = res.body_mut().read_to_string().unwrap();
1373        assert_eq!(body, "OK");
1374    }
1375
1376    // This doesn't need to run, just compile.
1377    fn _ensure_send_sync() {
1378        fn is_send(_t: impl Send) {}
1379        fn is_sync(_t: impl Sync) {}
1380
1381        // Agent
1382        is_send(Agent::new_with_defaults());
1383        is_sync(Agent::new_with_defaults());
1384
1385        // ResponseBuilder
1386        is_send(get("https://example.test"));
1387        is_sync(get("https://example.test"));
1388
1389        let data = vec![0_u8, 1, 2, 3, 4];
1390
1391        // Response<Body> via ResponseBuilder
1392        is_send(post("https://example.test").send(&data));
1393        is_sync(post("https://example.test").send(&data));
1394
1395        // Request<impl AsBody>
1396        is_send(Request::post("https://yaz").body(&data).unwrap());
1397        is_sync(Request::post("https://yaz").body(&data).unwrap());
1398
1399        // Response<Body> via Agent::run
1400        is_send(run(Request::post("https://yaz").body(&data).unwrap()));
1401        is_sync(run(Request::post("https://yaz").body(&data).unwrap()));
1402
1403        // Response<BodyReader<'a>>
1404        let mut response = post("https://yaz").send(&data).unwrap();
1405        let shared_reader = response.body_mut().as_reader();
1406        is_send(shared_reader);
1407        let shared_reader = response.body_mut().as_reader();
1408        is_sync(shared_reader);
1409
1410        // Response<BodyReader<'static>>
1411        let response = post("https://yaz").send(&data).unwrap();
1412        let owned_reader = response.into_parts().1.into_reader();
1413        is_send(owned_reader);
1414        let response = post("https://yaz").send(&data).unwrap();
1415        let owned_reader = response.into_parts().1.into_reader();
1416        is_sync(owned_reader);
1417
1418        let err = Error::HostNotFound;
1419        is_send(err);
1420        let err = Error::HostNotFound;
1421        is_sync(err);
1422    }
1423}