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