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