reqwest-0.11.27/.cargo_vcs_info.json0000644000000001360000000000100126760ustar { "git": { "sha1": "cf69fd4bfe22855d576497eb94e9eb549e742475" }, "path_in_vcs": "" }reqwest-0.11.27/.github/FUNDING.yml000064400000000000000000000000261046102023000146410ustar 00000000000000github: [seanmonstar] reqwest-0.11.27/.github/dependabot.yml000064400000000000000000000006721046102023000156630ustar 00000000000000version: 2 # Only enable cargo, turn off npm from wasm example updates: - package-ecosystem: "github-actions" # Workflow files stored in the # default location of `.github/workflows` directory: "/" schedule: interval: "daily" - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" # disable regular version updates, security updates are unaffected open-pull-requests-limit: 0 reqwest-0.11.27/.github/workflows/ci.yml000064400000000000000000000227601046102023000162100ustar 00000000000000name: CI on: pull_request: push: branches: - master env: REQWEST_TEST_BODY_FULL: 1 RUST_BACKTRACE: 1 CARGO_INCREMENTAL: 0 CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse jobs: ci-pass: name: CI is green runs-on: ubuntu-latest needs: - style - test - unstable - nightly - msrv - android - wasm - docs steps: - run: exit 0 style: name: Check Style runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install rust uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: cargo fmt -- --check run: cargo fmt -- --check - name: temporary workaround - fmt all files under src # Workaround for rust-lang/cargo#7732 run: cargo fmt -- --check $(find . -name '*.rs' -print) test: name: ${{ matrix.name }} needs: [style] runs-on: ${{ matrix.os || 'ubuntu-latest' }} # The build matrix does not yet support 'allow failures' at job level. # See `jobs.nightly` for the active nightly job definition. strategy: matrix: name: - linux / stable - linux / beta # - linux / nightly - macOS / stable - windows / stable-x86_64-msvc - windows / stable-i686-msvc - windows / stable-x86_64-gnu - windows / stable-i686-gnu - "feat.: default-tls disabled" - "feat.: rustls-tls" - "feat.: rustls-tls-manual-roots" - "feat.: native-tls" - "feat.: default-tls and rustls-tls" - "feat.: cookies" - "feat.: blocking" - "feat.: gzip" - "feat.: brotli" - "feat.: deflate" - "feat.: json" - "feat.: multipart" - "feat.: stream" - "feat.: socks/default-tls" - "feat.: socks/rustls-tls" - "feat.: hickory-dns" include: - name: linux / stable test-features: "--features __internal_proxy_sys_no_cache" - name: linux / beta rust: beta test-features: "--features __internal_proxy_sys_no_cache" # - name: linux / nightly # rust: nightly # test-features: "--features __internal_proxy_sys_no_cache" - name: macOS / stable os: macOS-latest test-features: "--features __internal_proxy_sys_no_cache" - name: windows / stable-x86_64-msvc os: windows-latest target: x86_64-pc-windows-msvc features: "--features blocking,gzip,brotli,deflate,json,multipart" - name: windows / stable-i686-msvc os: windows-latest target: i686-pc-windows-msvc features: "--features blocking,gzip,brotli,deflate,json,multipart" - name: windows / stable-x86_64-gnu os: windows-latest rust: stable-x86_64-pc-windows-gnu target: x86_64-pc-windows-gnu features: "--features blocking,gzip,brotli,deflate,json,multipart" package_name: mingw-w64-x86_64-gcc mingw64_path: "C:\\msys64\\mingw64\\bin" - name: windows / stable-i686-gnu os: windows-latest rust: stable-i686-pc-windows-gnu target: i686-pc-windows-gnu features: "--features blocking,gzip,brotli,deflate,json,multipart" package_name: mingw-w64-i686-gcc mingw64_path: "C:\\msys64\\mingw32\\bin" - name: "feat.: default-tls disabled" features: "--no-default-features" - name: "feat.: rustls-tls" features: "--no-default-features --features rustls-tls" - name: "feat.: rustls-tls-manual-roots" features: "--no-default-features --features rustls-tls-manual-roots" - name: "feat.: native-tls" features: "--features native-tls" - name: "feat.: default-tls and rustls-tls" features: "--features rustls-tls" - name: "feat.: cookies" features: "--features cookies" - name: "feat.: blocking" features: "--features blocking" - name: "feat.: gzip" features: "--features gzip" - name: "feat.: brotli" features: "--features brotli" - name: "feat.: deflate" features: "--features deflate" - name: "feat.: json" features: "--features json" - name: "feat.: multipart" features: "--features multipart" - name: "feat.: stream" features: "--features stream" - name: "feat.: socks/default-tls" features: "--features socks" - name: "feat.: socks/rustls-tls" features: "--features socks,rustls-tls" - name: "feat.: hickory-dns" features: "--features hickory-dns" steps: - name: Checkout uses: actions/checkout@v4 - name: Install rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust || 'stable' }} targets: ${{ matrix.target }} - name: Add mingw-w64 to path for i686-gnu run: | echo "${{ matrix.mingw64_path }}" >> $GITHUB_PATH echo "C:\msys64\usr\bin" >> $GITHUB_PATH if: matrix.mingw64_path shell: bash - name: Update gcc if: matrix.package_name run: pacman.exe -Sy --noconfirm ${{ matrix.package_name }} - name: Create Cargo.lock run: cargo update - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@v2 with: tool: cargo-nextest - name: Run tests run: | set -euxo pipefail cargo nextest run --locked --workspace ${{ matrix.features }} ${{ matrix.test-features }} cargo test --locked --workspace --doc ${{ matrix.features }} ${{ matrix.test-features }} shell: bash unstable: name: "unstable features" needs: [style] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install rust uses: dtolnay/rust-toolchain@master with: toolchain: 'stable' - name: Check run: RUSTFLAGS="--cfg reqwest_unstable" cargo check --features http3 docs: name: Docs runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Check documentation env: RUSTDOCFLAGS: --cfg reqwest_unstable -D warnings run: cargo doc --no-deps --document-private-items --all-features # Separate build job for nightly because of the missing feature for allowed failures at # job level. See `jobs.build.strategy.matrix`. nightly: name: linux / nightly needs: [style] # Use Ubuntu 20.04 here, because latest (22.04) uses OpenSSL v3. # Currently OpenSSL v3 causes compile issues with openssl-sys runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v4 - name: Install rust uses: dtolnay/rust-toolchain@nightly # - name: Build # run: cargo build # - name: Test # run: cargo test --features __internal_proxy_sys_no_cache -- --test-threads=1 - name: Check minimal versions env: RUSTFLAGS: --cfg reqwest_unstable # Make sure proc-macro2 v1.0.60 is used # See https://github.com/rust-lang/rust/issues/113152 run: | cargo clean cargo update -Z minimal-versions cargo update -p proc-macro2 --precise 1.0.60 cargo check cargo check --all-features msrv: name: MSRV needs: [style] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Get MSRV package metadata id: metadata run: cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' >> $GITHUB_OUTPUT - name: Install rust (${{ steps.metadata.outputs.msrv }}) uses: dtolnay/rust-toolchain@master with: toolchain: ${{ steps.metadata.outputs.msrv }} - name: Fix log and tokio versions run: | cargo update cargo update -p log --precise 0.4.18 cargo update -p tokio --precise 1.29.1 - uses: Swatinem/rust-cache@v2 - name: Check run: cargo check android: name: Android needs: [style] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install rust uses: dtolnay/rust-toolchain@stable with: target: aarch64-linux-android - name: Build # disable default-tls feature since cross-compiling openssl is dragons run: cargo build --target aarch64-linux-android --no-default-features wasm: name: WASM needs: [style] runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install rust uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - name: Check run: cargo check --target wasm32-unknown-unknown - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Wasm-pack test firefox run: wasm-pack test --headless --firefox - name: Wasm-pack test chrome run: wasm-pack test --headless --chrome reqwest-0.11.27/.gitignore000064400000000000000000000000351046102023000134540ustar 00000000000000target Cargo.lock *.swp .ideareqwest-0.11.27/CHANGELOG.md000064400000000000000000000723421046102023000133070ustar 00000000000000## v0.11.27 - Add `hickory-dns` feature, deprecating `trust-dns`. - (wasm) Fix `Form::text()` to not set octet-stream for plain text fields. ## v0.11.26 - Revert `system-configuration` upgrade, which broke MSRV on macOS. ## v0.11.25 - Fix `Certificate::from_pem_bundle()` parsing. - Fix Apple linker errors from detecting system proxies. ## v0.11.24 - Add `Certificate::from_pem_bundle()` to add a bundle. - Add `http3_prior_knowledge()` to blocking client builder. - Remove `Sync` bounds requirement for `Body::wrap_stream()`. - Fix HTTP/2 to retry `REFUSED_STREAM` requests. - Fix instances of converting `Url` to `Uri` that could panic. ## v0.11.23 - Add `Proxy::custom_http_auth(val)` for setting the raw `Proxy-Authorization` header when connecting to proxies. - Fix redirect to reject locations that are not `http://` or `https://`. - Fix setting `nodelay` when TLS is enabled but URL is HTTP. - (wasm) Add `ClientBuilder::user_agent(val)`. - (wasm) add `multipart::Form::headers(headers)`. ## v0.11.22 - Fix compilation on Windows when `trust-dns` is enabled. ## v0.11.21 - Add automatically detecting macOS proxy settings. - Add `ClientBuilder::tls_info(bool)`, which will put `tls::TlsInfo` into the response extensions. - Fix trust-dns resolver from possible hangs. - Fix connect timeout to be split among multiple IP addresses. ## v0.11.20 - Fix `deflate` decompression back to using zlib, as outlined in the spec. ## v0.11.19 - Add `ClientBuilder::http1_ignore_invalid_headers_in_responses()` option. - Add `ClientBuilder::http1_allow_spaces_after_header_name_in_responses()` option. - Add support for `ALL_PROXY` environment variable. - Add support for `use_preconfigured_tls` when combined with HTTP/3. - Fix `deflate` decompression from using the zlib decoder. - Fix `Response::{text, text_with_charset}()` to strip BOM characters. - Fix a panic when HTTP/3 is used if UDP isn't able to connect. - Fix some dependencies for HTTP/3. - Increase MSRV to 1.63. ## v0.11.18 - Fix `RequestBuilder::json()` method from overriding a previously set `content-type` header. An existing value will be left in place. - Upgrade internal dependencies for rustls and compression. ## v0.11.17 - Upgrade internal dependencies of Experimental HTTP/3 to use quinn v0.9 - (wasm) Fix blob url support ## v0.11.16 - Chore: set MSRV in `Cargo.toml`. - Docs: fix build on docs.rs ## v0.11.15 - Add `RequestBuilder` methods to split and reconstruct from its parts. - Add experimental HTTP/3 support. - Fix `connection_verbose` to log `write_vectored` calls. - (wasm) Make requests actually cancel if the future is dropped. ## v0.11.14 - Adds `Proxy::no_proxy(url)` that works like the NO_PROXY environment variable. - Adds `multipart::Part::headers(headers)` method to add custom headers. - (wasm) Add `Response::bytes_stream()`. - Perf: several internal optimizations reducing copies and memory allocations. ## v0.11.13 - Add `ClientBuilder::dns_resolver()` option for custom DNS resolvers. - Add `ClientBuilder::tls_sni(bool)` option to enable or disable TLS Server Name Indication. - Add `Identity::from_pkcs8_pem()` constructor when using `native-tls`. - Fix `redirect::Policy::limited(0)` from following any redirects. ## v0.11.12 - Add `ClientBuilder::resolve_to_addrs()` which allows a slice of IP addresses to be specified for a single host. - Add `Response::upgrade()` to await whether the server agrees to an HTTP upgrade. ## v0.11.11 - Add HTTP/2 keep-alive configuration methods on `ClientBuilder`. - Add `ClientBuilder::http1_allow_obsolete_multiline_headers_in_responses()`. - Add `impl Service` for `Client` and `&'_ Client`. - (wasm) Add `RequestBuilder::basic_auth()`. - Fix `RequestBuilder::header` to not override `sensitive` if user explicitly set on a `HeaderValue`. - Fix rustls parsing of elliptic curve private keys. - Fix Proxy URL parsing of some invalid targets. ## v0.11.10 - Add `Error::url()` to access the URL of an error. - Add `Response::extensions()` to access the `http::Extensions` of a response. - Fix `rustls-native-certs` to log an error instead of panicking when loading an invalid system certificate. - Fix passing Basic Authorization header to proxies. ## v0.11.9 - Add `ClientBuilder::http09_responses(bool)` option to allow receiving HTTP/0.9 responses. - Fix HTTP/2 to retry requests interrupted by an HTTP/2 graceful shutdown. - Fix proxy loading from environment variables to ignore empty values. ## v0.11.8 - Update internal webpki-roots dependency. ## v0.11.7 - Add `blocking::ClientBuilder::resolve()` option, matching the async builder. - Implement `From` for `Body`. - Fix `blocking` request-scoped timeout applying to bodies as well. - (wasm) Fix request bodies using multipart vs formdata. - Update internal `rustls` to 0.20. ## v0.11.6 - (wasm) Fix request bodies more. ## v0.11.5 - Add `ClientBuilder::http1_only()` method. - Add `tls::Version` type, and `ClientBuilder::min_tls_version()` and `ClientBuilder::max_tls_version()` methods. - Implement `TryFrom` for `http::Request`. - Implement `Clone` for `Identity`. - Fix `NO_PROXY`environment variable parsing to more closely match curl's. Comma-separated entries are now trimmed for whitespace, and `*` is allowed to match everything. - Fix redirection to respect `https_only` option. - (wasm) Add `Body::as_bytes()` method. - (wasm) Fix sometimes wrong conversation of bytes into a `JsValue`. - (wasm) Avoid dependency on serde-serialize feature. ## v0.11.4 - Add `ClientBuilder::resolve()` option to override DNS resolution for specific domains. - Add `native-tls-alpn` Cargo feature to use ALPN with the native-tls backend. - Add `ClientBuilder::deflate()` option and `deflate` Cargo feature to support decoding response bodies using deflate. - Add `RequestBuilder::version()` to allow setting the HTTP version of a request. - Fix allowing "invalid" certificates with the `rustls-tls` backend, when the server uses TLS v1.2 or v1.3. - (wasm) Add `try_clone` to `Request` and `RequestBuilder` ## v0.11.3 - Add `impl From for reqwest::Body`. - (wasm) Add credentials mode methods to `RequestBuilder`. ## v0.11.2 - Add `CookieStore` trait to customize the type that stores and retrieves cookies for a session. - Add `cookie::Jar` as a default `CookieStore`, easing creating some session cookies before creating the `Client`. - Add `ClientBuilder::http2_adaptive_window()` option to configure an adaptive HTTP2 flow control behavior. - Add `ClientBuilder::http2_max_frame_size()` option to adjust the maximum HTTP2 frame size that can be received. - Implement `IntoUrl` for `String`, making it more convenient to create requests with `format!`. ## v0.11.1 - Add `ClientBuilder::tls_built_in_root_certs()` option to disable built-in root certificates. - Fix `rustls-tls` glue to more often support ALPN to upgrade to HTTP/2. - Fix proxy parsing to assume `http://` if no scheme is found. - Fix connection pool idle reaping by enabling hyper's `runtime` feature. - (wasm) Add `Request::new()` constructor. # v0.11.0 - Change `multipart` to be an optional cargo feature. - Remove deprecated methods. - Update to Tokio v1.0. - Update to Bytes v1.0. - Update to hyper v0.14. ## v0.10.10 - Add `tcp_keepalive` option to `blocking::ClientBuilder`. - Add `multipart::Part::stream_with_length` constructor, to create a streaming part with a known length. - Add `ClientBuilder::https_only` option, to allow requiring URLs to be `https`. - Change default `tcp_keepalive` value to be disabled. ## v0.10.9 - Add `rustls-tls-native-roots`, `rustls-tls-webpki-roots`, and `rustls-tls-manual-roots` Cargo features, to configure which certificate roots to use with rustls. - Add `ClientBuilder::tcp_keepalive()` method to enable TCP keepalive. - Add `ClientBuilder::http1_writev()` method to force enable or disable vectored writes. - Add `Error::is_connect()` method to identify if the error is related to connection-establishment. - Add `blocking::ClientBuilder::brotli()` method. - Windows: Update default protocol to HTTP for HTTPS system proxies, when a protocol is not specified. - (wasm) Add support for Cloudflare workers runtime. - (wasm) Add `ClientBuilder::default_headers()` method. - (wasm) Add `RequestBuilder::build()` method. ## v0.10.8 - Add `must_use` to `RequestBuilder` and `ClientBuilder`. - Fix Windows system proxy detection of Fiddler proxies. - (wasm) Add `headers` method to `RequestBuilder`. - (wasm) Add `execute` method to `Client`. - (wasm) Add `TryFrom` for `Request`. - (wasm) Fix checking for global `window` to work in non-browser environments. - (wasm) Fix sending of an empty body when not required. ## v0.10.7 - Add `NO_PROXY` environment variable support. - Add more `Error::{is_request, is_body, is_decode}` getters. - Add conversion of `reqwest::ClientBuilder` to `reqwest::blocking::ClientBuilder`. - Add `headers_mut()` to `reqwest::blocking::Response`. - (wasm) Add `form()`, `query()`, `multipart` and `bearer_auth()` to `RequestBuilder`. ## v0.10.6 - Changed handling of URLs that don't have `http:` or `https:` schemes, returning an error instead. - Fixed a potential hyper-rustls feature conflict. ## v0.10.5 - Add `ClientBuilder::pool_idle_timeout` option. - Add `ClientBuilder::pool_max_idle_per_host` option, deprecate `max_idle_per_host`. - Add `Response::content_length` for WASM target. - Enable TCP_NODELAY by default. - Implement `TryFrom` for `blocking::Request`. - Implement `TryFrom` for `Request`. - Removes `From` for `Request`. - This is technically a breaking change, but was a mistake. It was not valid to convert from an `http::Request` to a `reqwest::Request` in an infallible fashion. It would panic if the conversion was not possible. Instead, the implementation has been changed to `TryFrom` to indicate it could fail. ## v0.10.4 - Add `trust-dns` optional feature to change DNS resolver. - Add `bytes()` method to `reqwest::blocking::Response`. - Add `buffer()` method to `reqwest::blocking::Body`. - Implement `From` for `reqwest::Request`. ## v0.10.3 - Upgrade internal `rustls` version. ## v0.10.2 - Add Brotli support, enabled with the optional `brotli` feature. - Add `Client::use_preconfigured_tls(tls_connector)` allowing manual configuration of TLS options. - Implement `Default` for blocking `Client`, `ClientBuilder`, and `multipart::Form`. - (wasm) Add `Response::error_for_status()` method. - (wasm) Add `Response::json()` method. - (wasm) Implement `Default` for `Client` and `ClientBuilder`. ## v0.10.1 - Add `socks` optional feature to support SOCKS5 proxies. - Add `RequestBuilder::timeout()` to configure a timeout for a single request, instead of using the client's timeout. - Add `ClientBuilder::connection_verbose()` option to enable verbose IO logs. - (wasm) Add `RequestBuilder::fetch_mode_no_cors()` option. - (wasm) Add `Response::url()` getter method. # v0.10.0 - Add `std::future::Future` support. - Add `wasm32-unknown-unknown` support (with fewer features). - Add ability to pass async `Response` as the `body` of another `Request`. - Add `Body::as_bytes()` method. - Add `Response::bytes_stream()` method to get body as an `impl Stream`. - Add `Request::try_clone()` method. - Change default `Client` API to async. The previous blocking client API is available at `reqwest::blocking`. - Change to no longer send a default `User-Agent` header. Add one via `ClientBuilder::user_agent()`. - Change to enable system/environment proxy detection by default. - Change `default-tls` feature to only include `ClientBuilder` options that both `native-tls` and `rustls` support. - Change default feature set to reduce unnecessary dependencies. Most features are disabled by default: - `blocking`: The `reqwest::blocking` (synchronous) client API. - `cookies`: Cookie store support. - `gzip`: Automatic response body decompression. - `json`: Request and response JSON body methods. - `stream`: `futures::Stream` support. - Change `Error` internal design, removing several `Error::is_*` inspector methods. - Change Redirect API: - Renamed types to be part of the `redirect` module (for example, `reqwest::RedirectPolicy` is now `reqwest::redirect::Policy`). - Removed `loop_detected` and `too_many_redirect` methods from `redirect::Attempt`, replaced with a generic `error` method. - The default policy no longer specifically looks for redirect loops (but they should be caught by the maximum limit). - Fix checking `HTTP_PROXY` environment variable if it the environment is from a CGI script. - Fix removal of username/password of parsed proxy URL. - Update `url` to v2.0. - Update `hyper` to v0.13. - Update `http` to v0.2. ## v0.9.19 - Add `ClientBuilder::use_sys_proxy()` to enable automatic detect of HTTP proxies configured on the system. - Add `ClientBuilder::no_proxy()` to disable system proxies. This is the default for 0.9, but will change to detecting system proxies by default in 0.10. - Add support for streaming request bodies in the async client. - Add `async::Response::text()` that returns a `Future` of the full body decoded to a `String`. - Add `Clone` for `Certificate`. ## v0.9.18 - Fix `Cookie` headers to no longer send as percent-encoded (instead, exactly as sent by the server). ## v0.9.17 - Fix `Cookie` headers so as to not include attributes from the `Set-Cookie` (like `HttpOnly`, `Secure`, etc). ## v0.9.16 - Add `Response::text_with_charset()` to allow setting the default charset to decode. - Add `Error::source()` implementation. - Add `async::ClientBuilder::timeout()` option, will timeout the connect, request, and response body futures. - Fix gzip + chunked transfer encoding issue preventing connection reuse. - Fix `RequestBuilder::query()` to not add just `"?"` if the encoded query is empty. - Fix including new cookie headers when response is a redirect. ## v0.9.15 - Fix sending of "appended" request headers. ## v0.9.14 - Add optional support for SOCKS5 proxies, by enabling the `socks5` cargo feature. - Add Cookie Store support to `Client`, automatically handling cookies for a session. * Add `ClientBuilder::cookie_store(enable: bool)` method to enable a cookie store that persists across requests. * Add `Response::cookies()` accessor that allows iterating over response cookies. - Fix `Proxy` to check the URL for a username and password. ## v0.9.13 ### Fixes - Fix panic on some invalid `Location` headers during redirects (error is logged and redirect response is returned instead). - Fix instance when server notices streaming request body is complete before reqwest does. ## v0.9.12 ### Features - Add `ClientBuilder::tcp_nodelay()` to allow disabling Nagle's algorithm. - Add `ClientBuilder::max_idle_per_host()` to allow reducing the number of idle pooled connections. - Add `RequestBuilder::bearer_auth()` method to async builder. ### Fixes - Fix capitalization error in async `RequestBuilder::basic_auth()`. - Fix ALPN causing issues when using a Proxy. ## v0.9.11 ### Features - Add `multipart::Form::percent_encode_noop()` to allow for servers which don't support percent encoding of parameters. - Add `ClientBuilder::http1_title_case_headers()` to force request headers to use Title-Case. - Add `ClientBuilder::connect_timeout()` to allow setting only a connect timeout. ## v0.9.10 ### Features - Add `ClientBuilder::local_address()` to bind to a local IP address. - Add `Response::error_for_status_ref()` to return an `Error` while borrowing a `Response`. ### Fixes - Fix `Identity::from_pem` with `rustls-tls` backend when using RSA private keys. ## v0.9.9 ### Features - Add `ClientBuilder::h2_prior_knowledge()` option to force HTTP2. - Add `Response::content_length()` to get the content-length of a response. - Enable ALPN h2 with the rustls-tls backend. ## v0.9.8 ### Fixes - Revert default DNS resolver to `getaddrinfo` in a threadpool. There is now a `trust-dns` optional feature to enable the Trust-DNS resolver. - Detect `Certificate` and `Identity` errors at construction time. ## v0.9.7 ### Fixes - Fix DNS resolver on Android (reverted back to `getaddrinfo`). - Fix sending unicode `filename`s in `multipart/form-data` requests. ## v0.9.6 ### Features - Add `Proxy::basic_auth` method to support proxy authorization. - Add `rustls-tls` optional feature to use rustls instead of native-tls. - Add `try_clone` method to `Request` and `RequestBuilder`. - Add `reqwest::async::multipart` support, similar to the synchronous API. - Adds `default-tls-vendored` optional feature to vendor OpenSSL. ### Fixes - Fix panic from top-level `reqwest::get` if client builder fails to build. - Removed timeout waiting for `reqwest::Client` runtime to startup. - Fix `RequestBuilder::headers` to properly append extra headers of the same name. ### Performance - Replaced DNS threadpool using `getaddrinfo` with a non-blocking DNS resolver. ## v0.9.5 ### Features - Adds `Response::remote_addr()` method to check the address of the connection used. - Adds `default-tls` crate feature, enabled by default, which allows users to *disable* TLS. ## v0.9.4 ### Features - Adds `percent_encoding_path_segment` and `percent_encoding_attr_char` configuration to `multipart::Form`. ### Fixes - Reverts `multipart::Form` default percent encoding format to `path-segment`. ## v0.9.3 ### Features - Adds `multipart::Part::bytes()` to create a part of raw bytes. - Adds constructors for `Response` to help with testing. ### Fixes - Properly percent-encoding more illegal characters in multipart filenames. - Ensure timed out requests cancel the associated async task. ## v0.9.2 ### Fixes - Fix panic when `Location` header has UTF-8 characters. ## v0.9.1 ### Fixes - Fix large request bodies failing because of improper handling of backpressure. - Remove body-related headers when redirect changes a `POST` into a `GET`. - Reduce memory size of `Response` and `Error` signicantly. # v0.9.0 ### Features - Upgrade to `tokio` 0.1. - Upgrade to `hyper` 0.12. - Upgrade to `native-tls` 0.2. - Add `ClientBuilder::danger_accept_invalid_certs(bool)` to disable certificate verification. - Add `RequestBuilder::bearer_auth(token)` to ease sending bearer tokens. - Add `headers()` and `headers_mut()` to `multipart::Part` to allow sending extra headers for a specific part. - Moved `request::unstable::async` to `reqwest::async`. ### Fixes - Fix panicking when passing a `Url` with a `file://` scheme. Instead, an `Error` is returned. ### Breaking Changes - Changed `ClientBuilder::danger_disable_hostname_verification()` to `ClientBuilder::danger_accept_invalid_hostnames(bool)`. - Changed `ClientBuilder` to be a by-value builder instead of by-ref. For single chains of method calls, this shouldn't affect you. For code that conditionally uses the builder, this kind of change is needed: ```rust // Old let mut builder = ClientBuilder::new(); if some_val { builder.gzip(false); } let client = builder.build()?; // New let mut builder = ClientBuilder::new(); if some_val { builder = builder.gzip(false); } let client = builder.build()?; ``` - Changed `RequestBuilder` to be a by-value builder of by-ref. See the previous note about `ClientBuilder` for affected code and how to change it. - Removed the `unstable` cargo-feature, and moved `reqwest::unstable::async` to `reqwest::async`. - Changed `multipart::Part::mime()` to `mime_str()`. ```rust // Old let part = multipart::Part::file(path)? .mime(mime::TEXT_PLAIN); // New let part = multipart::Part::file(path)? .mime_str("text/plain")?; ``` - The upgrade to `hyper` 0.12 means a temporary removal of the typed headers. The `RequestBuilder` has simple methods to set headers using strings, which can work in most places. ```rust // Old client .get("https://hyper.rs") .header(UserAgent::new("hallo")) .send()?; // New client .get("https://hyper.rs") .header("user-agent", "hallo") .send()?; ``` To ease the transition, there is a `hyper-011` cargo-feature that can be enabled. ```toml [dependencies] reqwest = { version = "0.9", features = ["hyper-011"] } ``` And then usage: ```rust client .get("https://hyper.rs") .header_011(reqwest::hyper_011::header::UserAgent::new("hallo")) .send()?; ``` ## v0.8.8 - Fix docs.rs/reqwest build. ## v0.8.7 ### Fixes - Send an extra CRLF at the end of multipart requests, since some servers expect it. - Removed internal dependency on `tokio-proto`, which removed unsafe `small-vec` dependency. ## v0.8.6 ### Features - Add `RedirectAttempt::status` to check status code that triggered redirect. - Add `RedirectPolicy::redirect` method publicly, to allow composing policies. ## v0.8.5 ### Features - Try to auto-detect encoding in `Response::text()`. - Add `Certificate::from_pem` to load PEM encoded client certificates. - Allow unsized types in `query`, `form`, and `json`. - Add `unstable::async::RequestBuilder::query`, mirroring the stable builder method. ## v0.8.4 ### Features - Add `RequestBuilder::query` to easily adjust query parameters of requests. ## v0.8.3 ### Features - Upgrades internal log crate usage to v0.4 ## v0.8.2 ### Fixes - Enable hyper's `no_proto` config, fixing several bugs in hyper. ## v0.8.1 ### Features - Add `ClientBuilder::default_headers` to set headers used for every request. - Add `async::ClientBuilder::dns_threads` to set number of threads use for DNS. - Add `Response::text` as shortcut to read the full body into a `String`. - Add `Response::copy_to` as shortcut for `std::io::copy`. # v0.8.0 ### Features - Client TLS Certificates (#43) - GZIP decoding has been added to the **async** Client (#161) - `ClientBuilder` and `RequestBuilder` hold their errors till consumed (#189) - `async::Response::body()` now returns a reference to the body instead of consuming the `Response` - A default timeout for `reqwest::Client` is used set to 30 seconds (#181) ### Breaking Changes - `Client::new` no longer returns a `Result`. To handle any panics that come from `Client::new`, the builder can be used instead. - `ClientBuilder` and `RequestBuilder` hold their errors till consumed (#189). This means a bunch of `?` will be going away, but means using the builders will be far easier now. Any error encountered inside the builders will now be returned when the builder is consumed. To get errors back immediately, the `Request` type can be used directly, by building pieces separately and calling setters. - `async::Response::body()` now returns a reference to the body instead of consuming the `Response`. - A default timeout for `reqwest::Client` is used set to 30 seconds (#181) For uses where the timeout is too short, it can be changed on the `ClientBuilder`, using the `timeout` method. Passing `None` will disable the timeout, reverting to the pre-0.8 behavior. ## v0.7.3 ### Features - `Proxy::custom(fn)` to allow dynamically picking a proxy URL ### Fixes - fix occasional panic when program exits while `Client` or `Response` are dropping. ## v0.7.2 ### Fixes - fix a panic when redirecting and a `Authorization` header was added (https://github.com/seanmonstar/reqwest/commit/cf246d072badd9b31b487e7a0b00490e4cc9584f) - fix redirects so that a GET will follow 307/308 responses (https://github.com/seanmonstar/reqwest/commit/2d11a4bd7167e1bf3a35b62f5aeb36d5d294e56e) ## v0.7.1 ### Fixes - fix remove accidental `println`s in the sending of a body - some documentation improvements # v0.7.0 ### Features - Proxy support (#30) - Self-signed TLS certificates (#97) - Disabling TLS hostname validation   (#89) - A `Request` type that can be used instead of the `RequestBuilder` (#85) - Add `Response::error_for_status()` to easily convert 400 and 500 status responses into an `Error` (#98) - Upgrade hyper to 0.11 - Synchronous `Client` remains. - Timeouts now affect DNS and socket connection. - Pool much better at evicting sockets when they die. - An `unstable` Cargo feature to enable `reqwest::unstable::async`. - A huge docs improvement! ### Fixes - Publicly exports `RedirectAction` and `RedirectAttempt` - `Error::get_ref` returns `Error + Send + Sync` ### Breaking Changes - hyper has been upgraded to 0.11, so `header`, `StatusCode`, and `Method` have breaking changes. - `mime` has been upgraded to 0.3, with a very different API. - All configuration methods have been removed from the `Client`, and moved to the `ClientBuilder`. - The `HttpVersion` type was completely removed. - `Error::cause()` now returns `Error::get_ref().cause()`. - All methods on `Client` that start a `RequestBuilder` now return a `Result` immediately, instead of delaying the URL parse error for later. - The `RequestBuilder` methods all take `&mut self`, instead of moving the builder, and return `&mut Self`. (This shouldn't actually affect most people who are building a request in a single chain.) - `Response::status()` returns a `StatusCode` instead of `&StatusCode`. ## v0.6.2 ### Features - adds `Client::referer(bool)` option to disable setting the `Referer` header during redirects (https://github.com/seanmonstar/reqwest/commit/bafcd7ae6fc232856dd6ddb8bf5b20dbbbfe0bc9) ### Fixes - fixes filtering sensitive headers during redirects (https://github.com/seanmonstar/reqwest/issues/10) - fixes sending of the Referer to an HTTP site when coming from HTTPS, and removes username and fragment explicitly (https://github.com/seanmonstar/reqwest/commit/d8696045b4c6bc4d9e33789cff6a9e1fa75462d7) - documentation updates ## v0.6.1 ### Features - adds `Error::get_ref` to get the underlying error that may have occurred. Includes a `'static` bounds, which allows for downcasting (as opposed to `Error::cause`). # v0.6.0 ### Features - Upgraded to serde `1.0` - Added a `url` [method](https://docs.rs/reqwest/0.6.0/reqwest/struct.Error.html#method.url) to `Error`, which returns a possible associated `Url` that occurred with this error. - Added `req.basic_auth(user, optional_pass)` [method](https://docs.rs/reqwest/0.6.0/reqwest/struct.RequestBuilder.html#method.basic_auth) to ease using `Basic` authentication. ### Breaking Changes - The publicly exposed peer dependency serde was upgraded. It is now `serde@1.0`. Mismatched version will give a compiler error that a serde trait is not implemented. - `Error` is no longer an `enum`, but an opaque struct. Details about it can be checked with `std::error::Error::cause()`, and methods on `reqwest::Error` include `is_http()`, `is_serialization()`, and `is_redirect()`. - `RedirectPolicy::custom` receives different arguments, and returns different values. See the [docs](https://docs.rs/reqwest/0.6.0/reqwest/struct.RedirectPolicy.html#method.custom) for an example. ## v0.5.2 ### Fixes - fix panic with Gzip decoder on an empty body (https://github.com/seanmonstar/reqwest/issues/82) ## v0.5.1 ### Features - add `Clone` implementation for `Client` # v0.5.0 ### Features - Automatic GZIP decoding: By default, `Client` will try to decode any responses that appear to be gzip encoded (based on headers). This can be disabled via `client.gzip(false)` (https://github.com/seanmonstar/reqwest/commit/ab5e477a123319efd4b17f3666b41b44ec244bee) - Specify a timeout for requests using `client.timeout(duration)`. (https://github.com/seanmonstar/reqwest/commit/ec049fefbae7355f6e4ddbbc7ebedcadb30e1e04) - Request bodies with a known length can be constructed with `Body::sized()` (https://github.com/seanmonstar/reqwest/commit/82f1877d4b6cba2fac432670ec306160aee5c501) - Add `Client.put`, `Client.patch`, and `Client.delete` convenience methods (https://github.com/seanmonstar/reqwest/commit/c37b8aa0338ac4142763d206c6df79856915056d, https://github.com/seanmonstar/reqwest/commit/4d6582d22b23c27927e481a9c8a83ad08cfd1a2a, https://github.com/seanmonstar/reqwest/commit/a3983f3122b2d1495ea36bb5a8fd019a7605ae56) - Add `reqwest::mime` (https://github.com/seanmonstar/reqwest/commit/0615c6d65e03ba9cb5364169c9e74f4f2a91554b) ### Breaking Changes The only breaking change is a behavioral one, all programs should still compile without modification. The automatic GZIP decoding could interfere in cases where a user was expecting the GZIP bytes, either to save to a file or decode themselves. To restore this functionality, set `client.gzip(false)`. # v0.4.0 - updated to serde 0.9 # v0.3.0 - updated to hyper 0.10 # v0.2.0 ### Features - add `Response.json()` method (https://github.com/seanmonstar/reqwest/commit/2d10ecc99e2aaed66616294baaf65380b446e1c6) - add `RedirectPolicy` (https://github.com/seanmonstar/reqwest/commit/e92b3e862a1a94c0b4173a7d49a315bc121da31e) - set an `Accept: */*` header by default if no `Accept` header is set (https://github.com/seanmonstar/reqwest/commit/559ae8011a2c098f4fe1821ec1d3444a46f4bf5e) - add support for 307 and 308 redirects (https://github.com/seanmonstar/reqwest/commit/a54447c1d9c75dab639333265f51a91a43e99c2e) - implement `Sync` for `Client`, and `Send` for `RequestBuilder` and `Response` (https://github.com/seanmonstar/reqwest/commit/d18a53b3fcc81c4a60875755c8e95d777a343319) - implement `Send` for `Error` (https://github.com/seanmonstar/reqwest/commit/20b161096e67d22c962e69b2656ae9741ac73c25) - implement `std::fmt::Debug` for all public types (https://github.com/seanmonstar/reqwest/commit/d624b0ef29020c6085ec94651a990f58ccd684e2) ### Breaking Changes - `Error::Serialize` now has a `Box` instead of `Box` - `RequestBuilder` no longer has an associated lifetime (was `RequestBuilder<'a>`) # v0.1.0 Initial release: http://seanmonstar.com/post/153221119046/introducing-reqwest reqwest-0.11.27/Cargo.lock0000644000001510440000000000100106560ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "async-compression" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" dependencies = [ "brotli", "flate2", "futures-core", "memchr", "pin-project-lite", "tokio", ] [[package]] name = "async-trait" version = "0.1.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "brotli" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bumpalo" version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "console_error_panic_hook" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ "cfg-if", "wasm-bindgen", ] [[package]] name = "cookie" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", "time", "version_check", ] [[package]] name = "cookie_store" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" dependencies = [ "cookie", "idna 0.3.0", "log", "publicsuffix", "serde", "serde_derive", "serde_json", "time", "url", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "crc32fast" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "data-encoding" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encoding_rs" version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] [[package]] name = "enum-as-inner" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "flate2" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "h3" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b83e1915177ea624b5bbbdb16bc54f0c106c9664892c695f995e53f5c6793b80" dependencies = [ "bytes", "fastrand", "futures-util", "http", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "h3-quinn" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac9675014d703c3d516a121757bbc02e53f1ee838e0729fc7534b35024a81ae4" dependencies = [ "bytes", "futures", "h3", "quinn", "quinn-proto", "tokio", "tokio-util", ] [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hickory-proto" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "091a6fbccf4860009355e3efc52ff4acf37a63489aad7435372d44ceeb6fbbcf" dependencies = [ "async-trait", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", "idna 0.4.0", "ipnet", "once_cell", "rand", "thiserror", "tinyvec", "tokio", "tracing", "url", ] [[package]] name = "hickory-resolver" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35b8f021164e6a984c9030023544c57789c51760065cd510572fedcfb04164e8" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", "lru-cache", "once_cell", "parking_lot", "rand", "resolv-conf", "smallvec", "thiserror", "tokio", "tracing", ] [[package]] name = "hostname" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", "winapi", ] [[package]] name = "http" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", "pin-project-lite", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", "want", ] [[package]] name = "hyper-rustls" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", "hyper", "rustls", "tokio", "tokio-rustls", ] [[package]] name = "hyper-tls" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", "native-tls", "tokio", "tokio-native-tls", ] [[package]] name = "idna" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2", "widestring", "windows-sys 0.48.0", "winreg", ] [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "itoa" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libflate" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" dependencies = [ "adler32", "crc32fast", "libflate_lz77", ] [[package]] name = "libflate_lz77" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" dependencies = [ "rle-decode-fast", ] [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lru-cache" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ "linked-hash-map", ] [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" dependencies = [ "mime", "unicase", ] [[package]] name = "miniz_oxide" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] [[package]] name = "native-tls" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "object" version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" version = "300.2.3+3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" dependencies = [ "cc", ] [[package]] name = "openssl-sys" version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", "openssl-src", "pkg-config", "vcpkg", ] [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.48.5", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "psl-types" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "publicsuffix" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" dependencies = [ "idna 0.3.0", "psl-types", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quinn" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" dependencies = [ "bytes", "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "thiserror", "tokio", "tracing", ] [[package]] name = "quinn-proto" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", "rand", "ring 0.16.20", "rustc-hash", "rustls", "slab", "thiserror", "tinyvec", "tracing", ] [[package]] name = "quinn-udp" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", "socket2", "tracing", "windows-sys 0.48.0", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" version = "0.11.27" dependencies = [ "async-compression", "base64", "brotli", "bytes", "cookie", "cookie_store", "doc-comment", "encoding_rs", "env_logger", "futures-channel", "futures-core", "futures-util", "h2", "h3", "h3-quinn", "hickory-resolver", "http", "http-body", "hyper", "hyper-rustls", "hyper-tls", "ipnet", "js-sys", "libflate", "log", "mime", "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", "tokio-socks", "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", "wasm-streams", "web-sys", "webpki-roots", "winreg", ] [[package]] name = "resolv-conf" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", "quick-error", ] [[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", "once_cell", "spin 0.5.2", "untrusted 0.7.1", "web-sys", "winapi", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", "windows-sys 0.52.0", ] [[package]] name = "rle-decode-fast" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring 0.17.8", "rustls-webpki", "sct", ] [[package]] name = "rustls-native-certs" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", "rustls-pemfile", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64", ] [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "ryu" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "schannel" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "security-framework" version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "syn" version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "system-configuration" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys 0.52.0", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-socks" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" dependencies = [ "either", "futures-util", "thiserror", "tokio", ] [[package]] name = "tokio-util" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicase" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna 0.5.0", "percent-encoding", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "serde", "serde_json", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-bindgen-test" version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" dependencies = [ "console_error_panic_hook", "js-sys", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", ] [[package]] name = "wasm-bindgen-test-macro" version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "wasm-streams" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "web-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "widestring" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.4", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm 0.52.4", "windows_aarch64_msvc 0.52.4", "windows_i686_gnu 0.52.4", "windows_i686_msvc 0.52.4", "windows_x86_64_gnu 0.52.4", "windows_x86_64_gnullvm 0.52.4", "windows_x86_64_msvc 0.52.4", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] reqwest-0.11.27/Cargo.toml0000644000000233030000000000100106750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.63.0" name = "reqwest" version = "0.11.27" authors = ["Sean McArthur "] autotests = true description = "higher level HTTP client library" documentation = "https://docs.rs/reqwest" readme = "README.md" keywords = [ "http", "request", "client", ] categories = [ "web-programming::http-client", "wasm", ] license = "MIT OR Apache-2.0" repository = "https://github.com/seanmonstar/reqwest" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", "--cfg", "reqwest_unstable", ] targets = [ "x86_64-unknown-linux-gnu", "wasm32-unknown-unknown", ] [package.metadata.playground] features = [ "blocking", "cookies", "json", "multipart", ] [[example]] name = "blocking" path = "examples/blocking.rs" required-features = ["blocking"] [[example]] name = "json_dynamic" path = "examples/json_dynamic.rs" required-features = ["json"] [[example]] name = "json_typed" path = "examples/json_typed.rs" required-features = ["json"] [[example]] name = "tor_socks" path = "examples/tor_socks.rs" required-features = ["socks"] [[example]] name = "form" path = "examples/form.rs" [[example]] name = "simple" path = "examples/simple.rs" [[test]] name = "blocking" path = "tests/blocking.rs" required-features = ["blocking"] [[test]] name = "cookie" path = "tests/cookie.rs" required-features = ["cookies"] [[test]] name = "gzip" path = "tests/gzip.rs" required-features = ["gzip"] [[test]] name = "brotli" path = "tests/brotli.rs" required-features = ["brotli"] [[test]] name = "deflate" path = "tests/deflate.rs" required-features = ["deflate"] [[test]] name = "multipart" path = "tests/multipart.rs" required-features = ["multipart"] [dependencies.base64] version = "0.21" [dependencies.bytes] version = "1.0" [dependencies.futures-core] version = "0.3.0" default-features = false [dependencies.futures-util] version = "0.3.0" default-features = false [dependencies.http] version = "0.2" [dependencies.mime_guess] version = "2.0" optional = true default-features = false [dependencies.serde] version = "1.0" [dependencies.serde_json] version = "1.0" optional = true [dependencies.serde_urlencoded] version = "0.7.1" [dependencies.sync_wrapper] version = "0.1.2" [dependencies.tower-service] version = "0.3" [dependencies.url] version = "2.2" [features] __internal_proxy_sys_no_cache = [] __rustls = [ "hyper-rustls", "tokio-rustls", "rustls", "__tls", ] __tls = ["dep:rustls-pemfile"] blocking = [ "futures-util/io", "tokio/sync", ] brotli = [ "async-compression", "async-compression/brotli", "tokio-util", ] cookies = [ "cookie_crate", "cookie_store", ] default = ["default-tls"] default-tls = [ "hyper-tls", "native-tls-crate", "__tls", "tokio-native-tls", ] deflate = [ "async-compression", "async-compression/zlib", "tokio-util", ] gzip = [ "async-compression", "async-compression/gzip", "tokio-util", ] hickory-dns = ["hickory-resolver"] http3 = [ "rustls-tls-manual-roots", "h3", "h3-quinn", "quinn", "futures-channel", ] json = ["serde_json"] multipart = ["mime_guess"] native-tls = ["default-tls"] native-tls-alpn = [ "native-tls", "native-tls-crate/alpn", ] native-tls-vendored = [ "native-tls", "native-tls-crate/vendored", ] rustls-tls = ["rustls-tls-webpki-roots"] rustls-tls-manual-roots = ["__rustls"] rustls-tls-native-roots = [ "rustls-native-certs", "__rustls", ] rustls-tls-webpki-roots = [ "webpki-roots", "__rustls", ] socks = ["tokio-socks"] stream = [ "tokio/fs", "tokio-util", "wasm-streams", ] trust-dns = ["hickory-dns"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.async-compression] version = "0.4.0" features = ["tokio"] optional = true default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dependencies.cookie_crate] version = "0.17.0" optional = true package = "cookie" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.cookie_store] version = "0.20.0" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.encoding_rs] version = "0.8" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.futures-channel] version = "0.3" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.h2] version = "0.3.14" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.h3] version = "0.0.3" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.h3-quinn] version = "0.0.4" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.hickory-resolver] version = "0.24" features = ["tokio-runtime"] optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.http-body] version = "0.4.0" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper] version = "0.14.21" features = [ "tcp", "http1", "http2", "client", "runtime", ] default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper-rustls] version = "0.24.0" optional = true default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dependencies.hyper-tls] version = "0.5" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.ipnet] version = "2.3" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.log] version = "0.4" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.mime] version = "0.3.16" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.native-tls-crate] version = "0.2.10" optional = true package = "native-tls" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.once_cell] version = "1" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.percent-encoding] version = "2.1" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.pin-project-lite] version = "0.2.0" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.quinn] version = "0.10" features = [ "tls-rustls", "ring", "runtime-tokio", ] optional = true default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dependencies.rustls] version = "0.21.6" features = ["dangerous_configuration"] optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.rustls-native-certs] version = "0.6" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.rustls-pemfile] version = "1.0" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] version = "1.0" features = [ "net", "time", ] default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-native-tls] version = "0.3.0" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-rustls] version = "0.24" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-socks] version = "0.5.1" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-util] version = "0.7.1" features = [ "codec", "io", ] optional = true default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dependencies.webpki-roots] version = "0.25" optional = true [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.brotli_crate] version = "3.3.0" package = "brotli" [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.doc-comment] version = "0.3" [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.env_logger] version = "0.10" [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.futures-util] version = "0.3.0" features = [ "std", "alloc", ] default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.hyper] version = "0.14" features = [ "tcp", "stream", "http1", "http2", "client", "server", "runtime", ] default-features = false [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.libflate] version = "1.0" [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.serde] version = "1.0" features = ["derive"] [target."cfg(not(target_arch = \"wasm32\"))".dev-dependencies.tokio] version = "1.0" features = [ "macros", "rt-multi-thread", ] default-features = false [target."cfg(target_arch = \"wasm32\")".dependencies.js-sys] version = "0.3.45" [target."cfg(target_arch = \"wasm32\")".dependencies.serde_json] version = "1.0" [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen] version = "0.2.68" [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures] version = "0.4.18" [target."cfg(target_arch = \"wasm32\")".dependencies.wasm-streams] version = "0.4" optional = true [target."cfg(target_arch = \"wasm32\")".dependencies.web-sys] version = "0.3.25" features = [ "AbortController", "AbortSignal", "Headers", "Request", "RequestInit", "RequestMode", "Response", "Window", "FormData", "Blob", "BlobPropertyBag", "ServiceWorkerGlobalScope", "RequestCredentials", "File", "ReadableStream", ] [target."cfg(target_arch = \"wasm32\")".dev-dependencies.wasm-bindgen] version = "0.2.68" features = ["serde-serialize"] [target."cfg(target_arch = \"wasm32\")".dev-dependencies.wasm-bindgen-test] version = "0.3" [target."cfg(target_os = \"macos\")".dependencies.system-configuration] version = "0.5.1" [target."cfg(windows)".dependencies.winreg] version = "0.50.0" reqwest-0.11.27/Cargo.toml.orig000064400000000000000000000161631046102023000143640ustar 00000000000000[package] name = "reqwest" version = "0.11.27" # remember to update html_root_url description = "higher level HTTP client library" keywords = ["http", "request", "client"] categories = ["web-programming::http-client", "wasm"] repository = "https://github.com/seanmonstar/reqwest" documentation = "https://docs.rs/reqwest" authors = ["Sean McArthur "] readme = "README.md" license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.63.0" autotests = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "--cfg", "reqwest_unstable"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [package.metadata.playground] features = [ "blocking", "cookies", "json", "multipart", ] [features] default = ["default-tls"] # Note: this doesn't enable the 'native-tls' feature, which adds specific # functionality for it. default-tls = ["hyper-tls", "native-tls-crate", "__tls", "tokio-native-tls"] # Enables native-tls specific functionality not available by default. native-tls = ["default-tls"] native-tls-alpn = ["native-tls", "native-tls-crate/alpn"] native-tls-vendored = ["native-tls", "native-tls-crate/vendored"] rustls-tls = ["rustls-tls-webpki-roots"] rustls-tls-manual-roots = ["__rustls"] rustls-tls-webpki-roots = ["webpki-roots", "__rustls"] rustls-tls-native-roots = ["rustls-native-certs", "__rustls"] blocking = ["futures-util/io", "tokio/sync"] cookies = ["cookie_crate", "cookie_store"] gzip = ["async-compression", "async-compression/gzip", "tokio-util"] brotli = ["async-compression", "async-compression/brotli", "tokio-util"] deflate = ["async-compression", "async-compression/zlib", "tokio-util"] json = ["serde_json"] multipart = ["mime_guess"] # Deprecated, remove this feature while bumping minor versions. trust-dns = ["hickory-dns"] hickory-dns = ["hickory-resolver"] stream = ["tokio/fs", "tokio-util", "wasm-streams"] socks = ["tokio-socks"] # Experimental HTTP/3 client. http3 = ["rustls-tls-manual-roots", "h3", "h3-quinn", "quinn", "futures-channel"] # Internal (PRIVATE!) features used to aid testing. # Don't rely on these whatsoever. They may disappear at anytime. # Enables common types used for TLS. Useless on its own. __tls = ["dep:rustls-pemfile"] # Enables common rustls code. # Equivalent to rustls-tls-manual-roots but shorter :) __rustls = ["hyper-rustls", "tokio-rustls", "rustls", "__tls"] # When enabled, disable using the cached SYS_PROXIES. __internal_proxy_sys_no_cache = [] [dependencies] base64 = "0.21" http = "0.2" url = "2.2" bytes = "1.0" serde = "1.0" serde_urlencoded = "0.7.1" tower-service = "0.3" futures-core = { version = "0.3.0", default-features = false } futures-util = { version = "0.3.0", default-features = false } sync_wrapper = "0.1.2" # Optional deps... ## json serde_json = { version = "1.0", optional = true } ## multipart mime_guess = { version = "2.0", default-features = false, optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] encoding_rs = "0.8" http-body = "0.4.0" hyper = { version = "0.14.21", default-features = false, features = ["tcp", "http1", "http2", "client", "runtime"] } h2 = "0.3.14" once_cell = "1" log = "0.4" mime = "0.3.16" percent-encoding = "2.1" tokio = { version = "1.0", default-features = false, features = ["net", "time"] } pin-project-lite = "0.2.0" ipnet = "2.3" # Optional deps... rustls-pemfile = { version = "1.0", optional = true } ## default-tls hyper-tls = { version = "0.5", optional = true } native-tls-crate = { version = "0.2.10", optional = true, package = "native-tls" } tokio-native-tls = { version = "0.3.0", optional = true } # rustls-tls hyper-rustls = { version = "0.24.0", default-features = false, optional = true } rustls = { version = "0.21.6", features = ["dangerous_configuration"], optional = true } tokio-rustls = { version = "0.24", optional = true } webpki-roots = { version = "0.25", optional = true } rustls-native-certs = { version = "0.6", optional = true } ## cookies cookie_crate = { version = "0.17.0", package = "cookie", optional = true } cookie_store = { version = "0.20.0", optional = true } ## compression async-compression = { version = "0.4.0", default-features = false, features = ["tokio"], optional = true } tokio-util = { version = "0.7.1", default-features = false, features = ["codec", "io"], optional = true } ## socks tokio-socks = { version = "0.5.1", optional = true } ## hickory-dns hickory-resolver = { version = "0.24", optional = true, features = ["tokio-runtime"] } # HTTP/3 experimental support h3 = { version = "0.0.3", optional = true } h3-quinn = { version = "0.0.4", optional = true } quinn = { version = "0.10", default-features = false, features = ["tls-rustls", "ring", "runtime-tokio"], optional = true } futures-channel = { version = "0.3", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] env_logger = "0.10" hyper = { version = "0.14", default-features = false, features = ["tcp", "stream", "http1", "http2", "client", "server", "runtime"] } serde = { version = "1.0", features = ["derive"] } libflate = "1.0" brotli_crate = { package = "brotli", version = "3.3.0" } doc-comment = "0.3" tokio = { version = "1.0", default-features = false, features = ["macros", "rt-multi-thread"] } futures-util = { version = "0.3.0", default-features = false, features = ["std", "alloc"] } [target.'cfg(windows)'.dependencies] winreg = "0.50.0" [target.'cfg(target_os = "macos")'.dependencies] system-configuration = "0.5.1" # wasm [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3.45" serde_json = "1.0" wasm-bindgen = "0.2.68" wasm-bindgen-futures = "0.4.18" wasm-streams = { version = "0.4", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] version = "0.3.25" features = [ "AbortController", "AbortSignal", "Headers", "Request", "RequestInit", "RequestMode", "Response", "Window", "FormData", "Blob", "BlobPropertyBag", "ServiceWorkerGlobalScope", "RequestCredentials", "File", "ReadableStream" ] [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen = { version = "0.2.68", features = ["serde-serialize"] } wasm-bindgen-test = "0.3" [[example]] name = "blocking" path = "examples/blocking.rs" required-features = ["blocking"] [[example]] name = "json_dynamic" path = "examples/json_dynamic.rs" required-features = ["json"] [[example]] name = "json_typed" path = "examples/json_typed.rs" required-features = ["json"] [[example]] name = "tor_socks" path = "examples/tor_socks.rs" required-features = ["socks"] [[example]] name = "form" path = "examples/form.rs" [[example]] name = "simple" path = "examples/simple.rs" [[test]] name = "blocking" path = "tests/blocking.rs" required-features = ["blocking"] [[test]] name = "cookie" path = "tests/cookie.rs" required-features = ["cookies"] [[test]] name = "gzip" path = "tests/gzip.rs" required-features = ["gzip"] [[test]] name = "brotli" path = "tests/brotli.rs" required-features = ["brotli"] [[test]] name = "deflate" path = "tests/deflate.rs" required-features = ["deflate"] [[test]] name = "multipart" path = "tests/multipart.rs" required-features = ["multipart"] reqwest-0.11.27/LICENSE-APACHE000064400000000000000000000251211046102023000134130ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 Sean McArthur Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. reqwest-0.11.27/LICENSE-MIT000064400000000000000000000020421046102023000131200ustar 00000000000000Copyright (c) 2016 Sean McArthur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. reqwest-0.11.27/README.md000064400000000000000000000046701046102023000127540ustar 00000000000000# reqwest [![crates.io](https://img.shields.io/crates/v/reqwest.svg)](https://crates.io/crates/reqwest) [![Documentation](https://docs.rs/reqwest/badge.svg)](https://docs.rs/reqwest) [![MIT/Apache-2 licensed](https://img.shields.io/crates/l/reqwest.svg)](./LICENSE-APACHE) [![CI](https://github.com/seanmonstar/reqwest/workflows/CI/badge.svg)](https://github.com/seanmonstar/reqwest/actions?query=workflow%3ACI) An ergonomic, batteries-included HTTP Client for Rust. - Async and blocking `Client`s - Plain bodies, JSON, urlencoded, multipart - Customizable redirect policy - HTTP Proxies - HTTPS via system-native TLS (or optionally, rustls) - Cookie Store - WASM ## Example This asynchronous example uses [Tokio](https://tokio.rs) and enables some optional features, so your `Cargo.toml` could look like this: ```toml [dependencies] reqwest = { version = "0.11", features = ["json"] } tokio = { version = "1", features = ["full"] } ``` And then the code: ```rust,no_run use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { let resp = reqwest::get("https://httpbin.org/ip") .await? .json::>() .await?; println!("{resp:#?}"); Ok(()) } ``` ## Commercial Support For private advice, support, reviews, access to the maintainer, and the like, reach out for [commercial support][sponsor]. ## Requirements On Linux: - OpenSSL with headers. See https://docs.rs/openssl for supported versions and more details. Alternatively you can enable the `native-tls-vendored` feature to compile a copy of OpenSSL. On Windows and macOS: - Nothing. Reqwest uses [rust-native-tls](https://github.com/sfackler/rust-native-tls), which will use the operating system TLS framework if available, meaning Windows and macOS. On Linux, it will use the available OpenSSL or fail to build if not found. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ## Sponsors Support this project by becoming a [sponsor][]. [sponsor]: https://seanmonstar.com/sponsor reqwest-0.11.27/examples/blocking.rs000064400000000000000000000015621046102023000154460ustar 00000000000000//! `cargo run --example blocking --features=blocking` #![deny(warnings)] fn main() -> Result<(), Box> { env_logger::init(); // Some simple CLI args requirements... let url = match std::env::args().nth(1) { Some(url) => url, None => { println!("No CLI URL provided, using default."); "https://hyper.rs".into() } }; eprintln!("Fetching {url:?}..."); // reqwest::blocking::get() is a convenience function. // // In most cases, you should create/build a reqwest::Client and reuse // it for all requests. let mut res = reqwest::blocking::get(url)?; eprintln!("Response: {:?} {}", res.version(), res.status()); eprintln!("Headers: {:#?}\n", res.headers()); // copy the response body directly to stdout res.copy_to(&mut std::io::stdout())?; Ok(()) } reqwest-0.11.27/examples/form.rs000064400000000000000000000015131046102023000146150ustar 00000000000000// Short example of a POST request with form data. // // This is using the `tokio` runtime. You'll need the following dependency: // // `tokio = { version = "1", features = ["full"] }` #[cfg(not(target_arch = "wasm32"))] #[tokio::main] async fn main() { let response = reqwest::Client::new() .post("http://www.baidu.com") .form(&[("one", "1")]) .send() .await .expect("send"); println!("Response status {}", response.status()); } // The [cfg(not(target_arch = "wasm32"))] above prevent building the tokio::main function // for wasm32 target, because tokio isn't compatible with wasm32. // If you aren't building for wasm32, you don't need that line. // The two lines below avoid the "'main' function not found" error when building for wasm32 target. #[cfg(target_arch = "wasm32")] fn main() {} reqwest-0.11.27/examples/h3_simple.rs000064400000000000000000000027561046102023000155470ustar 00000000000000#![deny(warnings)] // This is using the `tokio` runtime. You'll need the following dependency: // // `tokio = { version = "1", features = ["full"] }` #[cfg(feature = "http3")] #[cfg(not(target_arch = "wasm32"))] #[tokio::main] async fn main() -> Result<(), reqwest::Error> { use http::Version; use reqwest::{Client, IntoUrl, Response}; async fn get(url: T) -> reqwest::Result { Client::builder() .http3_prior_knowledge() .build()? .get(url) .version(Version::HTTP_3) .send() .await } // Some simple CLI args requirements... let url = match std::env::args().nth(1) { Some(url) => url, None => { println!("No CLI URL provided, using default."); "https://hyper.rs".into() } }; eprintln!("Fetching {url:?}..."); let res = get(url).await?; eprintln!("Response: {:?} {}", res.version(), res.status()); eprintln!("Headers: {:#?}\n", res.headers()); let body = res.text().await?; println!("{body}"); Ok(()) } // The [cfg(not(target_arch = "wasm32"))] above prevent building the tokio::main function // for wasm32 target, because tokio isn't compatible with wasm32. // If you aren't building for wasm32, you don't need that line. // The two lines below avoid the "'main' function not found" error when building for wasm32 target. #[cfg(any(target_arch = "wasm32", not(feature = "http3")))] fn main() {} reqwest-0.11.27/examples/json_dynamic.rs000064400000000000000000000022701046102023000163300ustar 00000000000000//! This example illustrates the way to send and receive arbitrary JSON. //! //! This is useful for some ad-hoc experiments and situations when you don't //! really care about the structure of the JSON and just need to display it or //! process it at runtime. // This is using the `tokio` runtime. You'll need the following dependency: // // `tokio = { version = "1", features = ["full"] }` #[tokio::main] async fn main() -> Result<(), reqwest::Error> { let echo_json: serde_json::Value = reqwest::Client::new() .post("https://jsonplaceholder.typicode.com/posts") .json(&serde_json::json!({ "title": "Reqwest.rs", "body": "https://docs.rs/reqwest", "userId": 1 })) .send() .await? .json() .await?; println!("{echo_json:#?}"); // Object( // { // "body": String( // "https://docs.rs/reqwest" // ), // "id": Number( // 101 // ), // "title": String( // "Reqwest.rs" // ), // "userId": Number( // 1 // ) // } // ) Ok(()) } reqwest-0.11.27/examples/json_typed.rs000064400000000000000000000023651046102023000160360ustar 00000000000000//! This example illustrates the way to send and receive statically typed JSON. //! //! In contrast to the arbitrary JSON example, this brings up the full power of //! Rust compile-time type system guaranties though it requires a little bit //! more code. // These require the `serde` dependency. use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct Post { id: Option, title: String, body: String, #[serde(rename = "userId")] user_id: i32, } // This is using the `tokio` runtime. You'll need the following dependency: // // `tokio = { version = "1", features = ["full"] }` #[tokio::main] async fn main() -> Result<(), reqwest::Error> { let new_post = Post { id: None, title: "Reqwest.rs".into(), body: "https://docs.rs/reqwest".into(), user_id: 1, }; let new_post: Post = reqwest::Client::new() .post("https://jsonplaceholder.typicode.com/posts") .json(&new_post) .send() .await? .json() .await?; println!("{new_post:#?}"); // Post { // id: Some( // 101 // ), // title: "Reqwest.rs", // body: "https://docs.rs/reqwest", // user_id: 1 // } Ok(()) } reqwest-0.11.27/examples/simple.rs000064400000000000000000000023651046102023000151510ustar 00000000000000#![deny(warnings)] // This is using the `tokio` runtime. You'll need the following dependency: // // `tokio = { version = "1", features = ["full"] }` #[cfg(not(target_arch = "wasm32"))] #[tokio::main] async fn main() -> Result<(), reqwest::Error> { // Some simple CLI args requirements... let url = if let Some(url) = std::env::args().nth(1) { url } else { println!("No CLI URL provided, using default."); "https://hyper.rs".into() }; eprintln!("Fetching {url:?}..."); // reqwest::get() is a convenience function. // // In most cases, you should create/build a reqwest::Client and reuse // it for all requests. let res = reqwest::get(url).await?; eprintln!("Response: {:?} {}", res.version(), res.status()); eprintln!("Headers: {:#?}\n", res.headers()); let body = res.text().await?; println!("{body}"); Ok(()) } // The [cfg(not(target_arch = "wasm32"))] above prevent building the tokio::main function // for wasm32 target, because tokio isn't compatible with wasm32. // If you aren't building for wasm32, you don't need that line. // The two lines below avoid the "'main' function not found" error when building for wasm32 target. #[cfg(target_arch = "wasm32")] fn main() {} reqwest-0.11.27/examples/tor_socks.rs000064400000000000000000000015001046102023000156540ustar 00000000000000#![deny(warnings)] // This is using the `tokio` runtime. You'll need the following dependency: // // `tokio = { version = "1", features = ["full"] }` #[tokio::main] async fn main() -> Result<(), reqwest::Error> { // Make sure you are running tor and this is your socks port let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:9050").expect("tor proxy should be there"); let client = reqwest::Client::builder() .proxy(proxy) .build() .expect("should be able to build reqwest client"); let res = client.get("https://check.torproject.org").send().await?; println!("Status: {}", res.status()); let text = res.text().await?; let is_tor = text.contains("Congratulations. This browser is configured to use Tor."); println!("Is Tor: {is_tor}"); assert!(is_tor); Ok(()) } reqwest-0.11.27/src/async_impl/body.rs000064400000000000000000000222021046102023000157140ustar 00000000000000use std::fmt; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use bytes::Bytes; use futures_core::Stream; use http_body::Body as HttpBody; use pin_project_lite::pin_project; use sync_wrapper::SyncWrapper; #[cfg(feature = "stream")] use tokio::fs::File; use tokio::time::Sleep; #[cfg(feature = "stream")] use tokio_util::io::ReaderStream; /// An asynchronous request body. pub struct Body { inner: Inner, } // The `Stream` trait isn't stable, so the impl isn't public. pub(crate) struct ImplStream(Body); enum Inner { Reusable(Bytes), Streaming { body: Pin< Box< dyn HttpBody> + Send + Sync, >, >, timeout: Option>>, }, } pin_project! { struct WrapStream { #[pin] inner: SyncWrapper, } } struct WrapHyper(hyper::Body); impl Body { /// Returns a reference to the internal data of the `Body`. /// /// `None` is returned, if the underlying data is a stream. pub fn as_bytes(&self) -> Option<&[u8]> { match &self.inner { Inner::Reusable(bytes) => Some(bytes.as_ref()), Inner::Streaming { .. } => None, } } /// Wrap a futures `Stream` in a box inside `Body`. /// /// # Example /// /// ``` /// # use reqwest::Body; /// # use futures_util; /// # fn main() { /// let chunks: Vec> = vec![ /// Ok("hello"), /// Ok(" "), /// Ok("world"), /// ]; /// /// let stream = futures_util::stream::iter(chunks); /// /// let body = Body::wrap_stream(stream); /// # } /// ``` /// /// # Optional /// /// This requires the `stream` feature to be enabled. #[cfg(feature = "stream")] #[cfg_attr(docsrs, doc(cfg(feature = "stream")))] pub fn wrap_stream(stream: S) -> Body where S: futures_core::stream::TryStream + Send + 'static, S::Error: Into>, Bytes: From, { Body::stream(stream) } pub(crate) fn stream(stream: S) -> Body where S: futures_core::stream::TryStream + Send + 'static, S::Error: Into>, Bytes: From, { use futures_util::TryStreamExt; let body = Box::pin(WrapStream { inner: SyncWrapper::new(stream.map_ok(Bytes::from).map_err(Into::into)), }); Body { inner: Inner::Streaming { body, timeout: None, }, } } pub(crate) fn response(body: hyper::Body, timeout: Option>>) -> Body { Body { inner: Inner::Streaming { body: Box::pin(WrapHyper(body)), timeout, }, } } #[cfg(feature = "blocking")] pub(crate) fn wrap(body: hyper::Body) -> Body { Body { inner: Inner::Streaming { body: Box::pin(WrapHyper(body)), timeout: None, }, } } pub(crate) fn empty() -> Body { Body::reusable(Bytes::new()) } pub(crate) fn reusable(chunk: Bytes) -> Body { Body { inner: Inner::Reusable(chunk), } } pub(crate) fn try_reuse(self) -> (Option, Self) { let reuse = match self.inner { Inner::Reusable(ref chunk) => Some(chunk.clone()), Inner::Streaming { .. } => None, }; (reuse, self) } pub(crate) fn try_clone(&self) -> Option { match self.inner { Inner::Reusable(ref chunk) => Some(Body::reusable(chunk.clone())), Inner::Streaming { .. } => None, } } pub(crate) fn into_stream(self) -> ImplStream { ImplStream(self) } #[cfg(feature = "multipart")] pub(crate) fn content_length(&self) -> Option { match self.inner { Inner::Reusable(ref bytes) => Some(bytes.len() as u64), Inner::Streaming { ref body, .. } => body.size_hint().exact(), } } } impl From for Body { #[inline] fn from(body: hyper::Body) -> Body { Self { inner: Inner::Streaming { body: Box::pin(WrapHyper(body)), timeout: None, }, } } } impl From for Body { #[inline] fn from(bytes: Bytes) -> Body { Body::reusable(bytes) } } impl From> for Body { #[inline] fn from(vec: Vec) -> Body { Body::reusable(vec.into()) } } impl From<&'static [u8]> for Body { #[inline] fn from(s: &'static [u8]) -> Body { Body::reusable(Bytes::from_static(s)) } } impl From for Body { #[inline] fn from(s: String) -> Body { Body::reusable(s.into()) } } impl From<&'static str> for Body { #[inline] fn from(s: &'static str) -> Body { s.as_bytes().into() } } #[cfg(feature = "stream")] #[cfg_attr(docsrs, doc(cfg(feature = "stream")))] impl From for Body { #[inline] fn from(file: File) -> Body { Body::wrap_stream(ReaderStream::new(file)) } } impl fmt::Debug for Body { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Body").finish() } } // ===== impl ImplStream ===== impl HttpBody for ImplStream { type Data = Bytes; type Error = crate::Error; fn poll_data( mut self: Pin<&mut Self>, cx: &mut Context, ) -> Poll>> { let opt_try_chunk = match self.0.inner { Inner::Streaming { ref mut body, ref mut timeout, } => { if let Some(ref mut timeout) = timeout { if let Poll::Ready(()) = timeout.as_mut().poll(cx) { return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut)))); } } futures_core::ready!(Pin::new(body).poll_data(cx)) .map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::body)) } Inner::Reusable(ref mut bytes) => { if bytes.is_empty() { None } else { Some(Ok(std::mem::replace(bytes, Bytes::new()))) } } }; Poll::Ready(opt_try_chunk) } fn poll_trailers( self: Pin<&mut Self>, _cx: &mut Context, ) -> Poll, Self::Error>> { Poll::Ready(Ok(None)) } fn is_end_stream(&self) -> bool { match self.0.inner { Inner::Streaming { ref body, .. } => body.is_end_stream(), Inner::Reusable(ref bytes) => bytes.is_empty(), } } fn size_hint(&self) -> http_body::SizeHint { match self.0.inner { Inner::Streaming { ref body, .. } => body.size_hint(), Inner::Reusable(ref bytes) => { let mut hint = http_body::SizeHint::default(); hint.set_exact(bytes.len() as u64); hint } } } } impl Stream for ImplStream { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { self.poll_data(cx) } } // ===== impl WrapStream ===== impl HttpBody for WrapStream where S: Stream>, D: Into, E: Into>, { type Data = Bytes; type Error = E; fn poll_data( self: Pin<&mut Self>, cx: &mut Context, ) -> Poll>> { let item = futures_core::ready!(self.project().inner.get_pin_mut().poll_next(cx)?); Poll::Ready(item.map(|val| Ok(val.into()))) } fn poll_trailers( self: Pin<&mut Self>, _cx: &mut Context, ) -> Poll, Self::Error>> { Poll::Ready(Ok(None)) } } // ===== impl WrapHyper ===== impl HttpBody for WrapHyper { type Data = Bytes; type Error = Box; fn poll_data( mut self: Pin<&mut Self>, cx: &mut Context, ) -> Poll>> { // safe pin projection Pin::new(&mut self.0) .poll_data(cx) .map(|opt| opt.map(|res| res.map_err(Into::into))) } fn poll_trailers( self: Pin<&mut Self>, _cx: &mut Context, ) -> Poll, Self::Error>> { Poll::Ready(Ok(None)) } fn is_end_stream(&self) -> bool { self.0.is_end_stream() } fn size_hint(&self) -> http_body::SizeHint { HttpBody::size_hint(&self.0) } } #[cfg(test)] mod tests { use super::Body; #[test] fn test_as_bytes() { let test_data = b"Test body"; let body = Body::from(&test_data[..]); assert_eq!(body.as_bytes(), Some(&test_data[..])); } } reqwest-0.11.27/src/async_impl/client.rs000064400000000000000000002640311046102023000162450ustar 00000000000000#[cfg(any(feature = "native-tls", feature = "__rustls",))] use std::any::Any; use std::net::IpAddr; use std::sync::Arc; use std::time::Duration; use std::{collections::HashMap, convert::TryInto, net::SocketAddr}; use std::{fmt, str}; use bytes::Bytes; use http::header::{ Entry, HeaderMap, HeaderValue, ACCEPT, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, LOCATION, PROXY_AUTHORIZATION, RANGE, REFERER, TRANSFER_ENCODING, USER_AGENT, }; use http::uri::Scheme; use http::Uri; use hyper::client::{HttpConnector, ResponseFuture as HyperResponseFuture}; #[cfg(feature = "native-tls-crate")] use native_tls_crate::TlsConnector; use pin_project_lite::pin_project; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::time::Sleep; use super::decoder::Accepts; use super::request::{Request, RequestBuilder}; use super::response::Response; use super::Body; #[cfg(feature = "http3")] use crate::async_impl::h3_client::connect::H3Connector; #[cfg(feature = "http3")] use crate::async_impl::h3_client::{H3Client, H3ResponseFuture}; use crate::connect::Connector; #[cfg(feature = "cookies")] use crate::cookie; #[cfg(feature = "hickory-dns")] use crate::dns::hickory::HickoryDnsResolver; use crate::dns::{gai::GaiResolver, DnsResolverWithOverrides, DynResolver, Resolve}; use crate::error; use crate::into_url::try_uri; use crate::redirect::{self, remove_sensitive_headers}; #[cfg(feature = "__tls")] use crate::tls::{self, TlsBackend}; #[cfg(feature = "__tls")] use crate::Certificate; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{IntoUrl, Method, Proxy, StatusCode, Url}; use log::{debug, trace}; #[cfg(feature = "http3")] use quinn::TransportConfig; #[cfg(feature = "http3")] use quinn::VarInt; /// An asynchronous `Client` to make Requests with. /// /// The Client has various configuration values to tweak, but the defaults /// are set to what is usually the most commonly desired value. To configure a /// `Client`, use `Client::builder()`. /// /// The `Client` holds a connection pool internally, so it is advised that /// you create one and **reuse** it. /// /// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it, /// because it already uses an [`Arc`] internally. /// /// [`Rc`]: std::rc::Rc #[derive(Clone)] pub struct Client { inner: Arc, } /// A `ClientBuilder` can be used to create a `Client` with custom configuration. #[must_use] pub struct ClientBuilder { config: Config, } enum HttpVersionPref { Http1, Http2, #[cfg(feature = "http3")] Http3, All, } struct Config { // NOTE: When adding a new field, update `fmt::Debug for ClientBuilder` accepts: Accepts, headers: HeaderMap, #[cfg(feature = "native-tls")] hostname_verification: bool, #[cfg(feature = "__tls")] certs_verification: bool, #[cfg(feature = "__tls")] tls_sni: bool, connect_timeout: Option, connection_verbose: bool, pool_idle_timeout: Option, pool_max_idle_per_host: usize, tcp_keepalive: Option, #[cfg(any(feature = "native-tls", feature = "__rustls"))] identity: Option, proxies: Vec, auto_sys_proxy: bool, redirect_policy: redirect::Policy, referer: bool, timeout: Option, #[cfg(feature = "__tls")] root_certs: Vec, #[cfg(feature = "__tls")] tls_built_in_root_certs: bool, #[cfg(feature = "__tls")] min_tls_version: Option, #[cfg(feature = "__tls")] max_tls_version: Option, #[cfg(feature = "__tls")] tls_info: bool, #[cfg(feature = "__tls")] tls: TlsBackend, http_version_pref: HttpVersionPref, http09_responses: bool, http1_title_case_headers: bool, http1_allow_obsolete_multiline_headers_in_responses: bool, http1_ignore_invalid_headers_in_responses: bool, http1_allow_spaces_after_header_name_in_responses: bool, http2_initial_stream_window_size: Option, http2_initial_connection_window_size: Option, http2_adaptive_window: bool, http2_max_frame_size: Option, http2_keep_alive_interval: Option, http2_keep_alive_timeout: Option, http2_keep_alive_while_idle: bool, local_address: Option, nodelay: bool, #[cfg(feature = "cookies")] cookie_store: Option>, hickory_dns: bool, error: Option, https_only: bool, #[cfg(feature = "http3")] tls_enable_early_data: bool, #[cfg(feature = "http3")] quic_max_idle_timeout: Option, #[cfg(feature = "http3")] quic_stream_receive_window: Option, #[cfg(feature = "http3")] quic_receive_window: Option, #[cfg(feature = "http3")] quic_send_window: Option, dns_overrides: HashMap>, dns_resolver: Option>, } impl Default for ClientBuilder { fn default() -> Self { Self::new() } } impl ClientBuilder { /// Constructs a new `ClientBuilder`. /// /// This is the same as `Client::builder()`. pub fn new() -> ClientBuilder { let mut headers: HeaderMap = HeaderMap::with_capacity(2); headers.insert(ACCEPT, HeaderValue::from_static("*/*")); ClientBuilder { config: Config { error: None, accepts: Accepts::default(), headers, #[cfg(feature = "native-tls")] hostname_verification: true, #[cfg(feature = "__tls")] certs_verification: true, #[cfg(feature = "__tls")] tls_sni: true, connect_timeout: None, connection_verbose: false, pool_idle_timeout: Some(Duration::from_secs(90)), pool_max_idle_per_host: std::usize::MAX, // TODO: Re-enable default duration once hyper's HttpConnector is fixed // to no longer error when an option fails. tcp_keepalive: None, //Some(Duration::from_secs(60)), proxies: Vec::new(), auto_sys_proxy: true, redirect_policy: redirect::Policy::default(), referer: true, timeout: None, #[cfg(feature = "__tls")] root_certs: Vec::new(), #[cfg(feature = "__tls")] tls_built_in_root_certs: true, #[cfg(any(feature = "native-tls", feature = "__rustls"))] identity: None, #[cfg(feature = "__tls")] min_tls_version: None, #[cfg(feature = "__tls")] max_tls_version: None, #[cfg(feature = "__tls")] tls_info: false, #[cfg(feature = "__tls")] tls: TlsBackend::default(), http_version_pref: HttpVersionPref::All, http09_responses: false, http1_title_case_headers: false, http1_allow_obsolete_multiline_headers_in_responses: false, http1_ignore_invalid_headers_in_responses: false, http1_allow_spaces_after_header_name_in_responses: false, http2_initial_stream_window_size: None, http2_initial_connection_window_size: None, http2_adaptive_window: false, http2_max_frame_size: None, http2_keep_alive_interval: None, http2_keep_alive_timeout: None, http2_keep_alive_while_idle: false, local_address: None, nodelay: true, hickory_dns: cfg!(feature = "hickory-dns"), #[cfg(feature = "cookies")] cookie_store: None, https_only: false, dns_overrides: HashMap::new(), #[cfg(feature = "http3")] tls_enable_early_data: false, #[cfg(feature = "http3")] quic_max_idle_timeout: None, #[cfg(feature = "http3")] quic_stream_receive_window: None, #[cfg(feature = "http3")] quic_receive_window: None, #[cfg(feature = "http3")] quic_send_window: None, dns_resolver: None, }, } } /// Returns a `Client` that uses this `ClientBuilder` configuration. /// /// # Errors /// /// This method fails if a TLS backend cannot be initialized, or the resolver /// cannot load the system configuration. pub fn build(self) -> crate::Result { let config = self.config; if let Some(err) = config.error { return Err(err); } let mut proxies = config.proxies; if config.auto_sys_proxy { proxies.push(Proxy::system()); } let proxies = Arc::new(proxies); #[allow(unused)] #[cfg(feature = "http3")] let mut h3_connector = None; let mut connector = { #[cfg(feature = "__tls")] fn user_agent(headers: &HeaderMap) -> Option { headers.get(USER_AGENT).cloned() } let mut resolver: Arc = match config.hickory_dns { false => Arc::new(GaiResolver::new()), #[cfg(feature = "hickory-dns")] true => Arc::new(HickoryDnsResolver::default()), #[cfg(not(feature = "hickory-dns"))] true => unreachable!("hickory-dns shouldn't be enabled unless the feature is"), }; if let Some(dns_resolver) = config.dns_resolver { resolver = dns_resolver; } if !config.dns_overrides.is_empty() { resolver = Arc::new(DnsResolverWithOverrides::new( resolver, config.dns_overrides, )); } let mut http = HttpConnector::new_with_resolver(DynResolver::new(resolver.clone())); http.set_connect_timeout(config.connect_timeout); #[cfg(all(feature = "http3", feature = "__rustls"))] let build_h3_connector = |resolver, tls, quic_max_idle_timeout: Option, quic_stream_receive_window, quic_receive_window, quic_send_window, local_address, http_version_pref: &HttpVersionPref| { let mut transport_config = TransportConfig::default(); if let Some(max_idle_timeout) = quic_max_idle_timeout { transport_config.max_idle_timeout(Some( max_idle_timeout.try_into().map_err(error::builder)?, )); } if let Some(stream_receive_window) = quic_stream_receive_window { transport_config.stream_receive_window(stream_receive_window); } if let Some(receive_window) = quic_receive_window { transport_config.receive_window(receive_window); } if let Some(send_window) = quic_send_window { transport_config.send_window(send_window); } let res = H3Connector::new( DynResolver::new(resolver), tls, local_address, transport_config, ); match res { Ok(connector) => Ok(Some(connector)), Err(err) => { if let HttpVersionPref::Http3 = http_version_pref { Err(error::builder(err)) } else { Ok(None) } } } }; #[cfg(feature = "__tls")] match config.tls { #[cfg(feature = "default-tls")] TlsBackend::Default => { let mut tls = TlsConnector::builder(); #[cfg(all(feature = "native-tls-alpn", not(feature = "http3")))] { match config.http_version_pref { HttpVersionPref::Http1 => { tls.request_alpns(&["http/1.1"]); } HttpVersionPref::Http2 => { tls.request_alpns(&["h2"]); } HttpVersionPref::All => { tls.request_alpns(&["h2", "http/1.1"]); } } } #[cfg(feature = "native-tls")] { tls.danger_accept_invalid_hostnames(!config.hostname_verification); } tls.danger_accept_invalid_certs(!config.certs_verification); tls.use_sni(config.tls_sni); tls.disable_built_in_roots(!config.tls_built_in_root_certs); for cert in config.root_certs { cert.add_to_native_tls(&mut tls); } #[cfg(feature = "native-tls")] { if let Some(id) = config.identity { id.add_to_native_tls(&mut tls)?; } } #[cfg(all(feature = "__rustls", not(feature = "native-tls")))] { // Default backend + rustls Identity doesn't work. if let Some(_id) = config.identity { return Err(crate::error::builder("incompatible TLS identity type")); } } if let Some(min_tls_version) = config.min_tls_version { let protocol = min_tls_version.to_native_tls().ok_or_else(|| { // TLS v1.3. This would be entirely reasonable, // native-tls just doesn't support it. // https://github.com/sfackler/rust-native-tls/issues/140 crate::error::builder("invalid minimum TLS version for backend") })?; tls.min_protocol_version(Some(protocol)); } if let Some(max_tls_version) = config.max_tls_version { let protocol = max_tls_version.to_native_tls().ok_or_else(|| { // TLS v1.3. // We could arguably do max_protocol_version(None), given // that 1.4 does not exist yet, but that'd get messy in the // future. crate::error::builder("invalid maximum TLS version for backend") })?; tls.max_protocol_version(Some(protocol)); } Connector::new_default_tls( http, tls, proxies.clone(), user_agent(&config.headers), config.local_address, config.nodelay, config.tls_info, )? } #[cfg(feature = "native-tls")] TlsBackend::BuiltNativeTls(conn) => Connector::from_built_default_tls( http, conn, proxies.clone(), user_agent(&config.headers), config.local_address, config.nodelay, config.tls_info, ), #[cfg(feature = "__rustls")] TlsBackend::BuiltRustls(conn) => { #[cfg(feature = "http3")] { h3_connector = build_h3_connector( resolver, conn.clone(), config.quic_max_idle_timeout, config.quic_stream_receive_window, config.quic_receive_window, config.quic_send_window, config.local_address, &config.http_version_pref, )?; } Connector::new_rustls_tls( http, conn, proxies.clone(), user_agent(&config.headers), config.local_address, config.nodelay, config.tls_info, ) } #[cfg(feature = "__rustls")] TlsBackend::Rustls => { use crate::tls::NoVerifier; // Set root certificates. let mut root_cert_store = rustls::RootCertStore::empty(); for cert in config.root_certs { cert.add_to_rustls(&mut root_cert_store)?; } #[cfg(feature = "rustls-tls-webpki-roots")] if config.tls_built_in_root_certs { use rustls::OwnedTrustAnchor; let trust_anchors = webpki_roots::TLS_SERVER_ROOTS.iter().map(|trust_anchor| { OwnedTrustAnchor::from_subject_spki_name_constraints( trust_anchor.subject, trust_anchor.spki, trust_anchor.name_constraints, ) }); root_cert_store.add_trust_anchors(trust_anchors); } #[cfg(feature = "rustls-tls-native-roots")] if config.tls_built_in_root_certs { let mut valid_count = 0; let mut invalid_count = 0; for cert in rustls_native_certs::load_native_certs() .map_err(crate::error::builder)? { let cert = rustls::Certificate(cert.0); // Continue on parsing errors, as native stores often include ancient or syntactically // invalid certificates, like root certificates without any X509 extensions. // Inspiration: https://github.com/rustls/rustls/blob/633bf4ba9d9521a95f68766d04c22e2b01e68318/rustls/src/anchors.rs#L105-L112 match root_cert_store.add(&cert) { Ok(_) => valid_count += 1, Err(err) => { invalid_count += 1; log::warn!( "rustls failed to parse DER certificate {err:?} {cert:?}" ); } } } if valid_count == 0 && invalid_count > 0 { return Err(crate::error::builder( "zero valid certificates found in native root store", )); } } // Set TLS versions. let mut versions = rustls::ALL_VERSIONS.to_vec(); if let Some(min_tls_version) = config.min_tls_version { versions.retain(|&supported_version| { match tls::Version::from_rustls(supported_version.version) { Some(version) => version >= min_tls_version, // Assume it's so new we don't know about it, allow it // (as of writing this is unreachable) None => true, } }); } if let Some(max_tls_version) = config.max_tls_version { versions.retain(|&supported_version| { match tls::Version::from_rustls(supported_version.version) { Some(version) => version <= max_tls_version, None => false, } }); } // Build TLS config let config_builder = rustls::ClientConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_protocol_versions(&versions) .map_err(crate::error::builder)? .with_root_certificates(root_cert_store); // Finalize TLS config let mut tls = if let Some(id) = config.identity { id.add_to_rustls(config_builder)? } else { config_builder.with_no_client_auth() }; // Certificate verifier if !config.certs_verification { tls.dangerous() .set_certificate_verifier(Arc::new(NoVerifier)); } tls.enable_sni = config.tls_sni; // ALPN protocol match config.http_version_pref { HttpVersionPref::Http1 => { tls.alpn_protocols = vec!["http/1.1".into()]; } HttpVersionPref::Http2 => { tls.alpn_protocols = vec!["h2".into()]; } #[cfg(feature = "http3")] HttpVersionPref::Http3 => { tls.alpn_protocols = vec!["h3".into()]; } HttpVersionPref::All => { tls.alpn_protocols = vec!["h2".into(), "http/1.1".into()]; } } #[cfg(feature = "http3")] { tls.enable_early_data = config.tls_enable_early_data; h3_connector = build_h3_connector( resolver, tls.clone(), config.quic_max_idle_timeout, config.quic_stream_receive_window, config.quic_receive_window, config.quic_send_window, config.local_address, &config.http_version_pref, )?; } Connector::new_rustls_tls( http, tls, proxies.clone(), user_agent(&config.headers), config.local_address, config.nodelay, config.tls_info, ) } #[cfg(any(feature = "native-tls", feature = "__rustls",))] TlsBackend::UnknownPreconfigured => { return Err(crate::error::builder( "Unknown TLS backend passed to `use_preconfigured_tls`", )); } } #[cfg(not(feature = "__tls"))] Connector::new(http, proxies.clone(), config.local_address, config.nodelay) }; connector.set_timeout(config.connect_timeout); connector.set_verbose(config.connection_verbose); let mut builder = hyper::Client::builder(); if matches!(config.http_version_pref, HttpVersionPref::Http2) { builder.http2_only(true); } if let Some(http2_initial_stream_window_size) = config.http2_initial_stream_window_size { builder.http2_initial_stream_window_size(http2_initial_stream_window_size); } if let Some(http2_initial_connection_window_size) = config.http2_initial_connection_window_size { builder.http2_initial_connection_window_size(http2_initial_connection_window_size); } if config.http2_adaptive_window { builder.http2_adaptive_window(true); } if let Some(http2_max_frame_size) = config.http2_max_frame_size { builder.http2_max_frame_size(http2_max_frame_size); } if let Some(http2_keep_alive_interval) = config.http2_keep_alive_interval { builder.http2_keep_alive_interval(http2_keep_alive_interval); } if let Some(http2_keep_alive_timeout) = config.http2_keep_alive_timeout { builder.http2_keep_alive_timeout(http2_keep_alive_timeout); } if config.http2_keep_alive_while_idle { builder.http2_keep_alive_while_idle(true); } builder.pool_idle_timeout(config.pool_idle_timeout); builder.pool_max_idle_per_host(config.pool_max_idle_per_host); connector.set_keepalive(config.tcp_keepalive); if config.http09_responses { builder.http09_responses(true); } if config.http1_title_case_headers { builder.http1_title_case_headers(true); } if config.http1_allow_obsolete_multiline_headers_in_responses { builder.http1_allow_obsolete_multiline_headers_in_responses(true); } if config.http1_ignore_invalid_headers_in_responses { builder.http1_ignore_invalid_headers_in_responses(true); } if config.http1_allow_spaces_after_header_name_in_responses { builder.http1_allow_spaces_after_header_name_in_responses(true); } let proxies_maybe_http_auth = proxies.iter().any(|p| p.maybe_has_http_auth()); Ok(Client { inner: Arc::new(ClientRef { accepts: config.accepts, #[cfg(feature = "cookies")] cookie_store: config.cookie_store, // Use match instead of map since config is partially moved // and it cannot be used in closure #[cfg(feature = "http3")] h3_client: match h3_connector { Some(h3_connector) => { Some(H3Client::new(h3_connector, config.pool_idle_timeout)) } None => None, }, hyper: builder.build(connector), headers: config.headers, redirect_policy: config.redirect_policy, referer: config.referer, request_timeout: config.timeout, proxies, proxies_maybe_http_auth, https_only: config.https_only, }), }) } // Higher-level options /// Sets the `User-Agent` header to be used by this client. /// /// # Example /// /// ```rust /// # async fn doc() -> Result<(), reqwest::Error> { /// // Name your user agent after your app? /// static APP_USER_AGENT: &str = concat!( /// env!("CARGO_PKG_NAME"), /// "/", /// env!("CARGO_PKG_VERSION"), /// ); /// /// let client = reqwest::Client::builder() /// .user_agent(APP_USER_AGENT) /// .build()?; /// let res = client.get("https://www.rust-lang.org").send().await?; /// # Ok(()) /// # } /// ``` pub fn user_agent(mut self, value: V) -> ClientBuilder where V: TryInto, V::Error: Into, { match value.try_into() { Ok(value) => { self.config.headers.insert(USER_AGENT, value); } Err(e) => { self.config.error = Some(crate::error::builder(e.into())); } }; self } /// Sets the default headers for every request. /// /// # Example /// /// ```rust /// use reqwest::header; /// # async fn doc() -> Result<(), reqwest::Error> { /// let mut headers = header::HeaderMap::new(); /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value")); /// /// // Consider marking security-sensitive headers with `set_sensitive`. /// let mut auth_value = header::HeaderValue::from_static("secret"); /// auth_value.set_sensitive(true); /// headers.insert(header::AUTHORIZATION, auth_value); /// /// // get a client builder /// let client = reqwest::Client::builder() /// .default_headers(headers) /// .build()?; /// let res = client.get("https://www.rust-lang.org").send().await?; /// # Ok(()) /// # } /// ``` /// /// Override the default headers: /// /// ```rust /// use reqwest::header; /// # async fn doc() -> Result<(), reqwest::Error> { /// let mut headers = header::HeaderMap::new(); /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value")); /// /// // get a client builder /// let client = reqwest::Client::builder() /// .default_headers(headers) /// .build()?; /// let res = client /// .get("https://www.rust-lang.org") /// .header("X-MY-HEADER", "new_value") /// .send() /// .await?; /// # Ok(()) /// # } /// ``` pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder { for (key, value) in headers.iter() { self.config.headers.insert(key, value.clone()); } self } /// Enable a persistent cookie store for the client. /// /// Cookies received in responses will be preserved and included in /// additional requests. /// /// By default, no cookie store is used. Enabling the cookie store /// with `cookie_store(true)` will set the store to a default implementation. /// It is **not** necessary to call [cookie_store(true)](crate::ClientBuilder::cookie_store) if [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) /// is used; calling [cookie_store(true)](crate::ClientBuilder::cookie_store) _after_ [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) will result /// in the provided `my_cookie_store` being **overridden** with a default implementation. /// /// # Optional /// /// This requires the optional `cookies` feature to be enabled. #[cfg(feature = "cookies")] #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookie_store(mut self, enable: bool) -> ClientBuilder { if enable { self.cookie_provider(Arc::new(cookie::Jar::default())) } else { self.config.cookie_store = None; self } } /// Set the persistent cookie store for the client. /// /// Cookies received in responses will be passed to this store, and /// additional requests will query this store for cookies. /// /// By default, no cookie store is used. It is **not** necessary to also call /// [cookie_store(true)](crate::ClientBuilder::cookie_store) if [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) is used; calling /// [cookie_store(true)](crate::ClientBuilder::cookie_store) _after_ [cookie_provider(my_cookie_store)](crate::ClientBuilder::cookie_provider) will result /// in the provided `my_cookie_store` being **overridden** with a default implementation. /// /// # Optional /// /// This requires the optional `cookies` feature to be enabled. #[cfg(feature = "cookies")] #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookie_provider( mut self, cookie_store: Arc, ) -> ClientBuilder { self.config.cookie_store = Some(cookie_store as _); self } /// Enable auto gzip decompression by checking the `Content-Encoding` response header. /// /// If auto gzip decompression is turned on: /// /// - When sending a request and if the request's headers do not already contain /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. /// The request body is **not** automatically compressed. /// - When receiving a response, if its headers contain a `Content-Encoding` value of /// `gzip`, both `Content-Encoding` and `Content-Length` are removed from the /// headers' set. The response body is automatically decompressed. /// /// If the `gzip` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `gzip` feature to be enabled #[cfg(feature = "gzip")] #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))] pub fn gzip(mut self, enable: bool) -> ClientBuilder { self.config.accepts.gzip = enable; self } /// Enable auto brotli decompression by checking the `Content-Encoding` response header. /// /// If auto brotli decompression is turned on: /// /// - When sending a request and if the request's headers do not already contain /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`. /// The request body is **not** automatically compressed. /// - When receiving a response, if its headers contain a `Content-Encoding` value of /// `br`, both `Content-Encoding` and `Content-Length` are removed from the /// headers' set. The response body is automatically decompressed. /// /// If the `brotli` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `brotli` feature to be enabled #[cfg(feature = "brotli")] #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))] pub fn brotli(mut self, enable: bool) -> ClientBuilder { self.config.accepts.brotli = enable; self } /// Enable auto deflate decompression by checking the `Content-Encoding` response header. /// /// If auto deflate decompression is turned on: /// /// - When sending a request and if the request's headers do not already contain /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `deflate`. /// The request body is **not** automatically compressed. /// - When receiving a response, if it's headers contain a `Content-Encoding` value that /// equals to `deflate`, both values `Content-Encoding` and `Content-Length` are removed from the /// headers' set. The response body is automatically decompressed. /// /// If the `deflate` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `deflate` feature to be enabled #[cfg(feature = "deflate")] #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))] pub fn deflate(mut self, enable: bool) -> ClientBuilder { self.config.accepts.deflate = enable; self } /// Disable auto response body gzip decompression. /// /// This method exists even if the optional `gzip` feature is not enabled. /// This can be used to ensure a `Client` doesn't use gzip decompression /// even if another dependency were to enable the optional `gzip` feature. pub fn no_gzip(self) -> ClientBuilder { #[cfg(feature = "gzip")] { self.gzip(false) } #[cfg(not(feature = "gzip"))] { self } } /// Disable auto response body brotli decompression. /// /// This method exists even if the optional `brotli` feature is not enabled. /// This can be used to ensure a `Client` doesn't use brotli decompression /// even if another dependency were to enable the optional `brotli` feature. pub fn no_brotli(self) -> ClientBuilder { #[cfg(feature = "brotli")] { self.brotli(false) } #[cfg(not(feature = "brotli"))] { self } } /// Disable auto response body deflate decompression. /// /// This method exists even if the optional `deflate` feature is not enabled. /// This can be used to ensure a `Client` doesn't use deflate decompression /// even if another dependency were to enable the optional `deflate` feature. pub fn no_deflate(self) -> ClientBuilder { #[cfg(feature = "deflate")] { self.deflate(false) } #[cfg(not(feature = "deflate"))] { self } } // Redirect options /// Set a `RedirectPolicy` for this client. /// /// Default will follow redirects up to a maximum of 10. pub fn redirect(mut self, policy: redirect::Policy) -> ClientBuilder { self.config.redirect_policy = policy; self } /// Enable or disable automatic setting of the `Referer` header. /// /// Default is `true`. pub fn referer(mut self, enable: bool) -> ClientBuilder { self.config.referer = enable; self } // Proxy options /// Add a `Proxy` to the list of proxies the `Client` will use. /// /// # Note /// /// Adding a proxy will disable the automatic usage of the "system" proxy. pub fn proxy(mut self, proxy: Proxy) -> ClientBuilder { self.config.proxies.push(proxy); self.config.auto_sys_proxy = false; self } /// Clear all `Proxies`, so `Client` will use no proxy anymore. /// /// # Note /// To add a proxy exclusion list, use [crate::proxy::Proxy::no_proxy()] /// on all desired proxies instead. /// /// This also disables the automatic usage of the "system" proxy. pub fn no_proxy(mut self) -> ClientBuilder { self.config.proxies.clear(); self.config.auto_sys_proxy = false; self } // Timeout options /// Enables a request timeout. /// /// The timeout is applied from when the request starts connecting until the /// response body has finished. /// /// Default is no timeout. pub fn timeout(mut self, timeout: Duration) -> ClientBuilder { self.config.timeout = Some(timeout); self } /// Set a timeout for only the connect phase of a `Client`. /// /// Default is `None`. /// /// # Note /// /// This **requires** the futures be executed in a tokio runtime with /// a tokio timer enabled. pub fn connect_timeout(mut self, timeout: Duration) -> ClientBuilder { self.config.connect_timeout = Some(timeout); self } /// Set whether connections should emit verbose logs. /// /// Enabling this option will emit [log][] messages at the `TRACE` level /// for read and write operations on connections. /// /// [log]: https://crates.io/crates/log pub fn connection_verbose(mut self, verbose: bool) -> ClientBuilder { self.config.connection_verbose = verbose; self } // HTTP options /// Set an optional timeout for idle sockets being kept-alive. /// /// Pass `None` to disable timeout. /// /// Default is 90 seconds. pub fn pool_idle_timeout(mut self, val: D) -> ClientBuilder where D: Into>, { self.config.pool_idle_timeout = val.into(); self } /// Sets the maximum idle connection per host allowed in the pool. pub fn pool_max_idle_per_host(mut self, max: usize) -> ClientBuilder { self.config.pool_max_idle_per_host = max; self } /// Send headers as title case instead of lowercase. pub fn http1_title_case_headers(mut self) -> ClientBuilder { self.config.http1_title_case_headers = true; self } /// Set whether HTTP/1 connections will accept obsolete line folding for /// header values. /// /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when /// parsing. pub fn http1_allow_obsolete_multiline_headers_in_responses( mut self, value: bool, ) -> ClientBuilder { self.config .http1_allow_obsolete_multiline_headers_in_responses = value; self } /// Sets whether invalid header lines should be silently ignored in HTTP/1 responses. pub fn http1_ignore_invalid_headers_in_responses(mut self, value: bool) -> ClientBuilder { self.config.http1_ignore_invalid_headers_in_responses = value; self } /// Set whether HTTP/1 connections will accept spaces between header /// names and the colon that follow them in responses. /// /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when /// parsing. pub fn http1_allow_spaces_after_header_name_in_responses( mut self, value: bool, ) -> ClientBuilder { self.config .http1_allow_spaces_after_header_name_in_responses = value; self } /// Only use HTTP/1. pub fn http1_only(mut self) -> ClientBuilder { self.config.http_version_pref = HttpVersionPref::Http1; self } /// Allow HTTP/0.9 responses pub fn http09_responses(mut self) -> ClientBuilder { self.config.http09_responses = true; self } /// Only use HTTP/2. pub fn http2_prior_knowledge(mut self) -> ClientBuilder { self.config.http_version_pref = HttpVersionPref::Http2; self } /// Only use HTTP/3. #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))] pub fn http3_prior_knowledge(mut self) -> ClientBuilder { self.config.http_version_pref = HttpVersionPref::Http3; self } /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control. /// /// Default is currently 65,535 but may change internally to optimize for common uses. pub fn http2_initial_stream_window_size(mut self, sz: impl Into>) -> ClientBuilder { self.config.http2_initial_stream_window_size = sz.into(); self } /// Sets the max connection-level flow control for HTTP2 /// /// Default is currently 65,535 but may change internally to optimize for common uses. pub fn http2_initial_connection_window_size( mut self, sz: impl Into>, ) -> ClientBuilder { self.config.http2_initial_connection_window_size = sz.into(); self } /// Sets whether to use an adaptive flow control. /// /// Enabling this will override the limits set in `http2_initial_stream_window_size` and /// `http2_initial_connection_window_size`. pub fn http2_adaptive_window(mut self, enabled: bool) -> ClientBuilder { self.config.http2_adaptive_window = enabled; self } /// Sets the maximum frame size to use for HTTP2. /// /// Default is currently 16,384 but may change internally to optimize for common uses. pub fn http2_max_frame_size(mut self, sz: impl Into>) -> ClientBuilder { self.config.http2_max_frame_size = sz.into(); self } /// Sets an interval for HTTP2 Ping frames should be sent to keep a connection alive. /// /// Pass `None` to disable HTTP2 keep-alive. /// Default is currently disabled. pub fn http2_keep_alive_interval( mut self, interval: impl Into>, ) -> ClientBuilder { self.config.http2_keep_alive_interval = interval.into(); self } /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. /// /// If the ping is not acknowledged within the timeout, the connection will be closed. /// Does nothing if `http2_keep_alive_interval` is disabled. /// Default is currently disabled. pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> ClientBuilder { self.config.http2_keep_alive_timeout = Some(timeout); self } /// Sets whether HTTP2 keep-alive should apply while the connection is idle. /// /// If disabled, keep-alive pings are only sent while there are open request/responses streams. /// If enabled, pings are also sent when no streams are active. /// Does nothing if `http2_keep_alive_interval` is disabled. /// Default is `false`. pub fn http2_keep_alive_while_idle(mut self, enabled: bool) -> ClientBuilder { self.config.http2_keep_alive_while_idle = enabled; self } // TCP options /// Set whether sockets have `TCP_NODELAY` enabled. /// /// Default is `true`. pub fn tcp_nodelay(mut self, enabled: bool) -> ClientBuilder { self.config.nodelay = enabled; self } /// Bind to a local IP Address. /// /// # Example /// /// ``` /// use std::net::IpAddr; /// let local_addr = IpAddr::from([12, 4, 1, 8]); /// let client = reqwest::Client::builder() /// .local_address(local_addr) /// .build().unwrap(); /// ``` pub fn local_address(mut self, addr: T) -> ClientBuilder where T: Into>, { self.config.local_address = addr.into(); self } /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration. /// /// If `None`, the option will not be set. pub fn tcp_keepalive(mut self, val: D) -> ClientBuilder where D: Into>, { self.config.tcp_keepalive = val.into(); self } // TLS options /// Add a custom root certificate. /// /// This can be used to connect to a server that has a self-signed /// certificate for example. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn add_root_certificate(mut self, cert: Certificate) -> ClientBuilder { self.config.root_certs.push(cert); self } /// Controls the use of built-in/preloaded certificates during certificate validation. /// /// Defaults to `true` -- built-in system certs will be used. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn tls_built_in_root_certs(mut self, tls_built_in_root_certs: bool) -> ClientBuilder { self.config.tls_built_in_root_certs = tls_built_in_root_certs; self } /// Sets the identity to be used for client certificate authentication. /// /// # Optional /// /// This requires the optional `native-tls` or `rustls-tls(-...)` feature to be /// enabled. #[cfg(any(feature = "native-tls", feature = "__rustls"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))] pub fn identity(mut self, identity: Identity) -> ClientBuilder { self.config.identity = Some(identity); self } /// Controls the use of hostname verification. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before you use this method. If /// hostname verification is not used, any valid certificate for any /// site will be trusted for use from any other. This introduces a /// significant vulnerability to man-in-the-middle attacks. /// /// # Optional /// /// This requires the optional `native-tls` feature to be enabled. #[cfg(feature = "native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] pub fn danger_accept_invalid_hostnames( mut self, accept_invalid_hostname: bool, ) -> ClientBuilder { self.config.hostname_verification = !accept_invalid_hostname; self } /// Controls the use of certificate validation. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before using this method. If /// invalid certificates are trusted, *any* certificate for *any* site /// will be trusted for use. This includes expired certificates. This /// introduces significant vulnerabilities, and should only be used /// as a last resort. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn danger_accept_invalid_certs(mut self, accept_invalid_certs: bool) -> ClientBuilder { self.config.certs_verification = !accept_invalid_certs; self } /// Controls the use of TLS server name indication. /// /// Defaults to `true`. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn tls_sni(mut self, tls_sni: bool) -> ClientBuilder { self.config.tls_sni = tls_sni; self } /// Set the minimum required TLS version for connections. /// /// By default the TLS backend's own default is used. /// /// # Errors /// /// A value of `tls::Version::TLS_1_3` will cause an error with the /// `native-tls`/`default-tls` backend. This does not mean the version /// isn't supported, just that it can't be set as a minimum due to /// technical limitations. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn min_tls_version(mut self, version: tls::Version) -> ClientBuilder { self.config.min_tls_version = Some(version); self } /// Set the maximum allowed TLS version for connections. /// /// By default there's no maximum. /// /// # Errors /// /// A value of `tls::Version::TLS_1_3` will cause an error with the /// `native-tls`/`default-tls` backend. This does not mean the version /// isn't supported, just that it can't be set as a maximum due to /// technical limitations. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn max_tls_version(mut self, version: tls::Version) -> ClientBuilder { self.config.max_tls_version = Some(version); self } /// Force using the native TLS backend. /// /// Since multiple TLS backends can be optionally enabled, this option will /// force the `native-tls` backend to be used for this `Client`. /// /// # Optional /// /// This requires the optional `native-tls` feature to be enabled. #[cfg(feature = "native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] pub fn use_native_tls(mut self) -> ClientBuilder { self.config.tls = TlsBackend::Default; self } /// Force using the Rustls TLS backend. /// /// Since multiple TLS backends can be optionally enabled, this option will /// force the `rustls` backend to be used for this `Client`. /// /// # Optional /// /// This requires the optional `rustls-tls(-...)` feature to be enabled. #[cfg(feature = "__rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))] pub fn use_rustls_tls(mut self) -> ClientBuilder { self.config.tls = TlsBackend::Rustls; self } /// Use a preconfigured TLS backend. /// /// If the passed `Any` argument is not a TLS backend that reqwest /// understands, the `ClientBuilder` will error when calling `build`. /// /// # Advanced /// /// This is an advanced option, and can be somewhat brittle. Usage requires /// keeping the preconfigured TLS argument version in sync with reqwest, /// since version mismatches will result in an "unknown" TLS backend. /// /// If possible, it's preferable to use the methods on `ClientBuilder` /// to configure reqwest's TLS. /// /// # Optional /// /// This requires one of the optional features `native-tls` or /// `rustls-tls(-...)` to be enabled. #[cfg(any(feature = "native-tls", feature = "__rustls",))] #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))] pub fn use_preconfigured_tls(mut self, tls: impl Any) -> ClientBuilder { let mut tls = Some(tls); #[cfg(feature = "native-tls")] { if let Some(conn) = (&mut tls as &mut dyn Any).downcast_mut::>() { let tls = conn.take().expect("is definitely Some"); let tls = crate::tls::TlsBackend::BuiltNativeTls(tls); self.config.tls = tls; return self; } } #[cfg(feature = "__rustls")] { if let Some(conn) = (&mut tls as &mut dyn Any).downcast_mut::>() { let tls = conn.take().expect("is definitely Some"); let tls = crate::tls::TlsBackend::BuiltRustls(tls); self.config.tls = tls; return self; } } // Otherwise, we don't recognize the TLS backend! self.config.tls = crate::tls::TlsBackend::UnknownPreconfigured; self } /// Add TLS information as `TlsInfo` extension to responses. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn tls_info(mut self, tls_info: bool) -> ClientBuilder { self.config.tls_info = tls_info; self } /// Restrict the Client to be used with HTTPS only requests. /// /// Defaults to false. pub fn https_only(mut self, enabled: bool) -> ClientBuilder { self.config.https_only = enabled; self } /// Enables the [hickory-dns](hickory_resolver) async resolver instead of a default threadpool /// using `getaddrinfo`. /// /// If the `hickory-dns` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `hickory-dns` feature to be enabled #[cfg(feature = "hickory-dns")] #[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))] #[deprecated(note = "use `hickory_dns` instead")] pub fn trust_dns(mut self, enable: bool) -> ClientBuilder { self.config.hickory_dns = enable; self } /// Enables the [hickory-dns](hickory_resolver) async resolver instead of a default threadpool /// using `getaddrinfo`. /// /// If the `hickory-dns` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `hickory-dns` feature to be enabled #[cfg(feature = "hickory-dns")] #[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))] pub fn hickory_dns(mut self, enable: bool) -> ClientBuilder { self.config.hickory_dns = enable; self } /// Disables the hickory-dns async resolver. /// /// This method exists even if the optional `hickory-dns` feature is not enabled. /// This can be used to ensure a `Client` doesn't use the hickory-dns async resolver /// even if another dependency were to enable the optional `hickory-dns` feature. #[deprecated(note = "use `no_hickory_dns` instead")] pub fn no_trust_dns(self) -> ClientBuilder { #[cfg(feature = "hickory-dns")] { self.hickory_dns(false) } #[cfg(not(feature = "hickory-dns"))] { self } } /// Disables the hickory-dns async resolver. /// /// This method exists even if the optional `hickory-dns` feature is not enabled. /// This can be used to ensure a `Client` doesn't use the hickory-dns async resolver /// even if another dependency were to enable the optional `hickory-dns` feature. pub fn no_hickory_dns(self) -> ClientBuilder { #[cfg(feature = "hickory-dns")] { self.hickory_dns(false) } #[cfg(not(feature = "hickory-dns"))] { self } } /// Override DNS resolution for specific domains to a particular IP address. /// /// Warning /// /// Since the DNS protocol has no notion of ports, if you wish to send /// traffic to a particular port you must include this port in the URL /// itself, any port in the overridden addr will be ignored and traffic sent /// to the conventional port for the given scheme (e.g. 80 for http). pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder { self.resolve_to_addrs(domain, &[addr]) } /// Override DNS resolution for specific domains to particular IP addresses. /// /// Warning /// /// Since the DNS protocol has no notion of ports, if you wish to send /// traffic to a particular port you must include this port in the URL /// itself, any port in the overridden addresses will be ignored and traffic sent /// to the conventional port for the given scheme (e.g. 80 for http). pub fn resolve_to_addrs(mut self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder { self.config .dns_overrides .insert(domain.to_string(), addrs.to_vec()); self } /// Override the DNS resolver implementation. /// /// Pass an `Arc` wrapping a trait object implementing `Resolve`. /// Overrides for specific names passed to `resolve` and `resolve_to_addrs` will /// still be applied on top of this resolver. pub fn dns_resolver(mut self, resolver: Arc) -> ClientBuilder { self.config.dns_resolver = Some(resolver as _); self } /// Whether to send data on the first flight ("early data") in TLS 1.3 handshakes /// for HTTP/3 connections. /// /// The default is false. #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))] pub fn set_tls_enable_early_data(mut self, enabled: bool) -> ClientBuilder { self.config.tls_enable_early_data = enabled; self } /// Maximum duration of inactivity to accept before timing out the QUIC connection. /// /// Please see docs in [`TransportConfig`] in [`quinn`]. /// /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))] pub fn set_quic_max_idle_timeout(mut self, value: Duration) -> ClientBuilder { self.config.quic_max_idle_timeout = Some(value); self } /// Maximum number of bytes the peer may transmit without acknowledgement on any one stream /// before becoming blocked. /// /// Please see docs in [`TransportConfig`] in [`quinn`]. /// /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))] pub fn set_quic_stream_receive_window(mut self, value: VarInt) -> ClientBuilder { self.config.quic_stream_receive_window = Some(value); self } /// Maximum number of bytes the peer may transmit across all streams of a connection before /// becoming blocked. /// /// Please see docs in [`TransportConfig`] in [`quinn`]. /// /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))] pub fn set_quic_receive_window(mut self, value: VarInt) -> ClientBuilder { self.config.quic_receive_window = Some(value); self } /// Maximum number of bytes to transmit to a peer without acknowledgment /// /// Please see docs in [`TransportConfig`] in [`quinn`]. /// /// [`TransportConfig`]: https://docs.rs/quinn/latest/quinn/struct.TransportConfig.html #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(all(reqwest_unstable, feature = "http3",))))] pub fn set_quic_send_window(mut self, value: u64) -> ClientBuilder { self.config.quic_send_window = Some(value); self } } type HyperClient = hyper::Client; impl Default for Client { fn default() -> Self { Self::new() } } impl Client { /// Constructs a new `Client`. /// /// # Panics /// /// This method panics if a TLS backend cannot be initialized, or the resolver /// cannot load the system configuration. /// /// Use `Client::builder()` if you wish to handle the failure as an `Error` /// instead of panicking. pub fn new() -> Client { ClientBuilder::new().build().expect("Client::new()") } /// Creates a `ClientBuilder` to configure a `Client`. /// /// This is the same as `ClientBuilder::new()`. pub fn builder() -> ClientBuilder { ClientBuilder::new() } /// Convenience method to make a `GET` request to a URL. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn get(&self, url: U) -> RequestBuilder { self.request(Method::GET, url) } /// Convenience method to make a `POST` request to a URL. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn post(&self, url: U) -> RequestBuilder { self.request(Method::POST, url) } /// Convenience method to make a `PUT` request to a URL. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn put(&self, url: U) -> RequestBuilder { self.request(Method::PUT, url) } /// Convenience method to make a `PATCH` request to a URL. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn patch(&self, url: U) -> RequestBuilder { self.request(Method::PATCH, url) } /// Convenience method to make a `DELETE` request to a URL. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn delete(&self, url: U) -> RequestBuilder { self.request(Method::DELETE, url) } /// Convenience method to make a `HEAD` request to a URL. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn head(&self, url: U) -> RequestBuilder { self.request(Method::HEAD, url) } /// Start building a `Request` with the `Method` and `Url`. /// /// Returns a `RequestBuilder`, which will allow setting headers and /// the request body before sending. /// /// # Errors /// /// This method fails whenever the supplied `Url` cannot be parsed. pub fn request(&self, method: Method, url: U) -> RequestBuilder { let req = url.into_url().map(move |url| Request::new(method, url)); RequestBuilder::new(self.clone(), req) } /// Executes a `Request`. /// /// A `Request` can be built manually with `Request::new()` or obtained /// from a RequestBuilder with `RequestBuilder::build()`. /// /// You should prefer to use the `RequestBuilder` and /// `RequestBuilder::send()`. /// /// # Errors /// /// This method fails if there was an error while sending request, /// redirect loop was detected or redirect limit was exhausted. pub fn execute( &self, request: Request, ) -> impl Future> { self.execute_request(request) } pub(super) fn execute_request(&self, req: Request) -> Pending { let (method, url, mut headers, body, timeout, version) = req.pieces(); if url.scheme() != "http" && url.scheme() != "https" { return Pending::new_err(error::url_bad_scheme(url)); } // check if we're in https_only mode and check the scheme of the current URL if self.inner.https_only && url.scheme() != "https" { return Pending::new_err(error::url_bad_scheme(url)); } // insert default headers in the request headers // without overwriting already appended headers. for (key, value) in &self.inner.headers { if let Entry::Vacant(entry) = headers.entry(key) { entry.insert(value.clone()); } } // Add cookies from the cookie store. #[cfg(feature = "cookies")] { if let Some(cookie_store) = self.inner.cookie_store.as_ref() { if headers.get(crate::header::COOKIE).is_none() { add_cookie_header(&mut headers, &**cookie_store, &url); } } } let accept_encoding = self.inner.accepts.as_str(); if let Some(accept_encoding) = accept_encoding { if !headers.contains_key(ACCEPT_ENCODING) && !headers.contains_key(RANGE) { headers.insert(ACCEPT_ENCODING, HeaderValue::from_static(accept_encoding)); } } let uri = match try_uri(&url) { Ok(uri) => uri, _ => return Pending::new_err(error::url_invalid_uri(url)), }; let (reusable, body) = match body { Some(body) => { let (reusable, body) = body.try_reuse(); (Some(reusable), body) } None => (None, Body::empty()), }; self.proxy_auth(&uri, &mut headers); let builder = hyper::Request::builder() .method(method.clone()) .uri(uri) .version(version); let in_flight = match version { #[cfg(feature = "http3")] http::Version::HTTP_3 if self.inner.h3_client.is_some() => { let mut req = builder.body(body).expect("valid request parts"); *req.headers_mut() = headers.clone(); ResponseFuture::H3(self.inner.h3_client.as_ref().unwrap().request(req)) } _ => { let mut req = builder .body(body.into_stream()) .expect("valid request parts"); *req.headers_mut() = headers.clone(); ResponseFuture::Default(self.inner.hyper.request(req)) } }; let timeout = timeout .or(self.inner.request_timeout) .map(tokio::time::sleep) .map(Box::pin); Pending { inner: PendingInner::Request(PendingRequest { method, url, headers, body: reusable, urls: Vec::new(), retry_count: 0, client: self.inner.clone(), in_flight, timeout, }), } } fn proxy_auth(&self, dst: &Uri, headers: &mut HeaderMap) { if !self.inner.proxies_maybe_http_auth { return; } // Only set the header here if the destination scheme is 'http', // since otherwise, the header will be included in the CONNECT tunnel // request instead. if dst.scheme() != Some(&Scheme::HTTP) { return; } if headers.contains_key(PROXY_AUTHORIZATION) { return; } for proxy in self.inner.proxies.iter() { if proxy.is_match(dst) { if let Some(header) = proxy.http_basic_auth(dst) { headers.insert(PROXY_AUTHORIZATION, header); } break; } } } } impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("Client"); self.inner.fmt_fields(&mut builder); builder.finish() } } impl tower_service::Service for Client { type Response = Response; type Error = crate::Error; type Future = Pending; fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { self.execute_request(req) } } impl tower_service::Service for &'_ Client { type Response = Response; type Error = crate::Error; type Future = Pending; fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { self.execute_request(req) } } impl fmt::Debug for ClientBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("ClientBuilder"); self.config.fmt_fields(&mut builder); builder.finish() } } impl Config { fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) { // Instead of deriving Debug, only print fields when their output // would provide relevant or interesting data. #[cfg(feature = "cookies")] { if let Some(_) = self.cookie_store { f.field("cookie_store", &true); } } f.field("accepts", &self.accepts); if !self.proxies.is_empty() { f.field("proxies", &self.proxies); } if !self.redirect_policy.is_default() { f.field("redirect_policy", &self.redirect_policy); } if self.referer { f.field("referer", &true); } f.field("default_headers", &self.headers); if self.http1_title_case_headers { f.field("http1_title_case_headers", &true); } if self.http1_allow_obsolete_multiline_headers_in_responses { f.field("http1_allow_obsolete_multiline_headers_in_responses", &true); } if self.http1_ignore_invalid_headers_in_responses { f.field("http1_ignore_invalid_headers_in_responses", &true); } if self.http1_allow_spaces_after_header_name_in_responses { f.field("http1_allow_spaces_after_header_name_in_responses", &true); } if matches!(self.http_version_pref, HttpVersionPref::Http1) { f.field("http1_only", &true); } if matches!(self.http_version_pref, HttpVersionPref::Http2) { f.field("http2_prior_knowledge", &true); } if let Some(ref d) = self.connect_timeout { f.field("connect_timeout", d); } if let Some(ref d) = self.timeout { f.field("timeout", d); } if let Some(ref v) = self.local_address { f.field("local_address", v); } if self.nodelay { f.field("tcp_nodelay", &true); } #[cfg(feature = "native-tls")] { if !self.hostname_verification { f.field("danger_accept_invalid_hostnames", &true); } } #[cfg(feature = "__tls")] { if !self.certs_verification { f.field("danger_accept_invalid_certs", &true); } if let Some(ref min_tls_version) = self.min_tls_version { f.field("min_tls_version", min_tls_version); } if let Some(ref max_tls_version) = self.max_tls_version { f.field("max_tls_version", max_tls_version); } f.field("tls_sni", &self.tls_sni); f.field("tls_info", &self.tls_info); } #[cfg(all(feature = "native-tls-crate", feature = "__rustls"))] { f.field("tls_backend", &self.tls); } if !self.dns_overrides.is_empty() { f.field("dns_overrides", &self.dns_overrides); } #[cfg(feature = "http3")] { if self.tls_enable_early_data { f.field("tls_enable_early_data", &true); } } } } struct ClientRef { accepts: Accepts, #[cfg(feature = "cookies")] cookie_store: Option>, headers: HeaderMap, hyper: HyperClient, #[cfg(feature = "http3")] h3_client: Option, redirect_policy: redirect::Policy, referer: bool, request_timeout: Option, proxies: Arc>, proxies_maybe_http_auth: bool, https_only: bool, } impl ClientRef { fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) { // Instead of deriving Debug, only print fields when their output // would provide relevant or interesting data. #[cfg(feature = "cookies")] { if let Some(_) = self.cookie_store { f.field("cookie_store", &true); } } f.field("accepts", &self.accepts); if !self.proxies.is_empty() { f.field("proxies", &self.proxies); } if !self.redirect_policy.is_default() { f.field("redirect_policy", &self.redirect_policy); } if self.referer { f.field("referer", &true); } f.field("default_headers", &self.headers); if let Some(ref d) = self.request_timeout { f.field("timeout", d); } } } pin_project! { pub struct Pending { #[pin] inner: PendingInner, } } enum PendingInner { Request(PendingRequest), Error(Option), } pin_project! { struct PendingRequest { method: Method, url: Url, headers: HeaderMap, body: Option>, urls: Vec, retry_count: usize, client: Arc, #[pin] in_flight: ResponseFuture, #[pin] timeout: Option>>, } } enum ResponseFuture { Default(HyperResponseFuture), #[cfg(feature = "http3")] H3(H3ResponseFuture), } impl PendingRequest { fn in_flight(self: Pin<&mut Self>) -> Pin<&mut ResponseFuture> { self.project().in_flight } fn timeout(self: Pin<&mut Self>) -> Pin<&mut Option>>> { self.project().timeout } fn urls(self: Pin<&mut Self>) -> &mut Vec { self.project().urls } fn headers(self: Pin<&mut Self>) -> &mut HeaderMap { self.project().headers } fn retry_error(mut self: Pin<&mut Self>, err: &(dyn std::error::Error + 'static)) -> bool { if !is_retryable_error(err) { return false; } trace!("can retry {err:?}"); let body = match self.body { Some(Some(ref body)) => Body::reusable(body.clone()), Some(None) => { debug!("error was retryable, but body not reusable"); return false; } None => Body::empty(), }; if self.retry_count >= 2 { trace!("retry count too high"); return false; } self.retry_count += 1; // If it parsed once, it should parse again let uri = try_uri(&self.url).expect("URL was already validated as URI"); *self.as_mut().in_flight().get_mut() = match *self.as_mut().in_flight().as_ref() { #[cfg(feature = "http3")] ResponseFuture::H3(_) => { let mut req = hyper::Request::builder() .method(self.method.clone()) .uri(uri) .body(body) .expect("valid request parts"); *req.headers_mut() = self.headers.clone(); ResponseFuture::H3( self.client .h3_client .as_ref() .expect("H3 client must exists, otherwise we can't have a h3 request here") .request(req), ) } _ => { let mut req = hyper::Request::builder() .method(self.method.clone()) .uri(uri) .body(body.into_stream()) .expect("valid request parts"); *req.headers_mut() = self.headers.clone(); ResponseFuture::Default(self.client.hyper.request(req)) } }; true } } fn is_retryable_error(err: &(dyn std::error::Error + 'static)) -> bool { #[cfg(feature = "http3")] if let Some(cause) = err.source() { if let Some(err) = cause.downcast_ref::() { debug!("determining if HTTP/3 error {err} can be retried"); // TODO: Does h3 provide an API for checking the error? return err.to_string().as_str() == "timeout"; } } if let Some(cause) = err.source() { if let Some(err) = cause.downcast_ref::() { // They sent us a graceful shutdown, try with a new connection! if err.is_go_away() && err.is_remote() && err.reason() == Some(h2::Reason::NO_ERROR) { return true; } // REFUSED_STREAM was sent from the server, which is safe to retry. // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2 if err.is_reset() && err.is_remote() && err.reason() == Some(h2::Reason::REFUSED_STREAM) { return true; } } } false } impl Pending { pub(super) fn new_err(err: crate::Error) -> Pending { Pending { inner: PendingInner::Error(Some(err)), } } fn inner(self: Pin<&mut Self>) -> Pin<&mut PendingInner> { self.project().inner } } impl Future for Pending { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let inner = self.inner(); match inner.get_mut() { PendingInner::Request(ref mut req) => Pin::new(req).poll(cx), PendingInner::Error(ref mut err) => Poll::Ready(Err(err .take() .expect("Pending error polled more than once"))), } } } impl Future for PendingRequest { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if let Some(delay) = self.as_mut().timeout().as_mut().as_pin_mut() { if let Poll::Ready(()) = delay.poll(cx) { return Poll::Ready(Err( crate::error::request(crate::error::TimedOut).with_url(self.url.clone()) )); } } loop { let res = match self.as_mut().in_flight().get_mut() { ResponseFuture::Default(r) => match Pin::new(r).poll(cx) { Poll::Ready(Err(e)) => { if self.as_mut().retry_error(&e) { continue; } return Poll::Ready(Err( crate::error::request(e).with_url(self.url.clone()) )); } Poll::Ready(Ok(res)) => res, Poll::Pending => return Poll::Pending, }, #[cfg(feature = "http3")] ResponseFuture::H3(r) => match Pin::new(r).poll(cx) { Poll::Ready(Err(e)) => { if self.as_mut().retry_error(&e) { continue; } return Poll::Ready(Err( crate::error::request(e).with_url(self.url.clone()) )); } Poll::Ready(Ok(res)) => res, Poll::Pending => return Poll::Pending, }, }; #[cfg(feature = "cookies")] { if let Some(ref cookie_store) = self.client.cookie_store { let mut cookies = cookie::extract_response_cookie_headers(&res.headers()).peekable(); if cookies.peek().is_some() { cookie_store.set_cookies(&mut cookies, &self.url); } } } let should_redirect = match res.status() { StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => { self.body = None; for header in &[ TRANSFER_ENCODING, CONTENT_ENCODING, CONTENT_TYPE, CONTENT_LENGTH, ] { self.headers.remove(header); } match self.method { Method::GET | Method::HEAD => {} _ => { self.method = Method::GET; } } true } StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => { match self.body { Some(Some(_)) | None => true, Some(None) => false, } } _ => false, }; if should_redirect { let loc = res.headers().get(LOCATION).and_then(|val| { let loc = (|| -> Option { // Some sites may send a utf-8 Location header, // even though we're supposed to treat those bytes // as opaque, we'll check specifically for utf8. self.url.join(str::from_utf8(val.as_bytes()).ok()?).ok() })(); // Check that the `url` is also a valid `http::Uri`. // // If not, just log it and skip the redirect. let loc = loc.and_then(|url| { if try_uri(&url).is_ok() { Some(url) } else { None } }); if loc.is_none() { debug!("Location header had invalid URI: {val:?}"); } loc }); if let Some(loc) = loc { if self.client.referer { if let Some(referer) = make_referer(&loc, &self.url) { self.headers.insert(REFERER, referer); } } let url = self.url.clone(); self.as_mut().urls().push(url); let action = self .client .redirect_policy .check(res.status(), &loc, &self.urls); match action { redirect::ActionKind::Follow => { debug!("redirecting '{}' to '{}'", self.url, loc); if loc.scheme() != "http" && loc.scheme() != "https" { return Poll::Ready(Err(error::url_bad_scheme(loc))); } if self.client.https_only && loc.scheme() != "https" { return Poll::Ready(Err(error::redirect( error::url_bad_scheme(loc.clone()), loc, ))); } self.url = loc; let mut headers = std::mem::replace(self.as_mut().headers(), HeaderMap::new()); remove_sensitive_headers(&mut headers, &self.url, &self.urls); let uri = try_uri(&self.url)?; let body = match self.body { Some(Some(ref body)) => Body::reusable(body.clone()), _ => Body::empty(), }; // Add cookies from the cookie store. #[cfg(feature = "cookies")] { if let Some(ref cookie_store) = self.client.cookie_store { add_cookie_header(&mut headers, &**cookie_store, &self.url); } } *self.as_mut().in_flight().get_mut() = match *self.as_mut().in_flight().as_ref() { #[cfg(feature = "http3")] ResponseFuture::H3(_) => { let mut req = hyper::Request::builder() .method(self.method.clone()) .uri(uri.clone()) .body(body) .expect("valid request parts"); *req.headers_mut() = headers.clone(); std::mem::swap(self.as_mut().headers(), &mut headers); ResponseFuture::H3(self.client.h3_client .as_ref() .expect("H3 client must exists, otherwise we can't have a h3 request here") .request(req)) } _ => { let mut req = hyper::Request::builder() .method(self.method.clone()) .uri(uri.clone()) .body(body.into_stream()) .expect("valid request parts"); *req.headers_mut() = headers.clone(); std::mem::swap(self.as_mut().headers(), &mut headers); ResponseFuture::Default(self.client.hyper.request(req)) } }; continue; } redirect::ActionKind::Stop => { debug!("redirect policy disallowed redirection to '{loc}'"); } redirect::ActionKind::Error(err) => { return Poll::Ready(Err(crate::error::redirect(err, self.url.clone()))); } } } } let res = Response::new( res, self.url.clone(), self.client.accepts, self.timeout.take(), ); return Poll::Ready(Ok(res)); } } } impl fmt::Debug for Pending { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.inner { PendingInner::Request(ref req) => f .debug_struct("Pending") .field("method", &req.method) .field("url", &req.url) .finish(), PendingInner::Error(ref err) => f.debug_struct("Pending").field("error", err).finish(), } } } fn make_referer(next: &Url, previous: &Url) -> Option { if next.scheme() == "http" && previous.scheme() == "https" { return None; } let mut referer = previous.clone(); let _ = referer.set_username(""); let _ = referer.set_password(None); referer.set_fragment(None); referer.as_str().parse().ok() } #[cfg(feature = "cookies")] fn add_cookie_header(headers: &mut HeaderMap, cookie_store: &dyn cookie::CookieStore, url: &Url) { if let Some(header) = cookie_store.cookies(url) { headers.insert(crate::header::COOKIE, header); } } #[cfg(test)] mod tests { #[tokio::test] async fn execute_request_rejects_invalid_urls() { let url_str = "hxxps://www.rust-lang.org/"; let url = url::Url::parse(url_str).unwrap(); let result = crate::get(url.clone()).await; assert!(result.is_err()); let err = result.err().unwrap(); assert!(err.is_builder()); assert_eq!(url_str, err.url().unwrap().as_str()); } /// https://github.com/seanmonstar/reqwest/issues/668 #[tokio::test] async fn execute_request_rejects_invalid_hostname() { let url_str = "https://{{hostname}}/"; let url = url::Url::parse(url_str).unwrap(); let result = crate::get(url.clone()).await; assert!(result.is_err()); let err = result.err().unwrap(); assert!(err.is_builder()); assert_eq!(url_str, err.url().unwrap().as_str()); } } reqwest-0.11.27/src/async_impl/decoder.rs000064400000000000000000000315171046102023000163750ustar 00000000000000use std::fmt; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; #[cfg(feature = "gzip")] use async_compression::tokio::bufread::GzipDecoder; #[cfg(feature = "brotli")] use async_compression::tokio::bufread::BrotliDecoder; #[cfg(feature = "deflate")] use async_compression::tokio::bufread::ZlibDecoder; use bytes::Bytes; use futures_core::Stream; use futures_util::stream::Peekable; use http::HeaderMap; use hyper::body::HttpBody; #[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))] use tokio_util::codec::{BytesCodec, FramedRead}; #[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))] use tokio_util::io::StreamReader; use super::super::Body; use crate::error; #[derive(Clone, Copy, Debug)] pub(super) struct Accepts { #[cfg(feature = "gzip")] pub(super) gzip: bool, #[cfg(feature = "brotli")] pub(super) brotli: bool, #[cfg(feature = "deflate")] pub(super) deflate: bool, } /// A response decompressor over a non-blocking stream of chunks. /// /// The inner decoder may be constructed asynchronously. pub(crate) struct Decoder { inner: Inner, } type PeekableIoStream = Peekable; #[cfg(any(feature = "gzip", feature = "brotli", feature = "deflate"))] type PeekableIoStreamReader = StreamReader; enum Inner { /// A `PlainText` decoder just returns the response content as is. PlainText(super::body::ImplStream), /// A `Gzip` decoder will uncompress the gzipped response content before returning it. #[cfg(feature = "gzip")] Gzip(Pin, BytesCodec>>>), /// A `Brotli` decoder will uncompress the brotlied response content before returning it. #[cfg(feature = "brotli")] Brotli(Pin, BytesCodec>>>), /// A `Deflate` decoder will uncompress the deflated response content before returning it. #[cfg(feature = "deflate")] Deflate(Pin, BytesCodec>>>), /// A decoder that doesn't have a value yet. #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))] Pending(Pin>), } /// A future attempt to poll the response body for EOF so we know whether to use gzip or not. struct Pending(PeekableIoStream, DecoderType); struct IoStream(super::body::ImplStream); enum DecoderType { #[cfg(feature = "gzip")] Gzip, #[cfg(feature = "brotli")] Brotli, #[cfg(feature = "deflate")] Deflate, } impl fmt::Debug for Decoder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Decoder").finish() } } impl Decoder { #[cfg(feature = "blocking")] pub(crate) fn empty() -> Decoder { Decoder { inner: Inner::PlainText(Body::empty().into_stream()), } } /// A plain text decoder. /// /// This decoder will emit the underlying chunks as-is. fn plain_text(body: Body) -> Decoder { Decoder { inner: Inner::PlainText(body.into_stream()), } } /// A gzip decoder. /// /// This decoder will buffer and decompress chunks that are gzipped. #[cfg(feature = "gzip")] fn gzip(body: Body) -> Decoder { use futures_util::StreamExt; Decoder { inner: Inner::Pending(Box::pin(Pending( IoStream(body.into_stream()).peekable(), DecoderType::Gzip, ))), } } /// A brotli decoder. /// /// This decoder will buffer and decompress chunks that are brotlied. #[cfg(feature = "brotli")] fn brotli(body: Body) -> Decoder { use futures_util::StreamExt; Decoder { inner: Inner::Pending(Box::pin(Pending( IoStream(body.into_stream()).peekable(), DecoderType::Brotli, ))), } } /// A deflate decoder. /// /// This decoder will buffer and decompress chunks that are deflated. #[cfg(feature = "deflate")] fn deflate(body: Body) -> Decoder { use futures_util::StreamExt; Decoder { inner: Inner::Pending(Box::pin(Pending( IoStream(body.into_stream()).peekable(), DecoderType::Deflate, ))), } } #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))] fn detect_encoding(headers: &mut HeaderMap, encoding_str: &str) -> bool { use http::header::{CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING}; use log::warn; let mut is_content_encoded = { headers .get_all(CONTENT_ENCODING) .iter() .any(|enc| enc == encoding_str) || headers .get_all(TRANSFER_ENCODING) .iter() .any(|enc| enc == encoding_str) }; if is_content_encoded { if let Some(content_length) = headers.get(CONTENT_LENGTH) { if content_length == "0" { warn!("{encoding_str} response with content-length of 0"); is_content_encoded = false; } } } if is_content_encoded { headers.remove(CONTENT_ENCODING); headers.remove(CONTENT_LENGTH); } is_content_encoded } /// Constructs a Decoder from a hyper request. /// /// A decoder is just a wrapper around the hyper request that knows /// how to decode the content body of the request. /// /// Uses the correct variant by inspecting the Content-Encoding header. pub(super) fn detect(_headers: &mut HeaderMap, body: Body, _accepts: Accepts) -> Decoder { #[cfg(feature = "gzip")] { if _accepts.gzip && Decoder::detect_encoding(_headers, "gzip") { return Decoder::gzip(body); } } #[cfg(feature = "brotli")] { if _accepts.brotli && Decoder::detect_encoding(_headers, "br") { return Decoder::brotli(body); } } #[cfg(feature = "deflate")] { if _accepts.deflate && Decoder::detect_encoding(_headers, "deflate") { return Decoder::deflate(body); } } Decoder::plain_text(body) } } impl Stream for Decoder { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { // Do a read or poll for a pending decoder value. match self.inner { #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))] Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) { Poll::Ready(Ok(inner)) => { self.inner = inner; self.poll_next(cx) } Poll::Ready(Err(e)) => Poll::Ready(Some(Err(crate::error::decode_io(e)))), Poll::Pending => Poll::Pending, }, Inner::PlainText(ref mut body) => Pin::new(body).poll_next(cx), #[cfg(feature = "gzip")] Inner::Gzip(ref mut decoder) => { match futures_core::ready!(Pin::new(decoder).poll_next(cx)) { Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))), Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))), None => Poll::Ready(None), } } #[cfg(feature = "brotli")] Inner::Brotli(ref mut decoder) => { match futures_core::ready!(Pin::new(decoder).poll_next(cx)) { Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))), Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))), None => Poll::Ready(None), } } #[cfg(feature = "deflate")] Inner::Deflate(ref mut decoder) => { match futures_core::ready!(Pin::new(decoder).poll_next(cx)) { Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes.freeze()))), Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))), None => Poll::Ready(None), } } } } } impl HttpBody for Decoder { type Data = Bytes; type Error = crate::Error; fn poll_data( self: Pin<&mut Self>, cx: &mut Context, ) -> Poll>> { self.poll_next(cx) } fn poll_trailers( self: Pin<&mut Self>, _cx: &mut Context, ) -> Poll, Self::Error>> { Poll::Ready(Ok(None)) } fn size_hint(&self) -> http_body::SizeHint { match self.inner { Inner::PlainText(ref body) => HttpBody::size_hint(body), // the rest are "unknown", so default #[cfg(any(feature = "brotli", feature = "gzip", feature = "deflate"))] _ => http_body::SizeHint::default(), } } } impl Future for Pending { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { use futures_util::StreamExt; match futures_core::ready!(Pin::new(&mut self.0).poll_peek(cx)) { Some(Ok(_)) => { // fallthrough } Some(Err(_e)) => { // error was just a ref, so we need to really poll to move it return Poll::Ready(Err(futures_core::ready!( Pin::new(&mut self.0).poll_next(cx) ) .expect("just peeked Some") .unwrap_err())); } None => return Poll::Ready(Ok(Inner::PlainText(Body::empty().into_stream()))), }; let _body = std::mem::replace( &mut self.0, IoStream(Body::empty().into_stream()).peekable(), ); match self.1 { #[cfg(feature = "brotli")] DecoderType::Brotli => Poll::Ready(Ok(Inner::Brotli(Box::pin(FramedRead::new( BrotliDecoder::new(StreamReader::new(_body)), BytesCodec::new(), ))))), #[cfg(feature = "gzip")] DecoderType::Gzip => Poll::Ready(Ok(Inner::Gzip(Box::pin(FramedRead::new( GzipDecoder::new(StreamReader::new(_body)), BytesCodec::new(), ))))), #[cfg(feature = "deflate")] DecoderType::Deflate => Poll::Ready(Ok(Inner::Deflate(Box::pin(FramedRead::new( ZlibDecoder::new(StreamReader::new(_body)), BytesCodec::new(), ))))), } } } impl Stream for IoStream { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { match futures_core::ready!(Pin::new(&mut self.0).poll_next(cx)) { Some(Ok(chunk)) => Poll::Ready(Some(Ok(chunk))), Some(Err(err)) => Poll::Ready(Some(Err(err.into_io()))), None => Poll::Ready(None), } } } // ===== impl Accepts ===== impl Accepts { pub(super) fn none() -> Self { Accepts { #[cfg(feature = "gzip")] gzip: false, #[cfg(feature = "brotli")] brotli: false, #[cfg(feature = "deflate")] deflate: false, } } pub(super) fn as_str(&self) -> Option<&'static str> { match (self.is_gzip(), self.is_brotli(), self.is_deflate()) { (true, true, true) => Some("gzip, br, deflate"), (true, true, false) => Some("gzip, br"), (true, false, true) => Some("gzip, deflate"), (false, true, true) => Some("br, deflate"), (true, false, false) => Some("gzip"), (false, true, false) => Some("br"), (false, false, true) => Some("deflate"), (false, false, false) => None, } } fn is_gzip(&self) -> bool { #[cfg(feature = "gzip")] { self.gzip } #[cfg(not(feature = "gzip"))] { false } } fn is_brotli(&self) -> bool { #[cfg(feature = "brotli")] { self.brotli } #[cfg(not(feature = "brotli"))] { false } } fn is_deflate(&self) -> bool { #[cfg(feature = "deflate")] { self.deflate } #[cfg(not(feature = "deflate"))] { false } } } impl Default for Accepts { fn default() -> Accepts { Accepts { #[cfg(feature = "gzip")] gzip: true, #[cfg(feature = "brotli")] brotli: true, #[cfg(feature = "deflate")] deflate: true, } } } reqwest-0.11.27/src/async_impl/h3_client/connect.rs000064400000000000000000000052401046102023000202630ustar 00000000000000use crate::async_impl::h3_client::dns::resolve; use crate::dns::DynResolver; use crate::error::BoxError; use bytes::Bytes; use h3::client::SendRequest; use h3_quinn::{Connection, OpenStreams}; use http::Uri; use hyper::client::connect::dns::Name; use quinn::{ClientConfig, Endpoint, TransportConfig}; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::sync::Arc; type H3Connection = ( h3::client::Connection, SendRequest, ); #[derive(Clone)] pub(crate) struct H3Connector { resolver: DynResolver, endpoint: Endpoint, } impl H3Connector { pub fn new( resolver: DynResolver, tls: rustls::ClientConfig, local_addr: Option, transport_config: TransportConfig, ) -> Result { let mut config = ClientConfig::new(Arc::new(tls)); // FIXME: Replace this when there is a setter. config.transport_config(Arc::new(transport_config)); let socket_addr = match local_addr { Some(ip) => SocketAddr::new(ip, 0), None => "[::]:0".parse::().unwrap(), }; let mut endpoint = Endpoint::client(socket_addr)?; endpoint.set_default_client_config(config); Ok(Self { resolver, endpoint }) } pub async fn connect(&mut self, dest: Uri) -> Result { let host = dest.host().ok_or("destination must have a host")?; let port = dest.port_u16().unwrap_or(443); let addrs = if let Some(addr) = IpAddr::from_str(host).ok() { // If the host is already an IP address, skip resolving. vec![SocketAddr::new(addr, port)] } else { let addrs = resolve(&mut self.resolver, Name::from_str(host)?).await?; let addrs = addrs.map(|mut addr| { addr.set_port(port); addr }); addrs.collect() }; self.remote_connect(addrs, host).await } async fn remote_connect( &mut self, addrs: Vec, server_name: &str, ) -> Result { let mut err = None; for addr in addrs { match self.endpoint.connect(addr, server_name)?.await { Ok(new_conn) => { let quinn_conn = Connection::new(new_conn); return Ok(h3::client::new(quinn_conn).await?); } Err(e) => err = Some(e), } } match err { Some(e) => Err(Box::new(e) as BoxError), None => Err("failed to establish connection for HTTP/3 request".into()), } } } reqwest-0.11.27/src/async_impl/h3_client/dns.rs000064400000000000000000000023731046102023000174220ustar 00000000000000use core::task; use hyper::client::connect::dns::Name; use std::future::Future; use std::net::SocketAddr; use std::task::Poll; use tower_service::Service; // Trait from hyper to implement DNS resolution for HTTP/3 client. pub trait Resolve { type Addrs: Iterator; type Error: Into>; type Future: Future>; fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; fn resolve(&mut self, name: Name) -> Self::Future; } impl Resolve for S where S: Service, S::Response: Iterator, S::Error: Into>, { type Addrs = S::Response; type Error = S::Error; type Future = S::Future; fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { Service::poll_ready(self, cx) } fn resolve(&mut self, name: Name) -> Self::Future { Service::call(self, name) } } pub(super) async fn resolve(resolver: &mut R, name: Name) -> Result where R: Resolve, { futures_util::future::poll_fn(|cx| resolver.poll_ready(cx)).await?; resolver.resolve(name).await } reqwest-0.11.27/src/async_impl/h3_client/mod.rs000064400000000000000000000047161046102023000174200ustar 00000000000000#![cfg(feature = "http3")] pub(crate) mod connect; pub(crate) mod dns; mod pool; use crate::async_impl::h3_client::pool::{Key, Pool, PoolClient}; use crate::error::{BoxError, Error, Kind}; use crate::{error, Body}; use connect::H3Connector; use futures_util::future; use http::{Request, Response}; use hyper::Body as HyperBody; use log::trace; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; #[derive(Clone)] pub(crate) struct H3Client { pool: Pool, connector: H3Connector, } impl H3Client { pub fn new(connector: H3Connector, pool_timeout: Option) -> Self { H3Client { pool: Pool::new(pool_timeout), connector, } } async fn get_pooled_client(&mut self, key: Key) -> Result { if let Some(client) = self.pool.try_pool(&key) { trace!("getting client from pool with key {key:?}"); return Ok(client); } trace!("did not find connection {key:?} in pool so connecting..."); let dest = pool::domain_as_uri(key.clone()); self.pool.connecting(key.clone())?; let (driver, tx) = self.connector.connect(dest).await?; Ok(self.pool.new_connection(key, driver, tx)) } async fn send_request( mut self, key: Key, req: Request, ) -> Result, Error> { let mut pooled = match self.get_pooled_client(key).await { Ok(client) => client, Err(e) => return Err(error::request(e)), }; pooled .send_request(req) .await .map_err(|e| Error::new(Kind::Request, Some(e))) } pub fn request(&self, mut req: Request) -> H3ResponseFuture { let pool_key = match pool::extract_domain(req.uri_mut()) { Ok(s) => s, Err(e) => { return H3ResponseFuture { inner: Box::pin(future::err(e)), } } }; H3ResponseFuture { inner: Box::pin(self.clone().send_request(pool_key, req)), } } } pub(crate) struct H3ResponseFuture { inner: Pin, Error>> + Send>>, } impl Future for H3ResponseFuture { type Output = Result, Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.inner.as_mut().poll(cx) } } reqwest-0.11.27/src/async_impl/h3_client/pool.rs000064400000000000000000000130031046102023000175770ustar 00000000000000use bytes::Bytes; use std::collections::{HashMap, HashSet}; use std::sync::mpsc::{Receiver, TryRecvError}; use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::time::Instant; use crate::error::{BoxError, Error, Kind}; use crate::Body; use bytes::Buf; use futures_util::future; use h3::client::SendRequest; use h3_quinn::{Connection, OpenStreams}; use http::uri::{Authority, Scheme}; use http::{Request, Response, Uri}; use hyper::Body as HyperBody; use log::trace; pub(super) type Key = (Scheme, Authority); #[derive(Clone)] pub struct Pool { inner: Arc>, } impl Pool { pub fn new(timeout: Option) -> Self { Self { inner: Arc::new(Mutex::new(PoolInner { connecting: HashSet::new(), idle_conns: HashMap::new(), timeout, })), } } pub fn connecting(&self, key: Key) -> Result<(), BoxError> { let mut inner = self.inner.lock().unwrap(); if !inner.connecting.insert(key.clone()) { return Err(format!("HTTP/3 connecting already in progress for {key:?}").into()); } return Ok(()); } pub fn try_pool(&self, key: &Key) -> Option { let mut inner = self.inner.lock().unwrap(); let timeout = inner.timeout; if let Some(conn) = inner.idle_conns.get(&key) { // We check first if the connection still valid // and if not, we remove it from the pool. if conn.is_invalid() { trace!("pooled HTTP/3 connection is invalid so removing it..."); inner.idle_conns.remove(&key); return None; } if let Some(duration) = timeout { if Instant::now().saturating_duration_since(conn.idle_timeout) > duration { trace!("pooled connection expired"); return None; } } } inner .idle_conns .get_mut(&key) .and_then(|conn| Some(conn.pool())) } pub fn new_connection( &mut self, key: Key, mut driver: h3::client::Connection, tx: SendRequest, ) -> PoolClient { let (close_tx, close_rx) = std::sync::mpsc::channel(); tokio::spawn(async move { if let Err(e) = future::poll_fn(|cx| driver.poll_close(cx)).await { trace!("poll_close returned error {e:?}"); close_tx.send(e).ok(); } }); let mut inner = self.inner.lock().unwrap(); let client = PoolClient::new(tx); let conn = PoolConnection::new(client.clone(), close_rx); inner.insert(key.clone(), conn); // We clean up "connecting" here so we don't have to acquire the lock again. let existed = inner.connecting.remove(&key); debug_assert!(existed, "key not in connecting set"); client } } struct PoolInner { connecting: HashSet, idle_conns: HashMap, timeout: Option, } impl PoolInner { fn insert(&mut self, key: Key, conn: PoolConnection) { if self.idle_conns.contains_key(&key) { trace!("connection already exists for key {key:?}"); } self.idle_conns.insert(key, conn); } } #[derive(Clone)] pub struct PoolClient { inner: SendRequest, } impl PoolClient { pub fn new(tx: SendRequest) -> Self { Self { inner: tx } } pub async fn send_request( &mut self, req: Request, ) -> Result, BoxError> { let (head, req_body) = req.into_parts(); let req = Request::from_parts(head, ()); let mut stream = self.inner.send_request(req).await?; match req_body.as_bytes() { Some(b) if !b.is_empty() => { stream.send_data(Bytes::copy_from_slice(b)).await?; } _ => {} } stream.finish().await?; let resp = stream.recv_response().await?; let mut resp_body = Vec::new(); while let Some(chunk) = stream.recv_data().await? { resp_body.extend(chunk.chunk()) } Ok(resp.map(|_| HyperBody::from(resp_body))) } } pub struct PoolConnection { // This receives errors from polling h3 driver. close_rx: Receiver, client: PoolClient, idle_timeout: Instant, } impl PoolConnection { pub fn new(client: PoolClient, close_rx: Receiver) -> Self { Self { close_rx, client, idle_timeout: Instant::now(), } } pub fn pool(&mut self) -> PoolClient { self.idle_timeout = Instant::now(); self.client.clone() } pub fn is_invalid(&self) -> bool { match self.close_rx.try_recv() { Err(TryRecvError::Empty) => false, Err(TryRecvError::Disconnected) => true, Ok(_) => true, } } } pub(crate) fn extract_domain(uri: &mut Uri) -> Result { let uri_clone = uri.clone(); match (uri_clone.scheme(), uri_clone.authority()) { (Some(scheme), Some(auth)) => Ok((scheme.clone(), auth.clone())), _ => Err(Error::new(Kind::Request, None::)), } } pub(crate) fn domain_as_uri((scheme, auth): Key) -> Uri { http::uri::Builder::new() .scheme(scheme) .authority(auth) .path_and_query("/") .build() .expect("domain is valid Uri") } reqwest-0.11.27/src/async_impl/mod.rs000064400000000000000000000006521046102023000155430ustar 00000000000000pub use self::body::Body; pub use self::client::{Client, ClientBuilder}; pub use self::request::{Request, RequestBuilder}; pub use self::response::Response; pub use self::upgrade::Upgraded; #[cfg(feature = "blocking")] pub(crate) use self::decoder::Decoder; pub mod body; pub mod client; pub mod decoder; pub mod h3_client; #[cfg(feature = "multipart")] pub mod multipart; pub(crate) mod request; mod response; mod upgrade; reqwest-0.11.27/src/async_impl/multipart.rs000064400000000000000000000511231046102023000170040ustar 00000000000000//! multipart/form-data use std::borrow::Cow; use std::fmt; use std::pin::Pin; use bytes::Bytes; use mime_guess::Mime; use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; use futures_core::Stream; use futures_util::{future, stream, StreamExt}; use super::Body; use crate::header::HeaderMap; /// An async multipart/form-data request. pub struct Form { inner: FormParts, } /// A field in a multipart form. pub struct Part { meta: PartMetadata, value: Body, body_length: Option, } pub(crate) struct FormParts

{ pub(crate) boundary: String, pub(crate) computed_headers: Vec>, pub(crate) fields: Vec<(Cow<'static, str>, P)>, pub(crate) percent_encoding: PercentEncoding, } pub(crate) struct PartMetadata { mime: Option, file_name: Option>, pub(crate) headers: HeaderMap, } pub(crate) trait PartProps { fn value_len(&self) -> Option; fn metadata(&self) -> &PartMetadata; } // ===== impl Form ===== impl Default for Form { fn default() -> Self { Self::new() } } impl Form { /// Creates a new async Form without any content. pub fn new() -> Form { Form { inner: FormParts::new(), } } /// Get the boundary that this form will use. #[inline] pub fn boundary(&self) -> &str { self.inner.boundary() } /// Add a data field with supplied name and value. /// /// # Examples /// /// ``` /// let form = reqwest::multipart::Form::new() /// .text("username", "seanmonstar") /// .text("password", "secret"); /// ``` pub fn text(self, name: T, value: U) -> Form where T: Into>, U: Into>, { self.part(name, Part::text(value)) } /// Adds a customized Part. pub fn part(self, name: T, part: Part) -> Form where T: Into>, { self.with_inner(move |inner| inner.part(name, part)) } /// Configure this `Form` to percent-encode using the `path-segment` rules. pub fn percent_encode_path_segment(self) -> Form { self.with_inner(|inner| inner.percent_encode_path_segment()) } /// Configure this `Form` to percent-encode using the `attr-char` rules. pub fn percent_encode_attr_chars(self) -> Form { self.with_inner(|inner| inner.percent_encode_attr_chars()) } /// Configure this `Form` to skip percent-encoding pub fn percent_encode_noop(self) -> Form { self.with_inner(|inner| inner.percent_encode_noop()) } /// Consume this instance and transform into an instance of Body for use in a request. pub(crate) fn stream(mut self) -> Body { if self.inner.fields.is_empty() { return Body::empty(); } // create initial part to init reduce chain let (name, part) = self.inner.fields.remove(0); let start = Box::pin(self.part_stream(name, part)) as Pin> + Send + Sync>>; let fields = self.inner.take_fields(); // for each field, chain an additional stream let stream = fields.into_iter().fold(start, |memo, (name, part)| { let part_stream = self.part_stream(name, part); Box::pin(memo.chain(part_stream)) as Pin> + Send + Sync>> }); // append special ending boundary let last = stream::once(future::ready(Ok( format!("--{}--\r\n", self.boundary()).into() ))); Body::stream(stream.chain(last)) } /// Generate a hyper::Body stream for a single Part instance of a Form request. pub(crate) fn part_stream( &mut self, name: T, part: Part, ) -> impl Stream> where T: Into>, { // start with boundary let boundary = stream::once(future::ready(Ok( format!("--{}\r\n", self.boundary()).into() ))); // append headers let header = stream::once(future::ready(Ok({ let mut h = self .inner .percent_encoding .encode_headers(&name.into(), &part.meta); h.extend_from_slice(b"\r\n\r\n"); h.into() }))); // then append form data followed by terminating CRLF boundary .chain(header) .chain(part.value.into_stream()) .chain(stream::once(future::ready(Ok("\r\n".into())))) } pub(crate) fn compute_length(&mut self) -> Option { self.inner.compute_length() } fn with_inner(self, func: F) -> Self where F: FnOnce(FormParts) -> FormParts, { Form { inner: func(self.inner), } } } impl fmt::Debug for Form { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt_fields("Form", f) } } // ===== impl Part ===== impl Part { /// Makes a text parameter. pub fn text(value: T) -> Part where T: Into>, { let body = match value.into() { Cow::Borrowed(slice) => Body::from(slice), Cow::Owned(string) => Body::from(string), }; Part::new(body, None) } /// Makes a new parameter from arbitrary bytes. pub fn bytes(value: T) -> Part where T: Into>, { let body = match value.into() { Cow::Borrowed(slice) => Body::from(slice), Cow::Owned(vec) => Body::from(vec), }; Part::new(body, None) } /// Makes a new parameter from an arbitrary stream. pub fn stream>(value: T) -> Part { Part::new(value.into(), None) } /// Makes a new parameter from an arbitrary stream with a known length. This is particularly /// useful when adding something like file contents as a stream, where you can know the content /// length beforehand. pub fn stream_with_length>(value: T, length: u64) -> Part { Part::new(value.into(), Some(length)) } fn new(value: Body, body_length: Option) -> Part { Part { meta: PartMetadata::new(), value, body_length, } } /// Tries to set the mime of this part. pub fn mime_str(self, mime: &str) -> crate::Result { Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) } // Re-export when mime 0.4 is available, with split MediaType/MediaRange. fn mime(self, mime: Mime) -> Part { self.with_inner(move |inner| inner.mime(mime)) } /// Sets the filename, builder style. pub fn file_name(self, filename: T) -> Part where T: Into>, { self.with_inner(move |inner| inner.file_name(filename)) } /// Sets custom headers for the part. pub fn headers(self, headers: HeaderMap) -> Part { self.with_inner(move |inner| inner.headers(headers)) } fn with_inner(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata, { Part { meta: func(self.meta), ..self } } } impl fmt::Debug for Part { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut dbg = f.debug_struct("Part"); dbg.field("value", &self.value); self.meta.fmt_fields(&mut dbg); dbg.finish() } } impl PartProps for Part { fn value_len(&self) -> Option { if self.body_length.is_some() { self.body_length } else { self.value.content_length() } } fn metadata(&self) -> &PartMetadata { &self.meta } } // ===== impl FormParts ===== impl FormParts

{ pub(crate) fn new() -> Self { FormParts { boundary: gen_boundary(), computed_headers: Vec::new(), fields: Vec::new(), percent_encoding: PercentEncoding::PathSegment, } } pub(crate) fn boundary(&self) -> &str { &self.boundary } /// Adds a customized Part. pub(crate) fn part(mut self, name: T, part: P) -> Self where T: Into>, { self.fields.push((name.into(), part)); self } /// Configure this `Form` to percent-encode using the `path-segment` rules. pub(crate) fn percent_encode_path_segment(mut self) -> Self { self.percent_encoding = PercentEncoding::PathSegment; self } /// Configure this `Form` to percent-encode using the `attr-char` rules. pub(crate) fn percent_encode_attr_chars(mut self) -> Self { self.percent_encoding = PercentEncoding::AttrChar; self } /// Configure this `Form` to skip percent-encoding pub(crate) fn percent_encode_noop(mut self) -> Self { self.percent_encoding = PercentEncoding::NoOp; self } // If predictable, computes the length the request will have // The length should be preditable if only String and file fields have been added, // but not if a generic reader has been added; pub(crate) fn compute_length(&mut self) -> Option { let mut length = 0u64; for &(ref name, ref field) in self.fields.iter() { match field.value_len() { Some(value_length) => { // We are constructing the header just to get its length. To not have to // construct it again when the request is sent we cache these headers. let header = self.percent_encoding.encode_headers(name, field.metadata()); let header_length = header.len(); self.computed_headers.push(header); // The additions mimic the format string out of which the field is constructed // in Reader. Not the cleanest solution because if that format string is // ever changed then this formula needs to be changed too which is not an // obvious dependency in the code. length += 2 + self.boundary().len() as u64 + 2 + header_length as u64 + 4 + value_length + 2 } _ => return None, } } // If there is a at least one field there is a special boundary for the very last field. if !self.fields.is_empty() { length += 2 + self.boundary().len() as u64 + 4 } Some(length) } /// Take the fields vector of this instance, replacing with an empty vector. fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> { std::mem::replace(&mut self.fields, Vec::new()) } } impl FormParts

{ pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct(ty_name) .field("boundary", &self.boundary) .field("parts", &self.fields) .finish() } } // ===== impl PartMetadata ===== impl PartMetadata { pub(crate) fn new() -> Self { PartMetadata { mime: None, file_name: None, headers: HeaderMap::default(), } } pub(crate) fn mime(mut self, mime: Mime) -> Self { self.mime = Some(mime); self } pub(crate) fn file_name(mut self, filename: T) -> Self where T: Into>, { self.file_name = Some(filename.into()); self } pub(crate) fn headers(mut self, headers: T) -> Self where T: Into, { self.headers = headers.into(); self } } impl PartMetadata { pub(crate) fn fmt_fields<'f, 'fa, 'fb>( &self, debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, ) -> &'f mut fmt::DebugStruct<'fa, 'fb> { debug_struct .field("mime", &self.mime) .field("file_name", &self.file_name) .field("headers", &self.headers) } } // https://url.spec.whatwg.org/#fragment-percent-encode-set const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS .add(b' ') .add(b'"') .add(b'<') .add(b'>') .add(b'`'); // https://url.spec.whatwg.org/#path-percent-encode-set const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}'); const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%'); // https://tools.ietf.org/html/rfc8187#section-3.2.1 const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC .remove(b'!') .remove(b'#') .remove(b'$') .remove(b'&') .remove(b'+') .remove(b'-') .remove(b'.') .remove(b'^') .remove(b'_') .remove(b'`') .remove(b'|') .remove(b'~'); pub(crate) enum PercentEncoding { PathSegment, AttrChar, NoOp, } impl PercentEncoding { pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec { let mut buf = Vec::new(); buf.extend_from_slice(b"Content-Disposition: form-data; "); match self.percent_encode(name) { Cow::Borrowed(value) => { // nothing has been percent encoded buf.extend_from_slice(b"name=\""); buf.extend_from_slice(value.as_bytes()); buf.extend_from_slice(b"\""); } Cow::Owned(value) => { // something has been percent encoded buf.extend_from_slice(b"name*=utf-8''"); buf.extend_from_slice(value.as_bytes()); } } // According to RFC7578 Section 4.2, `filename*=` syntax is invalid. // See https://github.com/seanmonstar/reqwest/issues/419. if let Some(filename) = &field.file_name { buf.extend_from_slice(b"; filename=\""); let legal_filename = filename .replace('\\', "\\\\") .replace('"', "\\\"") .replace('\r', "\\\r") .replace('\n', "\\\n"); buf.extend_from_slice(legal_filename.as_bytes()); buf.extend_from_slice(b"\""); } if let Some(mime) = &field.mime { buf.extend_from_slice(b"\r\nContent-Type: "); buf.extend_from_slice(mime.as_ref().as_bytes()); } for (k, v) in field.headers.iter() { buf.extend_from_slice(b"\r\n"); buf.extend_from_slice(k.as_str().as_bytes()); buf.extend_from_slice(b": "); buf.extend_from_slice(v.as_bytes()); } buf } fn percent_encode<'a>(&self, value: &'a str) -> Cow<'a, str> { use percent_encoding::utf8_percent_encode as percent_encode; match self { Self::PathSegment => percent_encode(value, PATH_SEGMENT_ENCODE_SET).into(), Self::AttrChar => percent_encode(value, ATTR_CHAR_ENCODE_SET).into(), Self::NoOp => value.into(), } } } fn gen_boundary() -> String { use crate::util::fast_random as random; let a = random(); let b = random(); let c = random(); let d = random(); format!("{a:016x}-{b:016x}-{c:016x}-{d:016x}") } #[cfg(test)] mod tests { use super::*; use futures_util::TryStreamExt; use futures_util::{future, stream}; use tokio::{self, runtime}; #[test] fn form_empty() { let form = Form::new(); let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let body = form.stream().into_stream(); let s = body.map_ok(|try_c| try_c.to_vec()).try_concat(); let out = rt.block_on(s); assert!(out.unwrap().is_empty()); } #[test] fn stream_to_end() { let mut form = Form::new() .part( "reader1", Part::stream(Body::stream(stream::once(future::ready::< Result, >(Ok( "part1".to_owned() ))))), ) .part("key1", Part::text("value1")) .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) .part( "reader2", Part::stream(Body::stream(stream::once(future::ready::< Result, >(Ok( "part2".to_owned() ))))), ) .part("key3", Part::text("value3").file_name("filename")); form.inner.boundary = "boundary".to_string(); let expected = "--boundary\r\n\ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ part1\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ value1\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\ Content-Type: image/bmp\r\n\r\n\ value2\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ part2\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ value3\r\n--boundary--\r\n"; let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let body = form.stream().into_stream(); let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat(); let out = rt.block_on(s).unwrap(); // These prints are for debug purposes in case the test fails println!( "START REAL\n{}\nEND REAL", std::str::from_utf8(&out).unwrap() ); println!("START EXPECTED\n{expected}\nEND EXPECTED"); assert_eq!(std::str::from_utf8(&out).unwrap(), expected); } #[test] fn stream_to_end_with_header() { let mut part = Part::text("value2").mime(mime::IMAGE_BMP); let mut headers = HeaderMap::new(); headers.insert("Hdr3", "/a/b/c".parse().unwrap()); part = part.headers(headers); let mut form = Form::new().part("key2", part); form.inner.boundary = "boundary".to_string(); let expected = "--boundary\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\ Content-Type: image/bmp\r\n\ hdr3: /a/b/c\r\n\ \r\n\ value2\r\n\ --boundary--\r\n"; let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let body = form.stream().into_stream(); let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat(); let out = rt.block_on(s).unwrap(); // These prints are for debug purposes in case the test fails println!( "START REAL\n{}\nEND REAL", std::str::from_utf8(&out).unwrap() ); println!("START EXPECTED\n{expected}\nEND EXPECTED"); assert_eq!(std::str::from_utf8(&out).unwrap(), expected); } #[test] fn correct_content_length() { // Setup an arbitrary data stream let stream_data = b"just some stream data"; let stream_len = stream_data.len(); let stream_data = stream_data .chunks(3) .map(|c| Ok::<_, std::io::Error>(Bytes::from(c))); let the_stream = futures_util::stream::iter(stream_data); let bytes_data = b"some bytes data".to_vec(); let bytes_len = bytes_data.len(); let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64); let body_part = Part::bytes(bytes_data); // A simple check to make sure we get the configured body length assert_eq!(stream_part.value_len().unwrap(), stream_len as u64); // Make sure it delegates to the underlying body if length is not specified assert_eq!(body_part.value_len().unwrap(), bytes_len as u64); } #[test] fn header_percent_encoding() { let name = "start%'\"\r\nßend"; let field = Part::text(""); assert_eq!( PercentEncoding::PathSegment.encode_headers(name, &field.meta), &b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..] ); assert_eq!( PercentEncoding::AttrChar.encode_headers(name, &field.meta), &b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..] ); } } reqwest-0.11.27/src/async_impl/request.rs000064400000000000000000001020071046102023000164510ustar 00000000000000use std::convert::TryFrom; use std::fmt; use std::future::Future; use std::time::Duration; use serde::Serialize; #[cfg(feature = "json")] use serde_json; use super::body::Body; use super::client::{Client, Pending}; #[cfg(feature = "multipart")] use super::multipart; use super::response::Response; #[cfg(feature = "multipart")] use crate::header::CONTENT_LENGTH; use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; use crate::{Method, Url}; use http::{request::Parts, Request as HttpRequest, Version}; /// A request which can be executed with `Client::execute()`. pub struct Request { method: Method, url: Url, headers: HeaderMap, body: Option, timeout: Option, version: Version, } /// A builder to construct the properties of a `Request`. /// /// To construct a `RequestBuilder`, refer to the `Client` documentation. #[must_use = "RequestBuilder does nothing until you 'send' it"] pub struct RequestBuilder { client: Client, request: crate::Result, } impl Request { /// Constructs a new request. #[inline] pub fn new(method: Method, url: Url) -> Self { Request { method, url, headers: HeaderMap::new(), body: None, timeout: None, version: Version::default(), } } /// Get the method. #[inline] pub fn method(&self) -> &Method { &self.method } /// Get a mutable reference to the method. #[inline] pub fn method_mut(&mut self) -> &mut Method { &mut self.method } /// Get the url. #[inline] pub fn url(&self) -> &Url { &self.url } /// Get a mutable reference to the url. #[inline] pub fn url_mut(&mut self) -> &mut Url { &mut self.url } /// Get the headers. #[inline] pub fn headers(&self) -> &HeaderMap { &self.headers } /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.headers } /// Get the body. #[inline] pub fn body(&self) -> Option<&Body> { self.body.as_ref() } /// Get a mutable reference to the body. #[inline] pub fn body_mut(&mut self) -> &mut Option { &mut self.body } /// Get the timeout. #[inline] pub fn timeout(&self) -> Option<&Duration> { self.timeout.as_ref() } /// Get a mutable reference to the timeout. #[inline] pub fn timeout_mut(&mut self) -> &mut Option { &mut self.timeout } /// Get the http version. #[inline] pub fn version(&self) -> Version { self.version } /// Get a mutable reference to the http version. #[inline] pub fn version_mut(&mut self) -> &mut Version { &mut self.version } /// Attempt to clone the request. /// /// `None` is returned if the request can not be cloned, i.e. if the body is a stream. pub fn try_clone(&self) -> Option { let body = match self.body.as_ref() { Some(body) => Some(body.try_clone()?), None => None, }; let mut req = Request::new(self.method().clone(), self.url().clone()); *req.timeout_mut() = self.timeout().copied(); *req.headers_mut() = self.headers().clone(); *req.version_mut() = self.version(); req.body = body; Some(req) } pub(super) fn pieces( self, ) -> ( Method, Url, HeaderMap, Option, Option, Version, ) { ( self.method, self.url, self.headers, self.body, self.timeout, self.version, ) } } impl RequestBuilder { pub(super) fn new(client: Client, request: crate::Result) -> RequestBuilder { let mut builder = RequestBuilder { client, request }; let auth = builder .request .as_mut() .ok() .and_then(|req| extract_authority(&mut req.url)); if let Some((username, password)) = auth { builder.basic_auth(username, password) } else { builder } } /// Assemble a builder starting from an existing `Client` and a `Request`. pub fn from_parts(client: Client, request: Request) -> RequestBuilder { RequestBuilder { client, request: crate::Result::Ok(request), } } /// Add a `Header` to this Request. pub fn header(self, key: K, value: V) -> RequestBuilder where HeaderName: TryFrom, >::Error: Into, HeaderValue: TryFrom, >::Error: Into, { self.header_sensitive(key, value, false) } /// Add a `Header` to this Request with ability to define if `header_value` is sensitive. fn header_sensitive(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder where HeaderName: TryFrom, >::Error: Into, HeaderValue: TryFrom, >::Error: Into, { let mut error = None; if let Ok(ref mut req) = self.request { match >::try_from(key) { Ok(key) => match >::try_from(value) { Ok(mut value) => { // We want to potentially make an unsensitive header // to be sensitive, not the reverse. So, don't turn off // a previously sensitive header. if sensitive { value.set_sensitive(true); } req.headers_mut().append(key, value); } Err(e) => error = Some(crate::error::builder(e.into())), }, Err(e) => error = Some(crate::error::builder(e.into())), }; } if let Some(err) = error { self.request = Err(err); } self } /// Add a set of Headers to the existing ones on this Request. /// /// The headers will be merged in to any already set. pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder { if let Ok(ref mut req) = self.request { crate::util::replace_headers(req.headers_mut(), headers); } self } /// Enable HTTP basic authentication. /// /// ```rust /// # use reqwest::Error; /// /// # async fn run() -> Result<(), Error> { /// let client = reqwest::Client::new(); /// let resp = client.delete("http://httpbin.org/delete") /// .basic_auth("admin", Some("good password")) /// .send() /// .await?; /// # Ok(()) /// # } /// ``` pub fn basic_auth(self, username: U, password: Option

) -> RequestBuilder where U: fmt::Display, P: fmt::Display, { let header_value = crate::util::basic_auth(username, password); self.header_sensitive(crate::header::AUTHORIZATION, header_value, true) } /// Enable HTTP bearer authentication. pub fn bearer_auth(self, token: T) -> RequestBuilder where T: fmt::Display, { let header_value = format!("Bearer {token}"); self.header_sensitive(crate::header::AUTHORIZATION, header_value, true) } /// Set the request body. pub fn body>(mut self, body: T) -> RequestBuilder { if let Ok(ref mut req) = self.request { *req.body_mut() = Some(body.into()); } self } /// Enables a request timeout. /// /// The timeout is applied from when the request starts connecting until the /// response body has finished. It affects only this request and overrides /// the timeout configured using `ClientBuilder::timeout()`. pub fn timeout(mut self, timeout: Duration) -> RequestBuilder { if let Ok(ref mut req) = self.request { *req.timeout_mut() = Some(timeout); } self } /// Sends a multipart/form-data body. /// /// ``` /// # use reqwest::Error; /// /// # async fn run() -> Result<(), Error> { /// let client = reqwest::Client::new(); /// let form = reqwest::multipart::Form::new() /// .text("key3", "value3") /// .text("key4", "value4"); /// /// /// let response = client.post("your url") /// .multipart(form) /// .send() /// .await?; /// # Ok(()) /// # } /// ``` #[cfg(feature = "multipart")] #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder { let mut builder = self.header( CONTENT_TYPE, format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(), ); builder = match multipart.compute_length() { Some(length) => builder.header(CONTENT_LENGTH, length), None => builder, }; if let Ok(ref mut req) = builder.request { *req.body_mut() = Some(multipart.stream()) } builder } /// Modify the query string of the URL. /// /// Modifies the URL of this request, adding the parameters provided. /// This method appends and does not overwrite. This means that it can /// be called multiple times and that existing query parameters are not /// overwritten if the same key is used. The key will simply show up /// twice in the query string. /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`. /// /// # Note /// This method does not support serializing a single key-value /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such /// as `.query(&[("key", "val")])`. It's also possible to serialize structs /// and maps into a key-value pair. /// /// # Errors /// This method will fail if the object you provide cannot be serialized /// into a query string. pub fn query(mut self, query: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { let url = req.url_mut(); let mut pairs = url.query_pairs_mut(); let serializer = serde_urlencoded::Serializer::new(&mut pairs); if let Err(err) = query.serialize(serializer) { error = Some(crate::error::builder(err)); } } if let Ok(ref mut req) = self.request { if let Some("") = req.url().query() { req.url_mut().set_query(None); } } if let Some(err) = error { self.request = Err(err); } self } /// Set HTTP version pub fn version(mut self, version: Version) -> RequestBuilder { if let Ok(ref mut req) = self.request { req.version = version; } self } /// Send a form body. /// /// Sets the body to the url encoded serialization of the passed value, /// and also sets the `Content-Type: application/x-www-form-urlencoded` /// header. /// /// ```rust /// # use reqwest::Error; /// # use std::collections::HashMap; /// # /// # async fn run() -> Result<(), Error> { /// let mut params = HashMap::new(); /// params.insert("lang", "rust"); /// /// let client = reqwest::Client::new(); /// let res = client.post("http://httpbin.org") /// .form(¶ms) /// .send() /// .await?; /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// This method fails if the passed value cannot be serialized into /// url encoded format pub fn form(mut self, form: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { match serde_urlencoded::to_string(form) { Ok(body) => { req.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded"), ); *req.body_mut() = Some(body.into()); } Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { self.request = Err(err); } self } /// Send a JSON body. /// /// # Optional /// /// This requires the optional `json` feature enabled. /// /// # Errors /// /// Serialization can fail if `T`'s implementation of `Serialize` decides to /// fail, or if `T` contains a map with non-string keys. #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub fn json(mut self, json: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { match serde_json::to_vec(json) { Ok(body) => { if !req.headers().contains_key(CONTENT_TYPE) { req.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); } *req.body_mut() = Some(body.into()); } Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { self.request = Err(err); } self } /// Disable CORS on fetching the request. /// /// # WASM /// /// This option is only effective with WebAssembly target. /// /// The [request mode][mdn] will be set to 'no-cors'. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode pub fn fetch_mode_no_cors(self) -> RequestBuilder { self } /// Build a `Request`, which can be inspected, modified and executed with /// `Client::execute()`. pub fn build(self) -> crate::Result { self.request } /// Build a `Request`, which can be inspected, modified and executed with /// `Client::execute()`. /// /// This is similar to [`RequestBuilder::build()`], but also returns the /// embedded `Client`. pub fn build_split(self) -> (Client, crate::Result) { (self.client, self.request) } /// Constructs the Request and sends it to the target URL, returning a /// future Response. /// /// # Errors /// /// This method fails if there was an error while sending request, /// redirect loop was detected or redirect limit was exhausted. /// /// # Example /// /// ```no_run /// # use reqwest::Error; /// # /// # async fn run() -> Result<(), Error> { /// let response = reqwest::Client::new() /// .get("https://hyper.rs") /// .send() /// .await?; /// # Ok(()) /// # } /// ``` pub fn send(self) -> impl Future> { match self.request { Ok(req) => self.client.execute_request(req), Err(err) => Pending::new_err(err), } } /// Attempt to clone the RequestBuilder. /// /// `None` is returned if the RequestBuilder can not be cloned, /// i.e. if the request body is a stream. /// /// # Examples /// /// ``` /// # use reqwest::Error; /// # /// # fn run() -> Result<(), Error> { /// let client = reqwest::Client::new(); /// let builder = client.post("http://httpbin.org/post") /// .body("from a &str!"); /// let clone = builder.try_clone(); /// assert!(clone.is_some()); /// # Ok(()) /// # } /// ``` pub fn try_clone(&self) -> Option { self.request .as_ref() .ok() .and_then(|req| req.try_clone()) .map(|req| RequestBuilder { client: self.client.clone(), request: Ok(req), }) } } impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_request_fields(&mut f.debug_struct("Request"), self).finish() } } impl fmt::Debug for RequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("RequestBuilder"); match self.request { Ok(ref req) => fmt_request_fields(&mut builder, req).finish(), Err(ref err) => builder.field("error", err).finish(), } } } fn fmt_request_fields<'a, 'b>( f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request, ) -> &'a mut fmt::DebugStruct<'a, 'b> { f.field("method", &req.method) .field("url", &req.url) .field("headers", &req.headers) } /// Check the request URL for a "username:password" type authority, and if /// found, remove it from the URL and return it. pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option)> { use percent_encoding::percent_decode; if url.has_authority() { let username: String = percent_decode(url.username().as_bytes()) .decode_utf8() .ok()? .into(); let password = url.password().and_then(|pass| { percent_decode(pass.as_bytes()) .decode_utf8() .ok() .map(String::from) }); if !username.is_empty() || password.is_some() { url.set_username("") .expect("has_authority means set_username shouldn't fail"); url.set_password(None) .expect("has_authority means set_password shouldn't fail"); return Some((username, password)); } } None } impl TryFrom> for Request where T: Into, { type Error = crate::Error; fn try_from(req: HttpRequest) -> crate::Result { let (parts, body) = req.into_parts(); let Parts { method, uri, headers, version, .. } = parts; let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?; Ok(Request { method, url, headers, body: Some(body.into()), timeout: None, version, }) } } impl TryFrom for HttpRequest { type Error = crate::Error; fn try_from(req: Request) -> crate::Result { let Request { method, url, headers, body, version, .. } = req; let mut req = HttpRequest::builder() .version(version) .method(method) .uri(url.as_str()) .body(body.unwrap_or_else(Body::empty)) .map_err(crate::error::builder)?; *req.headers_mut() = headers; Ok(req) } } #[cfg(test)] mod tests { use super::{Client, HttpRequest, Request, RequestBuilder, Version}; use crate::Method; use serde::Serialize; use std::collections::BTreeMap; use std::convert::TryFrom; #[test] fn add_query_append() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.get(some_url); let r = r.query(&[("foo", "bar")]); let r = r.query(&[("qux", 3)]); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=bar&qux=3")); } #[test] fn add_query_append_same() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.get(some_url); let r = r.query(&[("foo", "a"), ("foo", "b")]); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=a&foo=b")); } #[test] fn add_query_struct() { #[derive(Serialize)] struct Params { foo: String, qux: i32, } let client = Client::new(); let some_url = "https://google.com/"; let r = client.get(some_url); let params = Params { foo: "bar".into(), qux: 3, }; let r = r.query(¶ms); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=bar&qux=3")); } #[test] fn add_query_map() { let mut params = BTreeMap::new(); params.insert("foo", "bar"); params.insert("qux", "three"); let client = Client::new(); let some_url = "https://google.com/"; let r = client.get(some_url); let r = r.query(¶ms); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=bar&qux=three")); } #[test] fn test_replace_headers() { use http::HeaderMap; let mut headers = HeaderMap::new(); headers.insert("foo", "bar".parse().unwrap()); headers.append("foo", "baz".parse().unwrap()); let client = Client::new(); let req = client .get("https://hyper.rs") .header("im-a", "keeper") .header("foo", "pop me") .headers(headers) .build() .expect("request build"); assert_eq!(req.headers()["im-a"], "keeper"); let foo = req.headers().get_all("foo").iter().collect::>(); assert_eq!(foo.len(), 2); assert_eq!(foo[0], "bar"); assert_eq!(foo[1], "baz"); } #[test] fn normalize_empty_query() { let client = Client::new(); let some_url = "https://google.com/"; let empty_query: &[(&str, &str)] = &[]; let req = client .get(some_url) .query(empty_query) .build() .expect("request build"); assert_eq!(req.url().query(), None); assert_eq!(req.url().as_str(), "https://google.com/"); } #[test] fn try_clone_reusable() { let client = Client::new(); let builder = client .post("http://httpbin.org/post") .header("foo", "bar") .body("from a &str!"); let req = builder .try_clone() .expect("clone successful") .build() .expect("request is valid"); assert_eq!(req.url().as_str(), "http://httpbin.org/post"); assert_eq!(req.method(), Method::POST); assert_eq!(req.headers()["foo"], "bar"); } #[test] fn try_clone_no_body() { let client = Client::new(); let builder = client.get("http://httpbin.org/get"); let req = builder .try_clone() .expect("clone successful") .build() .expect("request is valid"); assert_eq!(req.url().as_str(), "http://httpbin.org/get"); assert_eq!(req.method(), Method::GET); assert!(req.body().is_none()); } #[test] #[cfg(feature = "stream")] fn try_clone_stream() { let chunks: Vec> = vec![Ok("hello"), Ok(" "), Ok("world")]; let stream = futures_util::stream::iter(chunks); let client = Client::new(); let builder = client .get("http://httpbin.org/get") .body(super::Body::wrap_stream(stream)); let clone = builder.try_clone(); assert!(clone.is_none()); } #[test] fn convert_url_authority_into_basic_auth() { let client = Client::new(); let some_url = "https://Aladdin:open sesame@localhost/"; let req = client.get(some_url).build().expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!( req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); } #[test] fn test_basic_auth_sensitive_header() { let client = Client::new(); let some_url = "https://localhost/"; let req = client .get(some_url) .basic_auth("Aladdin", Some("open sesame")) .build() .expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!( req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); assert!(req.headers()["authorization"].is_sensitive()); } #[test] fn test_bearer_auth_sensitive_header() { let client = Client::new(); let some_url = "https://localhost/"; let req = client .get(some_url) .bearer_auth("Hold my bear") .build() .expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!(req.headers()["authorization"], "Bearer Hold my bear"); assert!(req.headers()["authorization"].is_sensitive()); } #[test] fn test_explicit_sensitive_header() { let client = Client::new(); let some_url = "https://localhost/"; let mut header = http::HeaderValue::from_static("in plain sight"); header.set_sensitive(true); let req = client .get(some_url) .header("hiding", header) .build() .expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!(req.headers()["hiding"], "in plain sight"); assert!(req.headers()["hiding"].is_sensitive()); } #[test] fn convert_from_http_request() { let http_request = HttpRequest::builder() .method("GET") .uri("http://localhost/") .header("User-Agent", "my-awesome-agent/1.0") .body("test test test") .unwrap(); let req: Request = Request::try_from(http_request).unwrap(); assert!(req.body().is_some()); let test_data = b"test test test"; assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); let headers = req.headers(); assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0"); assert_eq!(req.method(), Method::GET); assert_eq!(req.url().as_str(), "http://localhost/"); } #[test] fn set_http_request_version() { let http_request = HttpRequest::builder() .method("GET") .uri("http://localhost/") .header("User-Agent", "my-awesome-agent/1.0") .version(Version::HTTP_11) .body("test test test") .unwrap(); let req: Request = Request::try_from(http_request).unwrap(); assert!(req.body().is_some()); let test_data = b"test test test"; assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); let headers = req.headers(); assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0"); assert_eq!(req.method(), Method::GET); assert_eq!(req.url().as_str(), "http://localhost/"); assert_eq!(req.version(), Version::HTTP_11); } #[test] fn builder_split_reassemble() { let builder = { let client = Client::new(); client.get("http://example.com") }; let (client, inner) = builder.build_split(); let request = inner.unwrap(); let builder = RequestBuilder::from_parts(client, request); builder.build().unwrap(); } /* use {body, Method}; use super::Client; use header::{Host, Headers, ContentType}; use std::collections::HashMap; use serde_urlencoded; use serde_json; #[test] fn basic_get_request() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let r = client.get(some_url).unwrap().build(); assert_eq!(r.method, Method::Get); assert_eq!(r.url.as_str(), some_url); } #[test] fn basic_head_request() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let r = client.head(some_url).unwrap().build(); assert_eq!(r.method, Method::Head); assert_eq!(r.url.as_str(), some_url); } #[test] fn basic_post_request() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let r = client.post(some_url).unwrap().build(); assert_eq!(r.method, Method::Post); assert_eq!(r.url.as_str(), some_url); } #[test] fn basic_put_request() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let r = client.put(some_url).unwrap().build(); assert_eq!(r.method, Method::Put); assert_eq!(r.url.as_str(), some_url); } #[test] fn basic_patch_request() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let r = client.patch(some_url).unwrap().build(); assert_eq!(r.method, Method::Patch); assert_eq!(r.url.as_str(), some_url); } #[test] fn basic_delete_request() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let r = client.delete(some_url).unwrap().build(); assert_eq!(r.method, Method::Delete); assert_eq!(r.url.as_str(), some_url); } #[test] fn add_header() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let header = Host { hostname: "google.com".to_string(), port: None, }; // Add a copy of the header to the request builder let r = r.header(header.clone()).build(); // then check it was actually added assert_eq!(r.headers.get::(), Some(&header)); } #[test] fn add_headers() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let header = Host { hostname: "google.com".to_string(), port: None, }; let mut headers = Headers::new(); headers.set(header); // Add a copy of the headers to the request builder let r = r.headers(headers.clone()).build(); // then make sure they were added correctly assert_eq!(r.headers, headers); } #[test] fn add_headers_multi() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let header = Host { hostname: "google.com".to_string(), port: None, }; let mut headers = Headers::new(); headers.set(header); // Add a copy of the headers to the request builder let r = r.headers(headers.clone()).build(); // then make sure they were added correctly assert_eq!(r.headers, headers); } #[test] fn add_body() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let body = "Some interesting content"; let r = r.body(body).build(); let buf = body::read_to_string(r.body.unwrap()).unwrap(); assert_eq!(buf, body); } #[test] fn add_form() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let mut form_data = HashMap::new(); form_data.insert("foo", "bar"); let r = r.form(&form_data).unwrap().build(); // Make sure the content type was set assert_eq!(r.headers.get::(), Some(&ContentType::form_url_encoded())); let buf = body::read_to_string(r.body.unwrap()).unwrap(); let body_should_be = serde_urlencoded::to_string(&form_data).unwrap(); assert_eq!(buf, body_should_be); } #[test] fn add_json() { let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let mut json_data = HashMap::new(); json_data.insert("foo", "bar"); let r = r.json(&json_data).unwrap().build(); // Make sure the content type was set assert_eq!(r.headers.get::(), Some(&ContentType::json())); let buf = body::read_to_string(r.body.unwrap()).unwrap(); let body_should_be = serde_json::to_string(&json_data).unwrap(); assert_eq!(buf, body_should_be); } #[test] fn add_json_fail() { use serde::{Serialize, Serializer}; use serde::ser::Error; struct MyStruct; impl Serialize for MyStruct { fn serialize(&self, _serializer: S) -> Result where S: Serializer { Err(S::Error::custom("nope")) } } let client = Client::new().unwrap(); let some_url = "https://google.com/"; let mut r = client.post(some_url).unwrap(); let json_data = MyStruct{}; assert!(r.json(&json_data).unwrap_err().is_serialization()); } */ } reqwest-0.11.27/src/async_impl/response.rs000064400000000000000000000317251046102023000166270ustar 00000000000000use std::fmt; use std::net::SocketAddr; use std::pin::Pin; use bytes::Bytes; use encoding_rs::{Encoding, UTF_8}; use futures_util::stream::StreamExt; use hyper::client::connect::HttpInfo; use hyper::{HeaderMap, StatusCode, Version}; use mime::Mime; #[cfg(feature = "json")] use serde::de::DeserializeOwned; #[cfg(feature = "json")] use serde_json; use tokio::time::Sleep; use url::Url; use super::body::Body; use super::decoder::{Accepts, Decoder}; #[cfg(feature = "cookies")] use crate::cookie; use crate::response::ResponseUrl; /// A Response to a submitted `Request`. pub struct Response { pub(super) res: hyper::Response, // Boxed to save space (11 words to 1 word), and it's not accessed // frequently internally. url: Box, } impl Response { pub(super) fn new( res: hyper::Response, url: Url, accepts: Accepts, timeout: Option>>, ) -> Response { let (mut parts, body) = res.into_parts(); let decoder = Decoder::detect(&mut parts.headers, Body::response(body, timeout), accepts); let res = hyper::Response::from_parts(parts, decoder); Response { res, url: Box::new(url), } } /// Get the `StatusCode` of this `Response`. #[inline] pub fn status(&self) -> StatusCode { self.res.status() } /// Get the HTTP `Version` of this `Response`. #[inline] pub fn version(&self) -> Version { self.res.version() } /// Get the `Headers` of this `Response`. #[inline] pub fn headers(&self) -> &HeaderMap { self.res.headers() } /// Get a mutable reference to the `Headers` of this `Response`. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { self.res.headers_mut() } /// Get the content-length of this response, if known. /// /// Reasons it may not be known: /// /// - The server didn't send a `content-length` header. /// - The response is compressed and automatically decoded (thus changing /// the actual decoded length). pub fn content_length(&self) -> Option { use hyper::body::HttpBody; HttpBody::size_hint(self.res.body()).exact() } /// Retrieve the cookies contained in the response. /// /// Note that invalid 'Set-Cookie' headers will be ignored. /// /// # Optional /// /// This requires the optional `cookies` feature to be enabled. #[cfg(feature = "cookies")] #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookies<'a>(&'a self) -> impl Iterator> + 'a { cookie::extract_response_cookies(self.res.headers()).filter_map(Result::ok) } /// Get the final `Url` of this `Response`. #[inline] pub fn url(&self) -> &Url { &self.url } /// Get the remote address used to get this `Response`. pub fn remote_addr(&self) -> Option { self.res .extensions() .get::() .map(|info| info.remote_addr()) } /// Returns a reference to the associated extensions. pub fn extensions(&self) -> &http::Extensions { self.res.extensions() } /// Returns a mutable reference to the associated extensions. pub fn extensions_mut(&mut self) -> &mut http::Extensions { self.res.extensions_mut() } // body methods /// Get the full response text. /// /// This method decodes the response body with BOM sniffing /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. /// Encoding is determined from the `charset` parameter of `Content-Type` header, /// and defaults to `utf-8` if not presented. /// /// Note that the BOM is stripped from the returned String. /// /// # Example /// /// ``` /// # async fn run() -> Result<(), Box> { /// let content = reqwest::get("http://httpbin.org/range/26") /// .await? /// .text() /// .await?; /// /// println!("text: {content:?}"); /// # Ok(()) /// # } /// ``` pub async fn text(self) -> crate::Result { self.text_with_charset("utf-8").await } /// Get the full response text given a specific encoding. /// /// This method decodes the response body with BOM sniffing /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. /// You can provide a default encoding for decoding the raw message, while the /// `charset` parameter of `Content-Type` header is still prioritized. For more information /// about the possible encoding name, please go to [`encoding_rs`] docs. /// /// Note that the BOM is stripped from the returned String. /// /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages /// /// # Example /// /// ``` /// # async fn run() -> Result<(), Box> { /// let content = reqwest::get("http://httpbin.org/range/26") /// .await? /// .text_with_charset("utf-8") /// .await?; /// /// println!("text: {content:?}"); /// # Ok(()) /// # } /// ``` pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result { let content_type = self .headers() .get(crate::header::CONTENT_TYPE) .and_then(|value| value.to_str().ok()) .and_then(|value| value.parse::().ok()); let encoding_name = content_type .as_ref() .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str())) .unwrap_or(default_encoding); let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8); let full = self.bytes().await?; let (text, _, _) = encoding.decode(&full); Ok(text.into_owned()) } /// Try to deserialize the response body as JSON. /// /// # Optional /// /// This requires the optional `json` feature enabled. /// /// # Examples /// /// ``` /// # extern crate reqwest; /// # extern crate serde; /// # /// # use reqwest::Error; /// # use serde::Deserialize; /// # /// // This `derive` requires the `serde` dependency. /// #[derive(Deserialize)] /// struct Ip { /// origin: String, /// } /// /// # async fn run() -> Result<(), Error> { /// let ip = reqwest::get("http://httpbin.org/ip") /// .await? /// .json::() /// .await?; /// /// println!("ip: {}", ip.origin); /// # Ok(()) /// # } /// # /// # fn main() { } /// ``` /// /// # Errors /// /// This method fails whenever the response body is not in JSON format /// or it cannot be properly deserialized to target type `T`. For more /// details please see [`serde_json::from_reader`]. /// /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub async fn json(self) -> crate::Result { let full = self.bytes().await?; serde_json::from_slice(&full).map_err(crate::error::decode) } /// Get the full response body as `Bytes`. /// /// # Example /// /// ``` /// # async fn run() -> Result<(), Box> { /// let bytes = reqwest::get("http://httpbin.org/ip") /// .await? /// .bytes() /// .await?; /// /// println!("bytes: {bytes:?}"); /// # Ok(()) /// # } /// ``` pub async fn bytes(self) -> crate::Result { hyper::body::to_bytes(self.res.into_body()).await } /// Stream a chunk of the response body. /// /// When the response body has been exhausted, this will return `None`. /// /// # Example /// /// ``` /// # async fn run() -> Result<(), Box> { /// let mut res = reqwest::get("https://hyper.rs").await?; /// /// while let Some(chunk) = res.chunk().await? { /// println!("Chunk: {chunk:?}"); /// } /// # Ok(()) /// # } /// ``` pub async fn chunk(&mut self) -> crate::Result> { if let Some(item) = self.res.body_mut().next().await { Ok(Some(item?)) } else { Ok(None) } } /// Convert the response into a `Stream` of `Bytes` from the body. /// /// # Example /// /// ``` /// use futures_util::StreamExt; /// /// # async fn run() -> Result<(), Box> { /// let mut stream = reqwest::get("http://httpbin.org/ip") /// .await? /// .bytes_stream(); /// /// while let Some(item) = stream.next().await { /// println!("Chunk: {:?}", item?); /// } /// # Ok(()) /// # } /// ``` /// /// # Optional /// /// This requires the optional `stream` feature to be enabled. #[cfg(feature = "stream")] #[cfg_attr(docsrs, doc(cfg(feature = "stream")))] pub fn bytes_stream(self) -> impl futures_core::Stream> { self.res.into_body() } // util methods /// Turn a response into an error if the server returned an error. /// /// # Example /// /// ``` /// # use reqwest::Response; /// fn on_response(res: Response) { /// match res.error_for_status() { /// Ok(_res) => (), /// Err(err) => { /// // asserting a 400 as an example /// // it could be any status between 400...599 /// assert_eq!( /// err.status(), /// Some(reqwest::StatusCode::BAD_REQUEST) /// ); /// } /// } /// } /// # fn main() {} /// ``` pub fn error_for_status(self) -> crate::Result { let status = self.status(); if status.is_client_error() || status.is_server_error() { Err(crate::error::status_code(*self.url, status)) } else { Ok(self) } } /// Turn a reference to a response into an error if the server returned an error. /// /// # Example /// /// ``` /// # use reqwest::Response; /// fn on_response(res: &Response) { /// match res.error_for_status_ref() { /// Ok(_res) => (), /// Err(err) => { /// // asserting a 400 as an example /// // it could be any status between 400...599 /// assert_eq!( /// err.status(), /// Some(reqwest::StatusCode::BAD_REQUEST) /// ); /// } /// } /// } /// # fn main() {} /// ``` pub fn error_for_status_ref(&self) -> crate::Result<&Self> { let status = self.status(); if status.is_client_error() || status.is_server_error() { Err(crate::error::status_code(*self.url.clone(), status)) } else { Ok(self) } } // private // The Response's body is an implementation detail. // You no longer need to get a reference to it, there are async methods // on the `Response` itself. // // This method is just used by the blocking API. #[cfg(feature = "blocking")] pub(crate) fn body_mut(&mut self) -> &mut Decoder { self.res.body_mut() } } impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Response") .field("url", self.url()) .field("status", &self.status()) .field("headers", self.headers()) .finish() } } impl> From> for Response { fn from(r: http::Response) -> Response { let (mut parts, body) = r.into_parts(); let body = body.into(); let decoder = Decoder::detect(&mut parts.headers, body, Accepts::none()); let url = parts .extensions .remove::() .unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap())); let url = url.0; let res = hyper::Response::from_parts(parts, decoder); Response { res, url: Box::new(url), } } } /// A `Response` can be piped as the `Body` of another request. impl From for Body { fn from(r: Response) -> Body { Body::stream(r.res.into_body()) } } #[cfg(test)] mod tests { use super::Response; use crate::ResponseBuilderExt; use http::response::Builder; use url::Url; #[test] fn test_from_http_response() { let url = Url::parse("http://example.com").unwrap(); let response = Builder::new() .status(200) .url(url.clone()) .body("foo") .unwrap(); let response = Response::from(response); assert_eq!(response.status(), 200); assert_eq!(*response.url(), url); } } reqwest-0.11.27/src/async_impl/upgrade.rs000064400000000000000000000036651046102023000164220ustar 00000000000000use std::pin::Pin; use std::task::{self, Poll}; use std::{fmt, io}; use futures_util::TryFutureExt; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; /// An upgraded HTTP connection. pub struct Upgraded { inner: hyper::upgrade::Upgraded, } impl AsyncRead for Upgraded { fn poll_read( mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { Pin::new(&mut self.inner).poll_read(cx, buf) } } impl AsyncWrite for Upgraded { fn poll_write( mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.inner).poll_write(cx, buf) } fn poll_write_vectored( mut self: Pin<&mut Self>, cx: &mut task::Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { Pin::new(&mut self.inner).poll_write_vectored(cx, bufs) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { Pin::new(&mut self.inner).poll_flush(cx) } fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { Pin::new(&mut self.inner).poll_shutdown(cx) } fn is_write_vectored(&self) -> bool { self.inner.is_write_vectored() } } impl fmt::Debug for Upgraded { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Upgraded").finish() } } impl From for Upgraded { fn from(inner: hyper::upgrade::Upgraded) -> Self { Upgraded { inner } } } impl super::response::Response { /// Consumes the response and returns a future for a possible HTTP upgrade. pub async fn upgrade(self) -> crate::Result { hyper::upgrade::on(self.res) .map_ok(Upgraded::from) .map_err(crate::error::upgrade) .await } } reqwest-0.11.27/src/blocking/body.rs000064400000000000000000000243651046102023000153620ustar 00000000000000use std::fmt; use std::fs::File; use std::future::Future; #[cfg(feature = "multipart")] use std::io::Cursor; use std::io::{self, Read}; use std::mem; use std::ptr; use bytes::buf::UninitSlice; use bytes::Bytes; use crate::async_impl; /// The body of a `Request`. /// /// In most cases, this is not needed directly, as the /// [`RequestBuilder.body`][builder] method uses `Into`, which allows /// passing many things (like a string or vector of bytes). /// /// [builder]: ./struct.RequestBuilder.html#method.body #[derive(Debug)] pub struct Body { kind: Kind, } impl Body { /// Instantiate a `Body` from a reader. /// /// # Note /// /// While allowing for many types to be used, these bodies do not have /// a way to reset to the beginning and be reused. This means that when /// encountering a 307 or 308 status code, instead of repeating the /// request at the new location, the `Response` will be returned with /// the redirect status code set. /// /// ```rust /// # use std::fs::File; /// # use reqwest::blocking::Body; /// # fn run() -> Result<(), Box> { /// let file = File::open("national_secrets.txt")?; /// let body = Body::new(file); /// # Ok(()) /// # } /// ``` /// /// If you have a set of bytes, like `String` or `Vec`, using the /// `From` implementations for `Body` will store the data in a manner /// it can be reused. /// /// ```rust /// # use reqwest::blocking::Body; /// # fn run() -> Result<(), Box> { /// let s = "A stringy body"; /// let body = Body::from(s); /// # Ok(()) /// # } /// ``` pub fn new(reader: R) -> Body { Body { kind: Kind::Reader(Box::from(reader), None), } } /// Create a `Body` from a `Read` where the size is known in advance /// but the data should not be fully loaded into memory. This will /// set the `Content-Length` header and stream from the `Read`. /// /// ```rust /// # use std::fs::File; /// # use reqwest::blocking::Body; /// # fn run() -> Result<(), Box> { /// let file = File::open("a_large_file.txt")?; /// let file_size = file.metadata()?.len(); /// let body = Body::sized(file, file_size); /// # Ok(()) /// # } /// ``` pub fn sized(reader: R, len: u64) -> Body { Body { kind: Kind::Reader(Box::from(reader), Some(len)), } } /// Returns the body as a byte slice if the body is already buffered in /// memory. For streamed requests this method returns `None`. pub fn as_bytes(&self) -> Option<&[u8]> { match self.kind { Kind::Reader(_, _) => None, Kind::Bytes(ref bytes) => Some(bytes.as_ref()), } } /// Converts streamed requests to their buffered equivalent and /// returns a reference to the buffer. If the request is already /// buffered, this has no effect. /// /// Be aware that for large requests this method is expensive /// and may cause your program to run out of memory. pub fn buffer(&mut self) -> Result<&[u8], crate::Error> { match self.kind { Kind::Reader(ref mut reader, maybe_len) => { let mut bytes = if let Some(len) = maybe_len { Vec::with_capacity(len as usize) } else { Vec::new() }; io::copy(reader, &mut bytes).map_err(crate::error::builder)?; self.kind = Kind::Bytes(bytes.into()); self.buffer() } Kind::Bytes(ref bytes) => Ok(bytes.as_ref()), } } #[cfg(feature = "multipart")] pub(crate) fn len(&self) -> Option { match self.kind { Kind::Reader(_, len) => len, Kind::Bytes(ref bytes) => Some(bytes.len() as u64), } } #[cfg(feature = "multipart")] pub(crate) fn into_reader(self) -> Reader { match self.kind { Kind::Reader(r, _) => Reader::Reader(r), Kind::Bytes(b) => Reader::Bytes(Cursor::new(b)), } } pub(crate) fn into_async(self) -> (Option, async_impl::Body, Option) { match self.kind { Kind::Reader(read, len) => { let (tx, rx) = hyper::Body::channel(); let tx = Sender { body: (read, len), tx, }; (Some(tx), async_impl::Body::wrap(rx), len) } Kind::Bytes(chunk) => { let len = chunk.len() as u64; (None, async_impl::Body::reusable(chunk), Some(len)) } } } pub(crate) fn try_clone(&self) -> Option { self.kind.try_clone().map(|kind| Body { kind }) } } enum Kind { Reader(Box, Option), Bytes(Bytes), } impl Kind { fn try_clone(&self) -> Option { match self { Kind::Reader(..) => None, Kind::Bytes(v) => Some(Kind::Bytes(v.clone())), } } } impl From> for Body { #[inline] fn from(v: Vec) -> Body { Body { kind: Kind::Bytes(v.into()), } } } impl From for Body { #[inline] fn from(s: String) -> Body { s.into_bytes().into() } } impl From<&'static [u8]> for Body { #[inline] fn from(s: &'static [u8]) -> Body { Body { kind: Kind::Bytes(Bytes::from_static(s)), } } } impl From<&'static str> for Body { #[inline] fn from(s: &'static str) -> Body { s.as_bytes().into() } } impl From for Body { #[inline] fn from(f: File) -> Body { let len = f.metadata().map(|m| m.len()).ok(); Body { kind: Kind::Reader(Box::new(f), len), } } } impl From for Body { #[inline] fn from(b: Bytes) -> Body { Body { kind: Kind::Bytes(b), } } } impl fmt::Debug for Kind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Kind::Reader(_, ref v) => f .debug_struct("Reader") .field("length", &DebugLength(v)) .finish(), Kind::Bytes(ref v) => fmt::Debug::fmt(v, f), } } } struct DebugLength<'a>(&'a Option); impl<'a> fmt::Debug for DebugLength<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self.0 { Some(ref len) => fmt::Debug::fmt(len, f), None => f.write_str("Unknown"), } } } #[cfg(feature = "multipart")] pub(crate) enum Reader { Reader(Box), Bytes(Cursor), } #[cfg(feature = "multipart")] impl Read for Reader { fn read(&mut self, buf: &mut [u8]) -> io::Result { match *self { Reader::Reader(ref mut rdr) => rdr.read(buf), Reader::Bytes(ref mut rdr) => rdr.read(buf), } } } pub(crate) struct Sender { body: (Box, Option), tx: hyper::body::Sender, } async fn send_future(sender: Sender) -> Result<(), crate::Error> { use bytes::{BufMut, BytesMut}; use std::cmp; let con_len = sender.body.1; let cap = cmp::min(sender.body.1.unwrap_or(8192), 8192); let mut written = 0; let mut buf = BytesMut::with_capacity(cap as usize); let mut body = sender.body.0; // Put in an option so that it can be consumed on error to call abort() let mut tx = Some(sender.tx); loop { if Some(written) == con_len { // Written up to content-length, so stop. return Ok(()); } // The input stream is read only if the buffer is empty so // that there is only one read in the buffer at any time. // // We need to know whether there is any data to send before // we check the transmission channel (with poll_ready below) // because somestimes the receiver disappears as soon as is // considers the data is completely transmitted, which may // be true. // // The use case is a web server that closes its // input stream as soon as the data received is valid JSON. // This behaviour is questionable, but it exists and the // fact is that there is actually no remaining data to read. if buf.is_empty() { if buf.remaining_mut() == 0 { buf.reserve(8192); // zero out the reserved memory let uninit = buf.chunk_mut(); unsafe { ptr::write_bytes(uninit.as_mut_ptr(), 0, uninit.len()); } } let bytes = unsafe { mem::transmute::<&mut UninitSlice, &mut [u8]>(buf.chunk_mut()) }; match body.read(bytes) { Ok(0) => { // The buffer was empty and nothing's left to // read. Return. return Ok(()); } Ok(n) => unsafe { buf.advance_mut(n); }, Err(e) => { tx.take().expect("tx only taken on error").abort(); return Err(crate::error::body(e)); } } } // The only way to get here is when the buffer is not empty. // We can check the transmission channel let buf_len = buf.len() as u64; tx.as_mut() .expect("tx only taken on error") .send_data(buf.split().freeze()) .await .map_err(crate::error::body)?; written += buf_len; } } impl Sender { // A `Future` that may do blocking read calls. // As a `Future`, this integrates easily with `wait::timeout`. pub(crate) fn send(self) -> impl Future> { send_future(self) } } // useful for tests, but not publicly exposed #[cfg(test)] pub(crate) fn read_to_string(mut body: Body) -> io::Result { let mut s = String::new(); match body.kind { Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s), Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s), } .map(|_| s) } reqwest-0.11.27/src/blocking/client.rs000064400000000000000000001216301046102023000156740ustar 00000000000000#[cfg(any(feature = "native-tls", feature = "__rustls",))] use std::any::Any; use std::convert::TryInto; use std::fmt; use std::future::Future; use std::net::IpAddr; use std::net::SocketAddr; use std::sync::Arc; use std::thread; use std::time::Duration; use http::header::HeaderValue; use log::{error, trace}; use tokio::sync::{mpsc, oneshot}; use super::request::{Request, RequestBuilder}; use super::response::Response; use super::wait; #[cfg(feature = "__tls")] use crate::tls; #[cfg(feature = "__tls")] use crate::Certificate; #[cfg(any(feature = "native-tls", feature = "__rustls"))] use crate::Identity; use crate::{async_impl, header, redirect, IntoUrl, Method, Proxy}; /// A `Client` to make Requests with. /// /// The Client has various configuration values to tweak, but the defaults /// are set to what is usually the most commonly desired value. To configure a /// `Client`, use `Client::builder()`. /// /// The `Client` holds a connection pool internally, so it is advised that /// you create one and **reuse** it. /// /// # Examples /// /// ```rust /// use reqwest::blocking::Client; /// # /// # fn run() -> Result<(), reqwest::Error> { /// let client = Client::new(); /// let resp = client.get("http://httpbin.org/").send()?; /// # drop(resp); /// # Ok(()) /// # } /// /// ``` #[derive(Clone)] pub struct Client { inner: ClientHandle, } /// A `ClientBuilder` can be used to create a `Client` with custom configuration. /// /// # Example /// /// ``` /// # fn run() -> Result<(), reqwest::Error> { /// use std::time::Duration; /// /// let client = reqwest::blocking::Client::builder() /// .timeout(Duration::from_secs(10)) /// .build()?; /// # Ok(()) /// # } /// ``` #[must_use] pub struct ClientBuilder { inner: async_impl::ClientBuilder, timeout: Timeout, } impl Default for ClientBuilder { fn default() -> Self { Self::new() } } impl ClientBuilder { /// Constructs a new `ClientBuilder`. /// /// This is the same as `Client::builder()`. pub fn new() -> ClientBuilder { ClientBuilder { inner: async_impl::ClientBuilder::new(), timeout: Timeout::default(), } } /// Returns a `Client` that uses this `ClientBuilder` configuration. /// /// # Errors /// /// This method fails if TLS backend cannot be initialized, or the resolver /// cannot load the system configuration. /// /// # Panics /// /// This method panics if called from within an async runtime. See docs on /// [`reqwest::blocking`][crate::blocking] for details. pub fn build(self) -> crate::Result { ClientHandle::new(self).map(|handle| Client { inner: handle }) } // Higher-level options /// Sets the `User-Agent` header to be used by this client. /// /// # Example /// /// ```rust /// # fn doc() -> Result<(), reqwest::Error> { /// // Name your user agent after your app? /// static APP_USER_AGENT: &str = concat!( /// env!("CARGO_PKG_NAME"), /// "/", /// env!("CARGO_PKG_VERSION"), /// ); /// /// let client = reqwest::blocking::Client::builder() /// .user_agent(APP_USER_AGENT) /// .build()?; /// let res = client.get("https://www.rust-lang.org").send()?; /// # Ok(()) /// # } /// ``` pub fn user_agent(self, value: V) -> ClientBuilder where V: TryInto, V::Error: Into, { self.with_inner(move |inner| inner.user_agent(value)) } /// Sets the default headers for every request. /// /// # Example /// /// ```rust /// use reqwest::header; /// # fn build_client() -> Result<(), reqwest::Error> { /// let mut headers = header::HeaderMap::new(); /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value")); /// headers.insert(header::AUTHORIZATION, header::HeaderValue::from_static("secret")); /// /// // Consider marking security-sensitive headers with `set_sensitive`. /// let mut auth_value = header::HeaderValue::from_static("secret"); /// auth_value.set_sensitive(true); /// headers.insert(header::AUTHORIZATION, auth_value); /// /// // get a client builder /// let client = reqwest::blocking::Client::builder() /// .default_headers(headers) /// .build()?; /// let res = client.get("https://www.rust-lang.org").send()?; /// # Ok(()) /// # } /// ``` /// /// Override the default headers: /// /// ```rust /// use reqwest::header; /// # fn build_client() -> Result<(), reqwest::Error> { /// let mut headers = header::HeaderMap::new(); /// headers.insert("X-MY-HEADER", header::HeaderValue::from_static("value")); /// /// // get a client builder /// let client = reqwest::blocking::Client::builder() /// .default_headers(headers) /// .build()?; /// let res = client /// .get("https://www.rust-lang.org") /// .header("X-MY-HEADER", "new_value") /// .send()?; /// # Ok(()) /// # } /// ``` pub fn default_headers(self, headers: header::HeaderMap) -> ClientBuilder { self.with_inner(move |inner| inner.default_headers(headers)) } /// Enable a persistent cookie store for the client. /// /// Cookies received in responses will be preserved and included in /// additional requests. /// /// By default, no cookie store is used. /// /// # Optional /// /// This requires the optional `cookies` feature to be enabled. #[cfg(feature = "cookies")] #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookie_store(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.cookie_store(enable)) } /// Set the persistent cookie store for the client. /// /// Cookies received in responses will be passed to this store, and /// additional requests will query this store for cookies. /// /// By default, no cookie store is used. /// /// # Optional /// /// This requires the optional `cookies` feature to be enabled. #[cfg(feature = "cookies")] #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookie_provider( self, cookie_store: Arc, ) -> ClientBuilder { self.with_inner(|inner| inner.cookie_provider(cookie_store)) } /// Enable auto gzip decompression by checking the `Content-Encoding` response header. /// /// If auto gzip decompresson is turned on: /// /// - When sending a request and if the request's headers do not already contain /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `gzip`. /// The request body is **not** automatically compressed. /// - When receiving a response, if it's headers contain a `Content-Encoding` value that /// equals to `gzip`, both values `Content-Encoding` and `Content-Length` are removed from the /// headers' set. The response body is automatically decompressed. /// /// If the `gzip` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `gzip` feature to be enabled #[cfg(feature = "gzip")] #[cfg_attr(docsrs, doc(cfg(feature = "gzip")))] pub fn gzip(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.gzip(enable)) } /// Enable auto brotli decompression by checking the `Content-Encoding` response header. /// /// If auto brotli decompression is turned on: /// /// - When sending a request and if the request's headers do not already contain /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `br`. /// The request body is **not** automatically compressed. /// - When receiving a response, if it's headers contain a `Content-Encoding` value that /// equals to `br`, both values `Content-Encoding` and `Content-Length` are removed from the /// headers' set. The response body is automatically decompressed. /// /// If the `brotli` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `brotli` feature to be enabled #[cfg(feature = "brotli")] #[cfg_attr(docsrs, doc(cfg(feature = "brotli")))] pub fn brotli(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.brotli(enable)) } /// Enable auto deflate decompression by checking the `Content-Encoding` response header. /// /// If auto deflate decompresson is turned on: /// /// - When sending a request and if the request's headers do not already contain /// an `Accept-Encoding` **and** `Range` values, the `Accept-Encoding` header is set to `deflate`. /// The request body is **not** automatically compressed. /// - When receiving a response, if it's headers contain a `Content-Encoding` value that /// equals to `deflate`, both values `Content-Encoding` and `Content-Length` are removed from the /// headers' set. The response body is automatically decompressed. /// /// If the `deflate` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `deflate` feature to be enabled #[cfg(feature = "deflate")] #[cfg_attr(docsrs, doc(cfg(feature = "deflate")))] pub fn deflate(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.deflate(enable)) } /// Disable auto response body gzip decompression. /// /// This method exists even if the optional `gzip` feature is not enabled. /// This can be used to ensure a `Client` doesn't use gzip decompression /// even if another dependency were to enable the optional `gzip` feature. pub fn no_gzip(self) -> ClientBuilder { self.with_inner(|inner| inner.no_gzip()) } /// Disable auto response body brotli decompression. /// /// This method exists even if the optional `brotli` feature is not enabled. /// This can be used to ensure a `Client` doesn't use brotli decompression /// even if another dependency were to enable the optional `brotli` feature. pub fn no_brotli(self) -> ClientBuilder { self.with_inner(|inner| inner.no_brotli()) } /// Disable auto response body deflate decompression. /// /// This method exists even if the optional `deflate` feature is not enabled. /// This can be used to ensure a `Client` doesn't use deflate decompression /// even if another dependency were to enable the optional `deflate` feature. pub fn no_deflate(self) -> ClientBuilder { self.with_inner(|inner| inner.no_deflate()) } // Redirect options /// Set a `redirect::Policy` for this client. /// /// Default will follow redirects up to a maximum of 10. pub fn redirect(self, policy: redirect::Policy) -> ClientBuilder { self.with_inner(move |inner| inner.redirect(policy)) } /// Enable or disable automatic setting of the `Referer` header. /// /// Default is `true`. pub fn referer(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.referer(enable)) } // Proxy options /// Add a `Proxy` to the list of proxies the `Client` will use. /// /// # Note /// /// Adding a proxy will disable the automatic usage of the "system" proxy. pub fn proxy(self, proxy: Proxy) -> ClientBuilder { self.with_inner(move |inner| inner.proxy(proxy)) } /// Clear all `Proxies`, so `Client` will use no proxy anymore. /// /// # Note /// To add a proxy exclusion list, use [crate::proxy::Proxy::no_proxy()] /// on all desired proxies instead. /// /// This also disables the automatic usage of the "system" proxy. pub fn no_proxy(self) -> ClientBuilder { self.with_inner(move |inner| inner.no_proxy()) } // Timeout options /// Set a timeout for connect, read and write operations of a `Client`. /// /// Default is 30 seconds. /// /// Pass `None` to disable timeout. pub fn timeout(mut self, timeout: T) -> ClientBuilder where T: Into>, { self.timeout = Timeout(timeout.into()); self } /// Set a timeout for only the connect phase of a `Client`. /// /// Default is `None`. pub fn connect_timeout(self, timeout: T) -> ClientBuilder where T: Into>, { let timeout = timeout.into(); if let Some(dur) = timeout { self.with_inner(|inner| inner.connect_timeout(dur)) } else { self } } /// Set whether connections should emit verbose logs. /// /// Enabling this option will emit [log][] messages at the `TRACE` level /// for read and write operations on connections. /// /// [log]: https://crates.io/crates/log pub fn connection_verbose(self, verbose: bool) -> ClientBuilder { self.with_inner(move |inner| inner.connection_verbose(verbose)) } // HTTP options /// Set an optional timeout for idle sockets being kept-alive. /// /// Pass `None` to disable timeout. /// /// Default is 90 seconds. pub fn pool_idle_timeout(self, val: D) -> ClientBuilder where D: Into>, { self.with_inner(|inner| inner.pool_idle_timeout(val)) } /// Sets the maximum idle connection per host allowed in the pool. pub fn pool_max_idle_per_host(self, max: usize) -> ClientBuilder { self.with_inner(move |inner| inner.pool_max_idle_per_host(max)) } /// Send headers as title case instead of lowercase. pub fn http1_title_case_headers(self) -> ClientBuilder { self.with_inner(|inner| inner.http1_title_case_headers()) } /// Set whether HTTP/1 connections will accept obsolete line folding for /// header values. /// /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when /// parsing. pub fn http1_allow_obsolete_multiline_headers_in_responses(self, value: bool) -> ClientBuilder { self.with_inner(|inner| inner.http1_allow_obsolete_multiline_headers_in_responses(value)) } /// Sets whether invalid header lines should be silently ignored in HTTP/1 responses. pub fn http1_ignore_invalid_headers_in_responses(self, value: bool) -> ClientBuilder { self.with_inner(|inner| inner.http1_ignore_invalid_headers_in_responses(value)) } /// Set whether HTTP/1 connections will accept spaces between header /// names and the colon that follow them in responses. /// /// Newline codepoints (\r and \n) will be transformed to spaces when /// parsing. pub fn http1_allow_spaces_after_header_name_in_responses(self, value: bool) -> ClientBuilder { self.with_inner(|inner| inner.http1_allow_spaces_after_header_name_in_responses(value)) } /// Only use HTTP/1. pub fn http1_only(self) -> ClientBuilder { self.with_inner(|inner| inner.http1_only()) } /// Allow HTTP/0.9 responses pub fn http09_responses(self) -> ClientBuilder { self.with_inner(|inner| inner.http09_responses()) } /// Only use HTTP/2. pub fn http2_prior_knowledge(self) -> ClientBuilder { self.with_inner(|inner| inner.http2_prior_knowledge()) } /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP2 stream-level flow control. /// /// Default is currently 65,535 but may change internally to optimize for common uses. pub fn http2_initial_stream_window_size(self, sz: impl Into>) -> ClientBuilder { self.with_inner(|inner| inner.http2_initial_stream_window_size(sz)) } /// Sets the max connection-level flow control for HTTP2 /// /// Default is currently 65,535 but may change internally to optimize for common uses. pub fn http2_initial_connection_window_size(self, sz: impl Into>) -> ClientBuilder { self.with_inner(|inner| inner.http2_initial_connection_window_size(sz)) } /// Sets whether to use an adaptive flow control. /// /// Enabling this will override the limits set in `http2_initial_stream_window_size` and /// `http2_initial_connection_window_size`. pub fn http2_adaptive_window(self, enabled: bool) -> ClientBuilder { self.with_inner(|inner| inner.http2_adaptive_window(enabled)) } /// Sets the maximum frame size to use for HTTP2. /// /// Default is currently 16,384 but may change internally to optimize for common uses. pub fn http2_max_frame_size(self, sz: impl Into>) -> ClientBuilder { self.with_inner(|inner| inner.http2_max_frame_size(sz)) } /// This requires the optional `http3` feature to be /// enabled. #[cfg(feature = "http3")] #[cfg_attr(docsrs, doc(cfg(feature = "http3")))] pub fn http3_prior_knowledge(self) -> ClientBuilder { self.with_inner(|inner| inner.http3_prior_knowledge()) } // TCP options /// Set whether sockets have `TCP_NODELAY` enabled. /// /// Default is `true`. pub fn tcp_nodelay(self, enabled: bool) -> ClientBuilder { self.with_inner(move |inner| inner.tcp_nodelay(enabled)) } /// Bind to a local IP Address. /// /// # Example /// /// ``` /// use std::net::IpAddr; /// let local_addr = IpAddr::from([12, 4, 1, 8]); /// let client = reqwest::blocking::Client::builder() /// .local_address(local_addr) /// .build().unwrap(); /// ``` pub fn local_address(self, addr: T) -> ClientBuilder where T: Into>, { self.with_inner(move |inner| inner.local_address(addr)) } /// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration. /// /// If `None`, the option will not be set. pub fn tcp_keepalive(self, val: D) -> ClientBuilder where D: Into>, { self.with_inner(move |inner| inner.tcp_keepalive(val)) } // TLS options /// Add a custom root certificate. /// /// This allows connecting to a server that has a self-signed /// certificate for example. This **does not** replace the existing /// trusted store. /// /// # Example /// /// ``` /// # use std::fs::File; /// # use std::io::Read; /// # fn build_client() -> Result<(), Box> { /// // read a local binary DER encoded certificate /// let der = std::fs::read("my-cert.der")?; /// /// // create a certificate /// let cert = reqwest::Certificate::from_der(&der)?; /// /// // get a client builder /// let client = reqwest::blocking::Client::builder() /// .add_root_certificate(cert) /// .build()?; /// # drop(client); /// # Ok(()) /// # } /// ``` /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn add_root_certificate(self, cert: Certificate) -> ClientBuilder { self.with_inner(move |inner| inner.add_root_certificate(cert)) } /// Controls the use of built-in system certificates during certificate validation. /// /// Defaults to `true` -- built-in system certs will be used. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn tls_built_in_root_certs(self, tls_built_in_root_certs: bool) -> ClientBuilder { self.with_inner(move |inner| inner.tls_built_in_root_certs(tls_built_in_root_certs)) } /// Sets the identity to be used for client certificate authentication. /// /// # Optional /// /// This requires the optional `native-tls` or `rustls-tls(-...)` feature to be /// enabled. #[cfg(any(feature = "native-tls", feature = "__rustls"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))] pub fn identity(self, identity: Identity) -> ClientBuilder { self.with_inner(move |inner| inner.identity(identity)) } /// Controls the use of hostname verification. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before you use this method. If /// hostname verification is not used, any valid certificate for any /// site will be trusted for use from any other. This introduces a /// significant vulnerability to man-in-the-middle attacks. /// /// # Optional /// /// This requires the optional `native-tls` feature to be enabled. #[cfg(feature = "native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostname: bool) -> ClientBuilder { self.with_inner(|inner| inner.danger_accept_invalid_hostnames(accept_invalid_hostname)) } /// Controls the use of certificate validation. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before using this method. If /// invalid certificates are trusted, *any* certificate for *any* site /// will be trusted for use. This includes expired certificates. This /// introduces significant vulnerabilities, and should only be used /// as a last resort. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn danger_accept_invalid_certs(self, accept_invalid_certs: bool) -> ClientBuilder { self.with_inner(|inner| inner.danger_accept_invalid_certs(accept_invalid_certs)) } /// Controls the use of TLS server name indication. /// /// Defaults to `true`. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn tls_sni(self, tls_sni: bool) -> ClientBuilder { self.with_inner(|inner| inner.tls_sni(tls_sni)) } /// Set the minimum required TLS version for connections. /// /// By default the TLS backend's own default is used. /// /// # Errors /// /// A value of `tls::Version::TLS_1_3` will cause an error with the /// `native-tls`/`default-tls` backend. This does not mean the version /// isn't supported, just that it can't be set as a minimum due to /// technical limitations. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn min_tls_version(self, version: tls::Version) -> ClientBuilder { self.with_inner(|inner| inner.min_tls_version(version)) } /// Set the maximum allowed TLS version for connections. /// /// By default there's no maximum. /// /// # Errors /// /// A value of `tls::Version::TLS_1_3` will cause an error with the /// `native-tls`/`default-tls` backend. This does not mean the version /// isn't supported, just that it can't be set as a maximum due to /// technical limitations. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn max_tls_version(self, version: tls::Version) -> ClientBuilder { self.with_inner(|inner| inner.max_tls_version(version)) } /// Force using the native TLS backend. /// /// Since multiple TLS backends can be optionally enabled, this option will /// force the `native-tls` backend to be used for this `Client`. /// /// # Optional /// /// This requires the optional `native-tls` feature to be enabled. #[cfg(feature = "native-tls")] #[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))] pub fn use_native_tls(self) -> ClientBuilder { self.with_inner(move |inner| inner.use_native_tls()) } /// Force using the Rustls TLS backend. /// /// Since multiple TLS backends can be optionally enabled, this option will /// force the `rustls` backend to be used for this `Client`. /// /// # Optional /// /// This requires the optional `rustls-tls(-...)` feature to be enabled. #[cfg(feature = "__rustls")] #[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))] pub fn use_rustls_tls(self) -> ClientBuilder { self.with_inner(move |inner| inner.use_rustls_tls()) } /// Add TLS information as `TlsInfo` extension to responses. /// /// # Optional /// /// This requires the optional `default-tls`, `native-tls`, or `rustls-tls(-...)` /// feature to be enabled. #[cfg(feature = "__tls")] #[cfg_attr( docsrs, doc(cfg(any( feature = "default-tls", feature = "native-tls", feature = "rustls-tls" ))) )] pub fn tls_info(self, tls_info: bool) -> ClientBuilder { self.with_inner(|inner| inner.tls_info(tls_info)) } /// Use a preconfigured TLS backend. /// /// If the passed `Any` argument is not a TLS backend that reqwest /// understands, the `ClientBuilder` will error when calling `build`. /// /// # Advanced /// /// This is an advanced option, and can be somewhat brittle. Usage requires /// keeping the preconfigured TLS argument version in sync with reqwest, /// since version mismatches will result in an "unknown" TLS backend. /// /// If possible, it's preferable to use the methods on `ClientBuilder` /// to configure reqwest's TLS. /// /// # Optional /// /// This requires one of the optional features `native-tls` or /// `rustls-tls(-...)` to be enabled. #[cfg(any(feature = "native-tls", feature = "__rustls",))] #[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))] pub fn use_preconfigured_tls(self, tls: impl Any) -> ClientBuilder { self.with_inner(move |inner| inner.use_preconfigured_tls(tls)) } /// Enables the [hickory-dns](hickory_resolver) async resolver instead of a default threadpool using `getaddrinfo`. /// /// If the `hickory-dns` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `hickory-dns` feature to be enabled #[cfg(feature = "hickory-dns")] #[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))] #[deprecated(note = "use `hickory_dns` instead", since = "0.12.0")] pub fn trust_dns(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.hickory_dns(enable)) } /// Enables the [hickory-dns](hickory_resolver) async resolver instead of a default threadpool using `getaddrinfo`. /// /// If the `hickory-dns` feature is turned on, the default option is enabled. /// /// # Optional /// /// This requires the optional `hickory-dns` feature to be enabled #[cfg(feature = "hickory-dns")] #[cfg_attr(docsrs, doc(cfg(feature = "hickory-dns")))] pub fn hickory_dns(self, enable: bool) -> ClientBuilder { self.with_inner(|inner| inner.hickory_dns(enable)) } /// Disables the hickory-dns async resolver. /// /// This method exists even if the optional `hickory-dns` feature is not enabled. /// This can be used to ensure a `Client` doesn't use the hickory-dns async resolver /// even if another dependency were to enable the optional `hickory-dns` feature. #[deprecated(note = "use `no_hickory_dns` instead", since = "0.12.0")] pub fn no_trust_dns(self) -> ClientBuilder { self.with_inner(|inner| inner.no_hickory_dns()) } /// Disables the hickory-dns async resolver. /// /// This method exists even if the optional `hickory-dns` feature is not enabled. /// This can be used to ensure a `Client` doesn't use the hickory-dns async resolver /// even if another dependency were to enable the optional `hickory-dns` feature. pub fn no_hickory_dns(self) -> ClientBuilder { self.with_inner(|inner| inner.no_hickory_dns()) } /// Restrict the Client to be used with HTTPS only requests. /// /// Defaults to false. pub fn https_only(self, enabled: bool) -> ClientBuilder { self.with_inner(|inner| inner.https_only(enabled)) } /// Override DNS resolution for specific domains to a particular IP address. /// /// Warning /// /// Since the DNS protocol has no notion of ports, if you wish to send /// traffic to a particular port you must include this port in the URL /// itself, any port in the overridden addr will be ignored and traffic sent /// to the conventional port for the given scheme (e.g. 80 for http). pub fn resolve(self, domain: &str, addr: SocketAddr) -> ClientBuilder { self.resolve_to_addrs(domain, &[addr]) } /// Override DNS resolution for specific domains to particular IP addresses. /// /// Warning /// /// Since the DNS protocol has no notion of ports, if you wish to send /// traffic to a particular port you must include this port in the URL /// itself, any port in the overridden addresses will be ignored and traffic sent /// to the conventional port for the given scheme (e.g. 80 for http). pub fn resolve_to_addrs(self, domain: &str, addrs: &[SocketAddr]) -> ClientBuilder { self.with_inner(|inner| inner.resolve_to_addrs(domain, addrs)) } // private fn with_inner(mut self, func: F) -> ClientBuilder where F: FnOnce(async_impl::ClientBuilder) -> async_impl::ClientBuilder, { self.inner = func(self.inner); self } } impl From for ClientBuilder { fn from(builder: async_impl::ClientBuilder) -> Self { Self { inner: builder, timeout: Timeout::default(), } } } impl Default for Client { fn default() -> Self { Self::new() } } impl Client { /// Constructs a new `Client`. /// /// # Panic /// /// This method panics if TLS backend cannot be initialized, or the resolver /// cannot load the system configuration. /// /// Use `Client::builder()` if you wish to handle the failure as an `Error` /// instead of panicking. /// /// This method also panics if called from within an async runtime. See docs /// on [`reqwest::blocking`][crate::blocking] for details. pub fn new() -> Client { ClientBuilder::new().build().expect("Client::new()") } /// Creates a `ClientBuilder` to configure a `Client`. /// /// This is the same as `ClientBuilder::new()`. pub fn builder() -> ClientBuilder { ClientBuilder::new() } /// Convenience method to make a `GET` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn get(&self, url: U) -> RequestBuilder { self.request(Method::GET, url) } /// Convenience method to make a `POST` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn post(&self, url: U) -> RequestBuilder { self.request(Method::POST, url) } /// Convenience method to make a `PUT` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn put(&self, url: U) -> RequestBuilder { self.request(Method::PUT, url) } /// Convenience method to make a `PATCH` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn patch(&self, url: U) -> RequestBuilder { self.request(Method::PATCH, url) } /// Convenience method to make a `DELETE` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn delete(&self, url: U) -> RequestBuilder { self.request(Method::DELETE, url) } /// Convenience method to make a `HEAD` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn head(&self, url: U) -> RequestBuilder { self.request(Method::HEAD, url) } /// Start building a `Request` with the `Method` and `Url`. /// /// Returns a `RequestBuilder`, which will allow setting headers and /// request body before sending. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn request(&self, method: Method, url: U) -> RequestBuilder { let req = url.into_url().map(move |url| Request::new(method, url)); RequestBuilder::new(self.clone(), req) } /// Executes a `Request`. /// /// A `Request` can be built manually with `Request::new()` or obtained /// from a RequestBuilder with `RequestBuilder::build()`. /// /// You should prefer to use the `RequestBuilder` and /// `RequestBuilder::send()`. /// /// # Errors /// /// This method fails if there was an error while sending request, /// or redirect limit was exhausted. pub fn execute(&self, request: Request) -> crate::Result { self.inner.execute_request(request) } } impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Client") //.field("gzip", &self.inner.gzip) //.field("redirect_policy", &self.inner.redirect_policy) //.field("referer", &self.inner.referer) .finish() } } impl fmt::Debug for ClientBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt(f) } } #[derive(Clone)] struct ClientHandle { timeout: Timeout, inner: Arc, } type OneshotResponse = oneshot::Sender>; type ThreadSender = mpsc::UnboundedSender<(async_impl::Request, OneshotResponse)>; struct InnerClientHandle { tx: Option, thread: Option>, } impl Drop for InnerClientHandle { fn drop(&mut self) { let id = self .thread .as_ref() .map(|h| h.thread().id()) .expect("thread not dropped yet"); trace!("closing runtime thread ({id:?})"); self.tx.take(); trace!("signaled close for runtime thread ({id:?})"); self.thread.take().map(|h| h.join()); trace!("closed runtime thread ({id:?})"); } } impl ClientHandle { fn new(builder: ClientBuilder) -> crate::Result { let timeout = builder.timeout; let builder = builder.inner; let (tx, rx) = mpsc::unbounded_channel::<(async_impl::Request, OneshotResponse)>(); let (spawn_tx, spawn_rx) = oneshot::channel::>(); let handle = thread::Builder::new() .name("reqwest-internal-sync-runtime".into()) .spawn(move || { use tokio::runtime; let rt = match runtime::Builder::new_current_thread() .enable_all() .build() .map_err(crate::error::builder) { Err(e) => { if let Err(e) = spawn_tx.send(Err(e)) { error!("Failed to communicate runtime creation failure: {e:?}"); } return; } Ok(v) => v, }; let f = async move { let client = match builder.build() { Err(e) => { if let Err(e) = spawn_tx.send(Err(e)) { error!("Failed to communicate client creation failure: {e:?}"); } return; } Ok(v) => v, }; if let Err(e) = spawn_tx.send(Ok(())) { error!("Failed to communicate successful startup: {e:?}"); return; } let mut rx = rx; while let Some((req, req_tx)) = rx.recv().await { let req_fut = client.execute(req); tokio::spawn(forward(req_fut, req_tx)); } trace!("({:?}) Receiver is shutdown", thread::current().id()); }; trace!("({:?}) start runtime::block_on", thread::current().id()); rt.block_on(f); trace!("({:?}) end runtime::block_on", thread::current().id()); drop(rt); trace!("({:?}) finished", thread::current().id()); }) .map_err(crate::error::builder)?; // Wait for the runtime thread to start up... match wait::timeout(spawn_rx, None) { Ok(Ok(())) => (), Ok(Err(err)) => return Err(err), Err(_canceled) => event_loop_panicked(), } let inner_handle = Arc::new(InnerClientHandle { tx: Some(tx), thread: Some(handle), }); Ok(ClientHandle { timeout, inner: inner_handle, }) } fn execute_request(&self, req: Request) -> crate::Result { let (tx, rx) = oneshot::channel(); let (req, body) = req.into_async(); let url = req.url().clone(); let timeout = req.timeout().copied().or(self.timeout.0); self.inner .tx .as_ref() .expect("core thread exited early") .send((req, tx)) .expect("core thread panicked"); let result: Result, wait::Waited> = if let Some(body) = body { let f = async move { body.send().await?; rx.await.map_err(|_canceled| event_loop_panicked()) }; wait::timeout(f, timeout) } else { let f = async move { rx.await.map_err(|_canceled| event_loop_panicked()) }; wait::timeout(f, timeout) }; match result { Ok(Err(err)) => Err(err.with_url(url)), Ok(Ok(res)) => Ok(Response::new( res, timeout, KeepCoreThreadAlive(Some(self.inner.clone())), )), Err(wait::Waited::TimedOut(e)) => Err(crate::error::request(e).with_url(url)), Err(wait::Waited::Inner(err)) => Err(err.with_url(url)), } } } async fn forward(fut: F, mut tx: OneshotResponse) where F: Future>, { use std::task::Poll; futures_util::pin_mut!(fut); // "select" on the sender being canceled, and the future completing let res = futures_util::future::poll_fn(|cx| { match fut.as_mut().poll(cx) { Poll::Ready(val) => Poll::Ready(Some(val)), Poll::Pending => { // check if the callback is canceled futures_core::ready!(tx.poll_closed(cx)); Poll::Ready(None) } } }) .await; if let Some(res) = res { let _ = tx.send(res); } // else request is canceled } #[derive(Clone, Copy)] struct Timeout(Option); impl Default for Timeout { fn default() -> Timeout { // default mentioned in ClientBuilder::timeout() doc comment Timeout(Some(Duration::from_secs(30))) } } pub(crate) struct KeepCoreThreadAlive(#[allow(unused)] Option>); impl KeepCoreThreadAlive { pub(crate) fn empty() -> KeepCoreThreadAlive { KeepCoreThreadAlive(None) } } #[cold] #[inline(never)] fn event_loop_panicked() -> ! { // The only possible reason there would be a Canceled error // is if the thread running the event loop panicked. We could return // an Err here, like a BrokenPipe, but the Client is not // recoverable. Additionally, the panic in the other thread // is not normal, and should likely be propagated. panic!("event loop thread panicked"); } reqwest-0.11.27/src/blocking/mod.rs000064400000000000000000000067311046102023000152010ustar 00000000000000//! A blocking Client API. //! //! The blocking `Client` will block the current thread to execute, instead //! of returning futures that need to be executed on a runtime. //! //! Conversely, the functionality in `reqwest::blocking` must *not* be executed //! within an async runtime, or it will panic when attempting to block. If //! calling directly from an async function, consider using an async //! [`reqwest::Client`][crate::Client] instead. If the immediate context is only //! synchronous, but a transitive caller is async, consider changing that caller //! to use [`tokio::task::spawn_blocking`] around the calls that need to block. //! //! # Optional //! //! This requires the optional `blocking` feature to be enabled. //! //! # Making a GET request //! //! For a single request, you can use the [`get`] shortcut method. //! //! ```rust //! # use reqwest::{Error, Response}; //! //! # fn run() -> Result<(), Error> { //! let body = reqwest::blocking::get("https://www.rust-lang.org")? //! .text()?; //! //! println!("body = {body:?}"); //! # Ok(()) //! # } //! ``` //! //! Additionally, the blocking [`Response`] struct implements Rust's //! `Read` trait, so many useful standard library and third party crates will //! have convenience methods that take a `Response` anywhere `T: Read` is //! acceptable. //! //! **NOTE**: If you plan to perform multiple requests, it is best to create a //! [`Client`] and reuse it, taking advantage of keep-alive connection pooling. //! //! # Making POST requests (or setting request bodies) //! //! There are several ways you can set the body of a request. The basic one is //! by using the `body()` method of a [`RequestBuilder`]. This lets you set the //! exact raw bytes of what the body should be. It accepts various types, //! including `String`, `Vec`, and `File`. If you wish to pass a custom //! Reader, you can use the `reqwest::blocking::Body::new()` constructor. //! //! ```rust //! # use reqwest::Error; //! # //! # fn run() -> Result<(), Error> { //! let client = reqwest::blocking::Client::new(); //! let res = client.post("http://httpbin.org/post") //! .body("the exact body that is sent") //! .send()?; //! # Ok(()) //! # } //! ``` //! //! ## And More //! //! Most features available to the asynchronous `Client` are also available, //! on the blocking `Client`, see those docs for more. mod body; mod client; #[cfg(feature = "multipart")] pub mod multipart; mod request; mod response; mod wait; pub use self::body::Body; pub use self::client::{Client, ClientBuilder}; pub use self::request::{Request, RequestBuilder}; pub use self::response::Response; /// Shortcut method to quickly make a *blocking* `GET` request. /// /// **NOTE**: This function creates a new internal `Client` on each call, /// and so should not be used if making many requests. Create a /// [`Client`](./struct.Client.html) instead. /// /// # Examples /// /// ```rust /// # fn run() -> Result<(), reqwest::Error> { /// let body = reqwest::blocking::get("https://www.rust-lang.org")? /// .text()?; /// # Ok(()) /// # } /// # fn main() { } /// ``` /// /// # Errors /// /// This function fails if: /// /// - the native TLS backend cannot be initialized, /// - the supplied `Url` cannot be parsed, /// - there was an error while sending request, /// - a redirect loop was detected, /// - the redirect limit was exhausted, or /// - the total download time exceeds 30 seconds. pub fn get(url: T) -> crate::Result { Client::builder().build()?.get(url).send() } reqwest-0.11.27/src/blocking/multipart.rs000064400000000000000000000350501046102023000164370ustar 00000000000000//! multipart/form-data //! //! To send a `multipart/form-data` body, a [`Form`] is built up, adding //! fields or customized [`Part`]s, and then calling the //! [`multipart`][builder] method on the `RequestBuilder`. //! //! # Example //! //! ``` //! use reqwest::blocking::multipart; //! //! # fn run() -> Result<(), Box> { //! let form = multipart::Form::new() //! // Adding just a simple text field... //! .text("username", "seanmonstar") //! // And a file... //! .file("photo", "/path/to/photo.png")?; //! //! // Customize all the details of a Part if needed... //! let bio = multipart::Part::text("hallo peeps") //! .file_name("bio.txt") //! .mime_str("text/plain")?; //! //! // Add the custom part to our form... //! let form = form.part("biography", bio); //! //! // And finally, send the form //! let client = reqwest::blocking::Client::new(); //! let resp = client //! .post("http://localhost:8080/user") //! .multipart(form) //! .send()?; //! # Ok(()) //! # } //! # fn main() {} //! ``` //! //! [builder]: ../struct.RequestBuilder.html#method.multipart use std::borrow::Cow; use std::fmt; use std::fs::File; use std::io::{self, Cursor, Read}; use std::path::Path; use mime_guess::{self, Mime}; use super::Body; use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps}; use crate::header::HeaderMap; /// A multipart/form-data request. pub struct Form { inner: FormParts, } /// A field in a multipart form. pub struct Part { meta: PartMetadata, value: Body, } impl Default for Form { fn default() -> Self { Self::new() } } impl Form { /// Creates a new Form without any content. pub fn new() -> Form { Form { inner: FormParts::new(), } } /// Get the boundary that this form will use. #[inline] pub fn boundary(&self) -> &str { self.inner.boundary() } /// Add a data field with supplied name and value. /// /// # Examples /// /// ``` /// let form = reqwest::blocking::multipart::Form::new() /// .text("username", "seanmonstar") /// .text("password", "secret"); /// ``` pub fn text(self, name: T, value: U) -> Form where T: Into>, U: Into>, { self.part(name, Part::text(value)) } /// Adds a file field. /// /// The path will be used to try to guess the filename and mime. /// /// # Examples /// /// ```no_run /// # fn run() -> std::io::Result<()> { /// let files = reqwest::blocking::multipart::Form::new() /// .file("key", "/path/to/file")?; /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// Errors when the file cannot be opened. pub fn file(self, name: T, path: U) -> io::Result

where T: Into>, U: AsRef, { Ok(self.part(name, Part::file(path)?)) } /// Adds a customized Part. pub fn part(self, name: T, part: Part) -> Form where T: Into>, { self.with_inner(move |inner| inner.part(name, part)) } /// Configure this `Form` to percent-encode using the `path-segment` rules. pub fn percent_encode_path_segment(self) -> Form { self.with_inner(|inner| inner.percent_encode_path_segment()) } /// Configure this `Form` to percent-encode using the `attr-char` rules. pub fn percent_encode_attr_chars(self) -> Form { self.with_inner(|inner| inner.percent_encode_attr_chars()) } /// Configure this `Form` to skip percent-encoding pub fn percent_encode_noop(self) -> Form { self.with_inner(|inner| inner.percent_encode_noop()) } pub(crate) fn reader(self) -> Reader { Reader::new(self) } // If predictable, computes the length the request will have // The length should be preditable if only String and file fields have been added, // but not if a generic reader has been added; pub(crate) fn compute_length(&mut self) -> Option { self.inner.compute_length() } fn with_inner(self, func: F) -> Self where F: FnOnce(FormParts) -> FormParts, { Form { inner: func(self.inner), } } } impl fmt::Debug for Form { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt_fields("Form", f) } } impl Part { /// Makes a text parameter. pub fn text(value: T) -> Part where T: Into>, { let body = match value.into() { Cow::Borrowed(slice) => Body::from(slice), Cow::Owned(string) => Body::from(string), }; Part::new(body) } /// Makes a new parameter from arbitrary bytes. pub fn bytes(value: T) -> Part where T: Into>, { let body = match value.into() { Cow::Borrowed(slice) => Body::from(slice), Cow::Owned(vec) => Body::from(vec), }; Part::new(body) } /// Adds a generic reader. /// /// Does not set filename or mime. pub fn reader(value: T) -> Part { Part::new(Body::new(value)) } /// Adds a generic reader with known length. /// /// Does not set filename or mime. pub fn reader_with_length(value: T, length: u64) -> Part { Part::new(Body::sized(value, length)) } /// Makes a file parameter. /// /// # Errors /// /// Errors when the file cannot be opened. pub fn file>(path: T) -> io::Result { let path = path.as_ref(); let file_name = path .file_name() .map(|filename| filename.to_string_lossy().into_owned()); let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or(""); let mime = mime_guess::from_ext(ext).first_or_octet_stream(); let file = File::open(path)?; let field = Part::new(Body::from(file)).mime(mime); Ok(if let Some(file_name) = file_name { field.file_name(file_name) } else { field }) } fn new(value: Body) -> Part { Part { meta: PartMetadata::new(), value, } } /// Tries to set the mime of this part. pub fn mime_str(self, mime: &str) -> crate::Result { Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) } // Re-export when mime 0.4 is available, with split MediaType/MediaRange. fn mime(self, mime: Mime) -> Part { self.with_inner(move |inner| inner.mime(mime)) } /// Sets the filename, builder style. pub fn file_name(self, filename: T) -> Part where T: Into>, { self.with_inner(move |inner| inner.file_name(filename)) } /// Sets custom headers for the part. pub fn headers(self, headers: HeaderMap) -> Part { self.with_inner(move |inner| inner.headers(headers)) } fn with_inner(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata, { Part { meta: func(self.meta), value: self.value, } } } impl fmt::Debug for Part { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut dbg = f.debug_struct("Part"); dbg.field("value", &self.value); self.meta.fmt_fields(&mut dbg); dbg.finish() } } impl PartProps for Part { fn value_len(&self) -> Option { self.value.len() } fn metadata(&self) -> &PartMetadata { &self.meta } } pub(crate) struct Reader { form: Form, active_reader: Option>, } impl fmt::Debug for Reader { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Reader").field("form", &self.form).finish() } } impl Reader { fn new(form: Form) -> Reader { let mut reader = Reader { form, active_reader: None, }; reader.next_reader(); reader } fn next_reader(&mut self) { self.active_reader = if !self.form.inner.fields.is_empty() { // We need to move out of the vector here because we are consuming the field's reader let (name, field) = self.form.inner.fields.remove(0); let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary())); let header = Cursor::new({ // Try to use cached headers created by compute_length let mut h = if !self.form.inner.computed_headers.is_empty() { self.form.inner.computed_headers.remove(0) } else { self.form .inner .percent_encoding .encode_headers(&name, field.metadata()) }; h.extend_from_slice(b"\r\n\r\n"); h }); let reader = boundary .chain(header) .chain(field.value.into_reader()) .chain(Cursor::new("\r\n")); // According to https://tools.ietf.org/html/rfc2046#section-5.1.1 // the very last field has a special boundary if !self.form.inner.fields.is_empty() { Some(Box::new(reader)) } else { Some(Box::new(reader.chain(Cursor::new(format!( "--{}--\r\n", self.form.boundary() ))))) } } else { None } } } impl Read for Reader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut total_bytes_read = 0usize; let mut last_read_bytes; loop { match self.active_reader { Some(ref mut reader) => { last_read_bytes = reader.read(&mut buf[total_bytes_read..])?; total_bytes_read += last_read_bytes; if total_bytes_read == buf.len() { return Ok(total_bytes_read); } } None => return Ok(total_bytes_read), }; if last_read_bytes == 0 && !buf.is_empty() { self.next_reader(); } } } } #[cfg(test)] mod tests { use super::*; #[test] fn form_empty() { let mut output = Vec::new(); let mut form = Form::new(); let length = form.compute_length(); form.reader().read_to_end(&mut output).unwrap(); assert_eq!(output, b""); assert_eq!(length.unwrap(), 0); } #[test] fn read_to_end() { let mut output = Vec::new(); let mut form = Form::new() .part("reader1", Part::reader(std::io::empty())) .part("key1", Part::text("value1")) .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) .part("reader2", Part::reader(std::io::empty())) .part("key3", Part::text("value3").file_name("filename")); form.inner.boundary = "boundary".to_string(); let length = form.compute_length(); let expected = "--boundary\r\n\ Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ \r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ value1\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\ Content-Type: image/bmp\r\n\r\n\ value2\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\ \r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ value3\r\n--boundary--\r\n"; form.reader().read_to_end(&mut output).unwrap(); // These prints are for debug purposes in case the test fails println!( "START REAL\n{}\nEND REAL", std::str::from_utf8(&output).unwrap() ); println!("START EXPECTED\n{expected}\nEND EXPECTED"); assert_eq!(std::str::from_utf8(&output).unwrap(), expected); assert!(length.is_none()); } #[test] fn read_to_end_with_length() { let mut output = Vec::new(); let mut form = Form::new() .text("key1", "value1") .part("key2", Part::text("value2").mime(mime::IMAGE_BMP)) .part("key3", Part::text("value3").file_name("filename")); form.inner.boundary = "boundary".to_string(); let length = form.compute_length(); let expected = "--boundary\r\n\ Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ value1\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\ Content-Type: image/bmp\r\n\r\n\ value2\r\n\ --boundary\r\n\ Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ value3\r\n--boundary--\r\n"; form.reader().read_to_end(&mut output).unwrap(); // These prints are for debug purposes in case the test fails println!( "START REAL\n{}\nEND REAL", std::str::from_utf8(&output).unwrap() ); println!("START EXPECTED\n{expected}\nEND EXPECTED"); assert_eq!(std::str::from_utf8(&output).unwrap(), expected); assert_eq!(length.unwrap(), expected.len() as u64); } #[test] fn read_to_end_with_header() { let mut output = Vec::new(); let mut part = Part::text("value2").mime(mime::IMAGE_BMP); let mut headers = HeaderMap::new(); headers.insert("Hdr3", "/a/b/c".parse().unwrap()); part = part.headers(headers); let mut form = Form::new().part("key2", part); form.inner.boundary = "boundary".to_string(); let expected = "--boundary\r\n\ Content-Disposition: form-data; name=\"key2\"\r\n\ Content-Type: image/bmp\r\n\ hdr3: /a/b/c\r\n\ \r\n\ value2\r\n\ --boundary--\r\n"; form.reader().read_to_end(&mut output).unwrap(); // These prints are for debug purposes in case the test fails println!( "START REAL\n{}\nEND REAL", std::str::from_utf8(&output).unwrap() ); println!("START EXPECTED\n{expected}\nEND EXPECTED"); assert_eq!(std::str::from_utf8(&output).unwrap(), expected); } } reqwest-0.11.27/src/blocking/request.rs000064400000000000000000000776441046102023000161250ustar 00000000000000use std::convert::TryFrom; use std::fmt; use std::time::Duration; use http::{request::Parts, Request as HttpRequest, Version}; use serde::Serialize; #[cfg(feature = "json")] use serde_json; use serde_urlencoded; use super::body::{self, Body}; #[cfg(feature = "multipart")] use super::multipart; use super::Client; use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; use crate::{async_impl, Method, Url}; /// A request which can be executed with `Client::execute()`. pub struct Request { body: Option, inner: async_impl::Request, } /// A builder to construct the properties of a `Request`. /// /// To construct a `RequestBuilder`, refer to the `Client` documentation. #[derive(Debug)] #[must_use = "RequestBuilder does nothing until you 'send' it"] pub struct RequestBuilder { client: Client, request: crate::Result, } impl Request { /// Constructs a new request. #[inline] pub fn new(method: Method, url: Url) -> Self { Request { body: None, inner: async_impl::Request::new(method, url), } } /// Get the method. #[inline] pub fn method(&self) -> &Method { self.inner.method() } /// Get a mutable reference to the method. #[inline] pub fn method_mut(&mut self) -> &mut Method { self.inner.method_mut() } /// Get the url. #[inline] pub fn url(&self) -> &Url { self.inner.url() } /// Get a mutable reference to the url. #[inline] pub fn url_mut(&mut self) -> &mut Url { self.inner.url_mut() } /// Get the headers. #[inline] pub fn headers(&self) -> &HeaderMap { self.inner.headers() } /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { self.inner.headers_mut() } /// Get the http version. #[inline] pub fn version(&self) -> Version { self.inner.version() } /// Get a mutable reference to the http version. #[inline] pub fn version_mut(&mut self) -> &mut Version { self.inner.version_mut() } /// Get the body. #[inline] pub fn body(&self) -> Option<&Body> { self.body.as_ref() } /// Get a mutable reference to the body. #[inline] pub fn body_mut(&mut self) -> &mut Option { &mut self.body } /// Get the timeout. #[inline] pub fn timeout(&self) -> Option<&Duration> { self.inner.timeout() } /// Get a mutable reference to the timeout. #[inline] pub fn timeout_mut(&mut self) -> &mut Option { self.inner.timeout_mut() } /// Attempts to clone the `Request`. /// /// None is returned if a body is which can not be cloned. This can be because the body is a /// stream. pub fn try_clone(&self) -> Option { let body = if let Some(ref body) = self.body.as_ref() { if let Some(body) = body.try_clone() { Some(body) } else { return None; } } else { None }; let mut req = Request::new(self.method().clone(), self.url().clone()); *req.headers_mut() = self.headers().clone(); *req.version_mut() = self.version().clone(); req.body = body; Some(req) } pub(crate) fn into_async(self) -> (async_impl::Request, Option) { use crate::header::CONTENT_LENGTH; let mut req_async = self.inner; let body = self.body.and_then(|body| { let (tx, body, len) = body.into_async(); if let Some(len) = len { req_async.headers_mut().insert(CONTENT_LENGTH, len.into()); } *req_async.body_mut() = Some(body); tx }); (req_async, body) } } impl RequestBuilder { pub(crate) fn new(client: Client, request: crate::Result) -> RequestBuilder { let mut builder = RequestBuilder { client, request }; let auth = builder .request .as_mut() .ok() .and_then(|req| async_impl::request::extract_authority(req.url_mut())); if let Some((username, password)) = auth { builder.basic_auth(username, password) } else { builder } } /// Add a `Header` to this Request. /// /// ```rust /// use reqwest::header::USER_AGENT; /// /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let res = client.get("https://www.rust-lang.org") /// .header(USER_AGENT, "foo") /// .send()?; /// # Ok(()) /// # } /// ``` pub fn header(self, key: K, value: V) -> RequestBuilder where HeaderName: TryFrom, HeaderValue: TryFrom, >::Error: Into, >::Error: Into, { self.header_sensitive(key, value, false) } /// Add a `Header` to this Request with ability to define if header_value is sensitive. fn header_sensitive(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder where HeaderName: TryFrom, HeaderValue: TryFrom, >::Error: Into, >::Error: Into, { let mut error = None; if let Ok(ref mut req) = self.request { match >::try_from(key) { Ok(key) => match >::try_from(value) { Ok(mut value) => { value.set_sensitive(sensitive); req.headers_mut().append(key, value); } Err(e) => error = Some(crate::error::builder(e.into())), }, Err(e) => error = Some(crate::error::builder(e.into())), }; } if let Some(err) = error { self.request = Err(err); } self } /// Add a set of Headers to the existing ones on this Request. /// /// The headers will be merged in to any already set. /// /// ```rust /// use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE}; /// # use std::fs; /// /// fn construct_headers() -> HeaderMap { /// let mut headers = HeaderMap::new(); /// headers.insert(USER_AGENT, HeaderValue::from_static("reqwest")); /// headers.insert(CONTENT_TYPE, HeaderValue::from_static("image/png")); /// headers /// } /// /// # fn run() -> Result<(), Box> { /// let file = fs::File::open("much_beauty.png")?; /// let client = reqwest::blocking::Client::new(); /// let res = client.post("http://httpbin.org/post") /// .headers(construct_headers()) /// .body(file) /// .send()?; /// # Ok(()) /// # } /// ``` pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder { if let Ok(ref mut req) = self.request { crate::util::replace_headers(req.headers_mut(), headers); } self } /// Enable HTTP basic authentication. /// /// ```rust /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let resp = client.delete("http://httpbin.org/delete") /// .basic_auth("admin", Some("good password")) /// .send()?; /// # Ok(()) /// # } /// ``` pub fn basic_auth(self, username: U, password: Option

) -> RequestBuilder where U: fmt::Display, P: fmt::Display, { let header_value = crate::util::basic_auth(username, password); self.header_sensitive(crate::header::AUTHORIZATION, header_value, true) } /// Enable HTTP bearer authentication. /// /// ```rust /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let resp = client.delete("http://httpbin.org/delete") /// .bearer_auth("token") /// .send()?; /// # Ok(()) /// # } /// ``` pub fn bearer_auth(self, token: T) -> RequestBuilder where T: fmt::Display, { let header_value = format!("Bearer {token}"); self.header_sensitive(crate::header::AUTHORIZATION, &*header_value, true) } /// Set the request body. /// /// # Examples /// /// Using a string: /// /// ```rust /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let res = client.post("http://httpbin.org/post") /// .body("from a &str!") /// .send()?; /// # Ok(()) /// # } /// ``` /// /// Using a `File`: /// /// ```rust /// # fn run() -> Result<(), Box> { /// let file = std::fs::File::open("from_a_file.txt")?; /// let client = reqwest::blocking::Client::new(); /// let res = client.post("http://httpbin.org/post") /// .body(file) /// .send()?; /// # Ok(()) /// # } /// ``` /// /// Using arbitrary bytes: /// /// ```rust /// # use std::fs; /// # fn run() -> Result<(), Box> { /// // from bytes! /// let bytes: Vec = vec![1, 10, 100]; /// let client = reqwest::blocking::Client::new(); /// let res = client.post("http://httpbin.org/post") /// .body(bytes) /// .send()?; /// # Ok(()) /// # } /// ``` pub fn body>(mut self, body: T) -> RequestBuilder { if let Ok(ref mut req) = self.request { *req.body_mut() = Some(body.into()); } self } /// Enables a request timeout. /// /// The timeout is applied from when the request starts connecting until the /// response body has finished. It affects only this request and overrides /// the timeout configured using `ClientBuilder::timeout()`. pub fn timeout(mut self, timeout: Duration) -> RequestBuilder { if let Ok(ref mut req) = self.request { *req.timeout_mut() = Some(timeout); } self } /// Modify the query string of the URL. /// /// Modifies the URL of this request, adding the parameters provided. /// This method appends and does not overwrite. This means that it can /// be called multiple times and that existing query parameters are not /// overwritten if the same key is used. The key will simply show up /// twice in the query string. /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`. /// /// ```rust /// # use reqwest::Error; /// # /// # fn run() -> Result<(), Error> { /// let client = reqwest::blocking::Client::new(); /// let res = client.get("http://httpbin.org") /// .query(&[("lang", "rust")]) /// .send()?; /// # Ok(()) /// # } /// ``` /// /// # Note /// This method does not support serializing a single key-value /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such /// as `.query(&[("key", "val")])`. It's also possible to serialize structs /// and maps into a key-value pair. /// /// # Errors /// This method will fail if the object you provide cannot be serialized /// into a query string. pub fn query(mut self, query: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { let url = req.url_mut(); let mut pairs = url.query_pairs_mut(); let serializer = serde_urlencoded::Serializer::new(&mut pairs); if let Err(err) = query.serialize(serializer) { error = Some(crate::error::builder(err)); } } if let Ok(ref mut req) = self.request { if let Some("") = req.url().query() { req.url_mut().set_query(None); } } if let Some(err) = error { self.request = Err(err); } self } /// Set HTTP version pub fn version(mut self, version: Version) -> RequestBuilder { if let Ok(ref mut req) = self.request { *req.version_mut() = version; } self } /// Send a form body. /// /// Sets the body to the url encoded serialization of the passed value, /// and also sets the `Content-Type: application/x-www-form-urlencoded` /// header. /// /// ```rust /// # use reqwest::Error; /// # use std::collections::HashMap; /// # /// # fn run() -> Result<(), Error> { /// let mut params = HashMap::new(); /// params.insert("lang", "rust"); /// /// let client = reqwest::blocking::Client::new(); /// let res = client.post("http://httpbin.org") /// .form(¶ms) /// .send()?; /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// This method fails if the passed value cannot be serialized into /// url encoded format pub fn form(mut self, form: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { match serde_urlencoded::to_string(form) { Ok(body) => { req.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded"), ); *req.body_mut() = Some(body.into()); } Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { self.request = Err(err); } self } /// Send a JSON body. /// /// Sets the body to the JSON serialization of the passed value, and /// also sets the `Content-Type: application/json` header. /// /// # Optional /// /// This requires the optional `json` feature enabled. /// /// # Examples /// /// ```rust /// # use reqwest::Error; /// # use std::collections::HashMap; /// # /// # fn run() -> Result<(), Error> { /// let mut map = HashMap::new(); /// map.insert("lang", "rust"); /// /// let client = reqwest::blocking::Client::new(); /// let res = client.post("http://httpbin.org") /// .json(&map) /// .send()?; /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// Serialization can fail if `T`'s implementation of `Serialize` decides to /// fail, or if `T` contains a map with non-string keys. #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub fn json(mut self, json: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { match serde_json::to_vec(json) { Ok(body) => { if !req.headers().contains_key(CONTENT_TYPE) { req.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); } *req.body_mut() = Some(body.into()); } Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { self.request = Err(err); } self } /// Sends a multipart/form-data body. /// /// ``` /// # use reqwest::Error; /// /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let form = reqwest::blocking::multipart::Form::new() /// .text("key3", "value3") /// .file("file", "/path/to/field")?; /// /// let response = client.post("your url") /// .multipart(form) /// .send()?; /// # Ok(()) /// # } /// ``` /// /// See [`multipart`](multipart/) for more examples. #[cfg(feature = "multipart")] #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder { let mut builder = self.header( CONTENT_TYPE, format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(), ); if let Ok(ref mut req) = builder.request { *req.body_mut() = Some(match multipart.compute_length() { Some(length) => Body::sized(multipart.reader(), length), None => Body::new(multipart.reader()), }) } builder } /// Build a `Request`, which can be inspected, modified and executed with /// `Client::execute()`. pub fn build(self) -> crate::Result { self.request } /// Constructs the Request and sends it the target URL, returning a Response. /// /// # Errors /// /// This method fails if there was an error while sending request, /// redirect loop was detected or redirect limit was exhausted. pub fn send(self) -> crate::Result { self.client.execute(self.request?) } /// Attempts to clone the `RequestBuilder`. /// /// None is returned if a body is which can not be cloned. This can be because the body is a /// stream. /// /// # Examples /// /// With a static body /// /// ```rust /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let builder = client.post("http://httpbin.org/post") /// .body("from a &str!"); /// let clone = builder.try_clone(); /// assert!(clone.is_some()); /// # Ok(()) /// # } /// ``` /// /// Without a body /// /// ```rust /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let builder = client.get("http://httpbin.org/get"); /// let clone = builder.try_clone(); /// assert!(clone.is_some()); /// # Ok(()) /// # } /// ``` /// /// With a non-clonable body /// /// ```rust /// # fn run() -> Result<(), Box> { /// let client = reqwest::blocking::Client::new(); /// let builder = client.get("http://httpbin.org/get") /// .body(reqwest::blocking::Body::new(std::io::empty())); /// let clone = builder.try_clone(); /// assert!(clone.is_none()); /// # Ok(()) /// # } /// ``` pub fn try_clone(&self) -> Option { self.request .as_ref() .ok() .and_then(|req| req.try_clone()) .map(|req| RequestBuilder { client: self.client.clone(), request: Ok(req), }) } } impl TryFrom> for Request where T: Into, { type Error = crate::Error; fn try_from(req: HttpRequest) -> crate::Result { let (parts, body) = req.into_parts(); let Parts { method, uri, headers, .. } = parts; let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?; let mut inner = async_impl::Request::new(method, url); crate::util::replace_headers(inner.headers_mut(), headers); Ok(Request { body: Some(body.into()), inner, }) } } impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_request_fields(&mut f.debug_struct("Request"), self).finish() } } fn fmt_request_fields<'a, 'b>( f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request, ) -> &'a mut fmt::DebugStruct<'a, 'b> { f.field("method", req.method()) .field("url", req.url()) .field("headers", req.headers()) } #[cfg(test)] mod tests { use super::super::{body, Client}; use super::{HttpRequest, Request, Version}; use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST}; use crate::Method; use serde::Serialize; #[cfg(feature = "json")] use serde_json; use serde_urlencoded; use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; #[test] fn basic_get_request() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.get(some_url).build().unwrap(); assert_eq!(r.method(), &Method::GET); assert_eq!(r.url().as_str(), some_url); } #[test] fn basic_head_request() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.head(some_url).build().unwrap(); assert_eq!(r.method(), &Method::HEAD); assert_eq!(r.url().as_str(), some_url); } #[test] fn basic_post_request() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url).build().unwrap(); assert_eq!(r.method(), &Method::POST); assert_eq!(r.url().as_str(), some_url); } #[test] fn basic_put_request() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.put(some_url).build().unwrap(); assert_eq!(r.method(), &Method::PUT); assert_eq!(r.url().as_str(), some_url); } #[test] fn basic_patch_request() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.patch(some_url).build().unwrap(); assert_eq!(r.method(), &Method::PATCH); assert_eq!(r.url().as_str(), some_url); } #[test] fn basic_delete_request() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.delete(some_url).build().unwrap(); assert_eq!(r.method(), &Method::DELETE); assert_eq!(r.url().as_str(), some_url); } #[test] fn add_header() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let header = HeaderValue::from_static("google.com"); // Add a copy of the header to the request builder let r = r.header(HOST, header.clone()).build().unwrap(); // then check it was actually added assert_eq!(r.headers().get(HOST), Some(&header)); } #[test] fn add_headers() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let header = HeaderValue::from_static("google.com"); let mut headers = HeaderMap::new(); headers.insert(HOST, header); // Add a copy of the headers to the request builder let r = r.headers(headers.clone()).build().unwrap(); // then make sure they were added correctly assert_eq!(r.headers(), &headers); } #[test] fn add_headers_multi() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let header_json = HeaderValue::from_static("application/json"); let header_xml = HeaderValue::from_static("application/xml"); let mut headers = HeaderMap::new(); headers.append(ACCEPT, header_json); headers.append(ACCEPT, header_xml); // Add a copy of the headers to the request builder let r = r.headers(headers.clone()).build().unwrap(); // then make sure they were added correctly assert_eq!(r.headers(), &headers); let mut all_values = r.headers().get_all(ACCEPT).iter(); assert_eq!(all_values.next().unwrap(), &"application/json"); assert_eq!(all_values.next().unwrap(), &"application/xml"); assert_eq!(all_values.next(), None); } #[test] fn add_body() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let body = "Some interesting content"; let mut r = r.body(body).build().unwrap(); let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); assert_eq!(buf, body); } #[test] fn add_query_append() { let client = Client::new(); let some_url = "https://google.com/"; let mut r = client.get(some_url); r = r.query(&[("foo", "bar")]); r = r.query(&[("qux", 3)]); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=bar&qux=3")); } #[test] fn add_query_append_same() { let client = Client::new(); let some_url = "https://google.com/"; let mut r = client.get(some_url); r = r.query(&[("foo", "a"), ("foo", "b")]); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=a&foo=b")); } #[test] fn add_query_struct() { #[derive(Serialize)] struct Params { foo: String, qux: i32, } let client = Client::new(); let some_url = "https://google.com/"; let mut r = client.get(some_url); let params = Params { foo: "bar".into(), qux: 3, }; r = r.query(¶ms); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=bar&qux=3")); } #[test] fn add_query_map() { let mut params = BTreeMap::new(); params.insert("foo", "bar"); params.insert("qux", "three"); let client = Client::new(); let some_url = "https://google.com/"; let mut r = client.get(some_url); r = r.query(¶ms); let req = r.build().expect("request is valid"); assert_eq!(req.url().query(), Some("foo=bar&qux=three")); } #[test] fn add_form() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let mut form_data = HashMap::new(); form_data.insert("foo", "bar"); let mut r = r.form(&form_data).build().unwrap(); // Make sure the content type was set assert_eq!( r.headers().get(CONTENT_TYPE).unwrap(), &"application/x-www-form-urlencoded" ); let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); let body_should_be = serde_urlencoded::to_string(&form_data).unwrap(); assert_eq!(buf, body_should_be); } #[test] #[cfg(feature = "json")] fn add_json() { let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let mut json_data = HashMap::new(); json_data.insert("foo", "bar"); let mut r = r.json(&json_data).build().unwrap(); // Make sure the content type was set assert_eq!(r.headers().get(CONTENT_TYPE).unwrap(), &"application/json"); let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); let body_should_be = serde_json::to_string(&json_data).unwrap(); assert_eq!(buf, body_should_be); } #[test] #[cfg(feature = "json")] fn add_json_fail() { use serde::ser::Error as _; use serde::{Serialize, Serializer}; use std::error::Error as _; struct MyStruct; impl Serialize for MyStruct { fn serialize(&self, _serializer: S) -> Result where S: Serializer, { Err(S::Error::custom("nope")) } } let client = Client::new(); let some_url = "https://google.com/"; let r = client.post(some_url); let json_data = MyStruct; let err = r.json(&json_data).build().unwrap_err(); assert!(err.is_builder()); // well, duh ;) assert!(err.source().unwrap().is::()); } #[test] fn test_replace_headers() { use http::HeaderMap; let mut headers = HeaderMap::new(); headers.insert("foo", "bar".parse().unwrap()); headers.append("foo", "baz".parse().unwrap()); let client = Client::new(); let req = client .get("https://hyper.rs") .header("im-a", "keeper") .header("foo", "pop me") .headers(headers) .build() .expect("request build"); assert_eq!(req.headers()["im-a"], "keeper"); let foo = req.headers().get_all("foo").iter().collect::>(); assert_eq!(foo.len(), 2); assert_eq!(foo[0], "bar"); assert_eq!(foo[1], "baz"); } #[test] fn normalize_empty_query() { let client = Client::new(); let some_url = "https://google.com/"; let empty_query: &[(&str, &str)] = &[]; let req = client .get(some_url) .query(empty_query) .build() .expect("request build"); assert_eq!(req.url().query(), None); assert_eq!(req.url().as_str(), "https://google.com/"); } #[test] fn convert_url_authority_into_basic_auth() { let client = Client::new(); let some_url = "https://Aladdin:open sesame@localhost/"; let req = client.get(some_url).build().expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!( req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); } #[test] fn convert_from_http_request() { let http_request = HttpRequest::builder() .method("GET") .uri("http://localhost/") .header("User-Agent", "my-awesome-agent/1.0") .body("test test test") .unwrap(); let req: Request = Request::try_from(http_request).unwrap(); assert_eq!(req.body().is_none(), false); let test_data = b"test test test"; assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); let headers = req.headers(); assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0"); assert_eq!(req.method(), Method::GET); assert_eq!(req.url().as_str(), "http://localhost/"); } #[test] fn set_http_request_version() { let http_request = HttpRequest::builder() .method("GET") .uri("http://localhost/") .header("User-Agent", "my-awesome-agent/1.0") .version(Version::HTTP_11) .body("test test test") .unwrap(); let req: Request = Request::try_from(http_request).unwrap(); assert_eq!(req.body().is_none(), false); let test_data = b"test test test"; assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); let headers = req.headers(); assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0"); assert_eq!(req.method(), Method::GET); assert_eq!(req.url().as_str(), "http://localhost/"); assert_eq!(req.version(), Version::HTTP_11); } #[test] fn test_basic_auth_sensitive_header() { let client = Client::new(); let some_url = "https://localhost/"; let req = client .get(some_url) .basic_auth("Aladdin", Some("open sesame")) .build() .expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!( req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); assert_eq!(req.headers()["authorization"].is_sensitive(), true); } #[test] fn test_bearer_auth_sensitive_header() { let client = Client::new(); let some_url = "https://localhost/"; let req = client .get(some_url) .bearer_auth("Hold my bear") .build() .expect("request build"); assert_eq!(req.url().as_str(), "https://localhost/"); assert_eq!(req.headers()["authorization"], "Bearer Hold my bear"); assert_eq!(req.headers()["authorization"].is_sensitive(), true); } } reqwest-0.11.27/src/blocking/response.rs000064400000000000000000000316131046102023000162550ustar 00000000000000use std::fmt; use std::io::{self, Read}; use std::mem; use std::net::SocketAddr; use std::pin::Pin; use std::time::Duration; use bytes::Bytes; use http; use hyper::header::HeaderMap; #[cfg(feature = "json")] use serde::de::DeserializeOwned; use super::client::KeepCoreThreadAlive; use super::wait; #[cfg(feature = "cookies")] use crate::cookie; use crate::{async_impl, StatusCode, Url, Version}; /// A Response to a submitted `Request`. pub struct Response { inner: async_impl::Response, body: Option>>, timeout: Option, _thread_handle: KeepCoreThreadAlive, } impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.inner, f) } } impl Response { pub(crate) fn new( res: async_impl::Response, timeout: Option, thread: KeepCoreThreadAlive, ) -> Response { Response { inner: res, body: None, timeout, _thread_handle: thread, } } /// Get the `StatusCode` of this `Response`. /// /// # Examples /// /// Checking for general status class: /// /// ```rust /// # #[cfg(feature = "json")] /// # fn run() -> Result<(), Box> { /// let resp = reqwest::blocking::get("http://httpbin.org/get")?; /// if resp.status().is_success() { /// println!("success!"); /// } else if resp.status().is_server_error() { /// println!("server error!"); /// } else { /// println!("Something else happened. Status: {:?}", resp.status()); /// } /// # Ok(()) /// # } /// ``` /// /// Checking for specific status codes: /// /// ```rust /// use reqwest::blocking::Client; /// use reqwest::StatusCode; /// # fn run() -> Result<(), Box> { /// let client = Client::new(); /// /// let resp = client.post("http://httpbin.org/post") /// .body("possibly too large") /// .send()?; /// /// match resp.status() { /// StatusCode::OK => println!("success!"), /// StatusCode::PAYLOAD_TOO_LARGE => { /// println!("Request payload is too large!"); /// } /// s => println!("Received response status: {s:?}"), /// }; /// # Ok(()) /// # } /// ``` #[inline] pub fn status(&self) -> StatusCode { self.inner.status() } /// Get the `Headers` of this `Response`. /// /// # Example /// /// Saving an etag when caching a file: /// /// ``` /// use reqwest::blocking::Client; /// use reqwest::header::ETAG; /// /// # fn run() -> Result<(), Box> { /// let client = Client::new(); /// /// let mut resp = client.get("http://httpbin.org/cache").send()?; /// if resp.status().is_success() { /// if let Some(etag) = resp.headers().get(ETAG) { /// std::fs::write("etag", etag.as_bytes()); /// } /// let mut file = std::fs::File::create("file")?; /// resp.copy_to(&mut file)?; /// } /// # Ok(()) /// # } /// ``` #[inline] pub fn headers(&self) -> &HeaderMap { self.inner.headers() } /// Get a mutable reference to the `Headers` of this `Response`. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { self.inner.headers_mut() } /// Retrieve the cookies contained in the response. /// /// Note that invalid 'Set-Cookie' headers will be ignored. /// /// # Optional /// /// This requires the optional `cookies` feature to be enabled. #[cfg(feature = "cookies")] #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookies<'a>(&'a self) -> impl Iterator> + 'a { cookie::extract_response_cookies(self.headers()).filter_map(Result::ok) } /// Get the HTTP `Version` of this `Response`. #[inline] pub fn version(&self) -> Version { self.inner.version() } /// Get the final `Url` of this `Response`. /// /// # Example /// /// ```rust /// # fn run() -> Result<(), Box> { /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?; /// assert_eq!(resp.url().as_str(), "http://httpbin.org/get"); /// # Ok(()) /// # } /// ``` #[inline] pub fn url(&self) -> &Url { self.inner.url() } /// Get the remote address used to get this `Response`. /// /// # Example /// /// ```rust /// # fn run() -> Result<(), Box> { /// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?; /// println!("httpbin.org address: {:?}", resp.remote_addr()); /// # Ok(()) /// # } /// ``` pub fn remote_addr(&self) -> Option { self.inner.remote_addr() } /// Returns a reference to the associated extensions. pub fn extensions(&self) -> &http::Extensions { self.inner.extensions() } /// Returns a mutable reference to the associated extensions. pub fn extensions_mut(&mut self) -> &mut http::Extensions { self.inner.extensions_mut() } /// Get the content-length of the response, if it is known. /// /// Reasons it may not be known: /// /// - The server didn't send a `content-length` header. /// - The response is gzipped and automatically decoded (thus changing /// the actual decoded length). pub fn content_length(&self) -> Option { self.inner.content_length() } /// Try and deserialize the response body as JSON using `serde`. /// /// # Optional /// /// This requires the optional `json` feature enabled. /// /// # Examples /// /// ```rust /// # extern crate reqwest; /// # extern crate serde; /// # /// # use reqwest::Error; /// # use serde::Deserialize; /// # /// // This `derive` requires the `serde` dependency. /// #[derive(Deserialize)] /// struct Ip { /// origin: String, /// } /// /// # fn run() -> Result<(), Error> { /// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?; /// # Ok(()) /// # } /// # /// # fn main() { } /// ``` /// /// # Errors /// /// This method fails whenever the response body is not in JSON format /// or it cannot be properly deserialized to target type `T`. For more /// details please see [`serde_json::from_reader`]. /// /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub fn json(self) -> crate::Result { wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e { wait::Waited::TimedOut(e) => crate::error::decode(e), wait::Waited::Inner(e) => e, }) } /// Get the full response body as `Bytes`. /// /// # Example /// /// ``` /// # fn run() -> Result<(), Box> { /// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?; /// /// println!("bytes: {bytes:?}"); /// # Ok(()) /// # } /// ``` pub fn bytes(self) -> crate::Result { wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e { wait::Waited::TimedOut(e) => crate::error::decode(e), wait::Waited::Inner(e) => e, }) } /// Get the response text. /// /// This method decodes the response body with BOM sniffing /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. /// Encoding is determined from the `charset` parameter of `Content-Type` header, /// and defaults to `utf-8` if not presented. /// /// # Example /// /// ```rust /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?; /// # Ok(()) /// # } /// ``` pub fn text(self) -> crate::Result { self.text_with_charset("utf-8") } /// Get the response text given a specific encoding. /// /// This method decodes the response body with BOM sniffing /// and with malformed sequences replaced with the REPLACEMENT CHARACTER. /// You can provide a default encoding for decoding the raw message, while the /// `charset` parameter of `Content-Type` header is still prioritized. For more information /// about the possible encoding name, please go to [`encoding_rs`] docs. /// /// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages /// /// # Example /// /// ```rust /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let content = reqwest::blocking::get("http://httpbin.org/range/26")? /// .text_with_charset("utf-8")?; /// # Ok(()) /// # } /// ``` pub fn text_with_charset(self, default_encoding: &str) -> crate::Result { wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| { match e { wait::Waited::TimedOut(e) => crate::error::decode(e), wait::Waited::Inner(e) => e, } }) } /// Copy the response body into a writer. /// /// This function internally uses [`std::io::copy`] and hence will continuously read data from /// the body and then write it into writer in a streaming fashion until EOF is met. /// /// On success, the total number of bytes that were copied to `writer` is returned. /// /// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html /// /// # Example /// /// ```rust /// # fn run() -> Result<(), Box> { /// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?; /// let mut buf: Vec = vec![]; /// resp.copy_to(&mut buf)?; /// assert_eq!(b"abcde", buf.as_slice()); /// # Ok(()) /// # } /// ``` pub fn copy_to(&mut self, w: &mut W) -> crate::Result where W: io::Write, { io::copy(self, w).map_err(crate::error::decode_io) } /// Turn a response into an error if the server returned an error. /// /// # Example /// /// ```rust,no_run /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let res = reqwest::blocking::get("http://httpbin.org/status/400")? /// .error_for_status(); /// if let Err(err) = res { /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST)); /// } /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn error_for_status(self) -> crate::Result { let Response { body, inner, timeout, _thread_handle, } = self; inner.error_for_status().map(move |inner| Response { inner, body, timeout, _thread_handle, }) } /// Turn a reference to a response into an error if the server returned an error. /// /// # Example /// /// ```rust,no_run /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let res = reqwest::blocking::get("http://httpbin.org/status/400")?; /// let res = res.error_for_status_ref(); /// if let Err(err) = res { /// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST)); /// } /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn error_for_status_ref(&self) -> crate::Result<&Self> { self.inner.error_for_status_ref().and_then(|_| Ok(self)) } // private fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> { use futures_util::TryStreamExt; if self.body.is_none() { let body = mem::replace(self.inner.body_mut(), async_impl::Decoder::empty()); let body = body.map_err(crate::error::into_io).into_async_read(); self.body = Some(Box::pin(body)); } self.body.as_mut().expect("body was init").as_mut() } } impl Read for Response { fn read(&mut self, buf: &mut [u8]) -> io::Result { use futures_util::io::AsyncReadExt; let timeout = self.timeout; wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e { wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(), wait::Waited::Inner(e) => e, }) } } impl> From> for Response { fn from(r: http::Response) -> Response { let response = async_impl::Response::from(r); Response::new(response, None, KeepCoreThreadAlive::empty()) } } reqwest-0.11.27/src/blocking/wait.rs000064400000000000000000000040561046102023000153640ustar 00000000000000use std::future::Future; use std::sync::Arc; use std::task::{Context, Poll}; use std::thread::{self, Thread}; use std::time::Duration; use tokio::time::Instant; pub(crate) fn timeout(fut: F, timeout: Option) -> Result> where F: Future>, { enter(); let deadline = timeout.map(|d| { log::trace!("wait at most {d:?}"); Instant::now() + d }); let thread = ThreadWaker(thread::current()); // Arc shouldn't be necessary, since `Thread` is reference counted internally, // but let's just stay safe for now. let waker = futures_util::task::waker(Arc::new(thread)); let mut cx = Context::from_waker(&waker); futures_util::pin_mut!(fut); loop { match fut.as_mut().poll(&mut cx) { Poll::Ready(Ok(val)) => return Ok(val), Poll::Ready(Err(err)) => return Err(Waited::Inner(err)), Poll::Pending => (), // fallthrough } if let Some(deadline) = deadline { let now = Instant::now(); if now >= deadline { log::trace!("wait timeout exceeded"); return Err(Waited::TimedOut(crate::error::TimedOut)); } log::trace!( "({:?}) park timeout {:?}", thread::current().id(), deadline - now ); thread::park_timeout(deadline - now); } else { log::trace!("({:?}) park without timeout", thread::current().id()); thread::park(); } } } #[derive(Debug)] pub(crate) enum Waited { TimedOut(crate::error::TimedOut), Inner(E), } struct ThreadWaker(Thread); impl futures_util::task::ArcWake for ThreadWaker { fn wake_by_ref(arc_self: &Arc) { arc_self.0.unpark(); } } fn enter() { // Check we aren't already in a runtime #[cfg(debug_assertions)] { let _enter = tokio::runtime::Builder::new_current_thread() .build() .expect("build shell runtime") .enter(); } } reqwest-0.11.27/src/connect.rs000064400000000000000000001213641046102023000142630ustar 00000000000000#[cfg(feature = "__tls")] use http::header::HeaderValue; use http::uri::{Authority, Scheme}; use http::Uri; use hyper::client::connect::{Connected, Connection}; use hyper::service::Service; #[cfg(feature = "native-tls-crate")] use native_tls_crate::{TlsConnector, TlsConnectorBuilder}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use pin_project_lite::pin_project; use std::future::Future; use std::io::{self, IoSlice}; use std::net::IpAddr; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; #[cfg(feature = "default-tls")] use self::native_tls_conn::NativeTlsConn; #[cfg(feature = "__rustls")] use self::rustls_tls_conn::RustlsTlsConn; use crate::dns::DynResolver; use crate::error::BoxError; use crate::proxy::{Proxy, ProxyScheme}; pub(crate) type HttpConnector = hyper::client::HttpConnector; #[derive(Clone)] pub(crate) struct Connector { inner: Inner, proxies: Arc>, verbose: verbose::Wrapper, timeout: Option, #[cfg(feature = "__tls")] nodelay: bool, #[cfg(feature = "__tls")] tls_info: bool, #[cfg(feature = "__tls")] user_agent: Option, } #[derive(Clone)] enum Inner { #[cfg(not(feature = "__tls"))] Http(HttpConnector), #[cfg(feature = "default-tls")] DefaultTls(HttpConnector, TlsConnector), #[cfg(feature = "__rustls")] RustlsTls { http: HttpConnector, tls: Arc, tls_proxy: Arc, }, } impl Connector { #[cfg(not(feature = "__tls"))] pub(crate) fn new( mut http: HttpConnector, proxies: Arc>, local_addr: T, nodelay: bool, ) -> Connector where T: Into>, { http.set_local_address(local_addr.into()); http.set_nodelay(nodelay); Connector { inner: Inner::Http(http), verbose: verbose::OFF, proxies, timeout: None, } } #[cfg(feature = "default-tls")] pub(crate) fn new_default_tls( http: HttpConnector, tls: TlsConnectorBuilder, proxies: Arc>, user_agent: Option, local_addr: T, nodelay: bool, tls_info: bool, ) -> crate::Result where T: Into>, { let tls = tls.build().map_err(crate::error::builder)?; Ok(Self::from_built_default_tls( http, tls, proxies, user_agent, local_addr, nodelay, tls_info, )) } #[cfg(feature = "default-tls")] pub(crate) fn from_built_default_tls( mut http: HttpConnector, tls: TlsConnector, proxies: Arc>, user_agent: Option, local_addr: T, nodelay: bool, tls_info: bool, ) -> Connector where T: Into>, { http.set_local_address(local_addr.into()); http.set_nodelay(nodelay); http.enforce_http(false); Connector { inner: Inner::DefaultTls(http, tls), proxies, verbose: verbose::OFF, timeout: None, nodelay, tls_info, user_agent, } } #[cfg(feature = "__rustls")] pub(crate) fn new_rustls_tls( mut http: HttpConnector, tls: rustls::ClientConfig, proxies: Arc>, user_agent: Option, local_addr: T, nodelay: bool, tls_info: bool, ) -> Connector where T: Into>, { http.set_local_address(local_addr.into()); http.set_nodelay(nodelay); http.enforce_http(false); let (tls, tls_proxy) = if proxies.is_empty() { let tls = Arc::new(tls); (tls.clone(), tls) } else { let mut tls_proxy = tls.clone(); tls_proxy.alpn_protocols.clear(); (Arc::new(tls), Arc::new(tls_proxy)) }; Connector { inner: Inner::RustlsTls { http, tls, tls_proxy, }, proxies, verbose: verbose::OFF, timeout: None, nodelay, tls_info, user_agent, } } pub(crate) fn set_timeout(&mut self, timeout: Option) { self.timeout = timeout; } pub(crate) fn set_verbose(&mut self, enabled: bool) { self.verbose.0 = enabled; } #[cfg(feature = "socks")] async fn connect_socks(&self, dst: Uri, proxy: ProxyScheme) -> Result { let dns = match proxy { ProxyScheme::Socks5 { remote_dns: false, .. } => socks::DnsResolve::Local, ProxyScheme::Socks5 { remote_dns: true, .. } => socks::DnsResolve::Proxy, ProxyScheme::Http { .. } | ProxyScheme::Https { .. } => { unreachable!("connect_socks is only called for socks proxies"); } }; match &self.inner { #[cfg(feature = "default-tls")] Inner::DefaultTls(_http, tls) => { if dst.scheme() == Some(&Scheme::HTTPS) { let host = dst.host().ok_or("no host in url")?.to_string(); let conn = socks::connect(proxy, dst, dns).await?; let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone()); let io = tls_connector.connect(&host, conn).await?; return Ok(Conn { inner: self.verbose.wrap(NativeTlsConn { inner: io }), is_proxy: false, tls_info: self.tls_info, }); } } #[cfg(feature = "__rustls")] Inner::RustlsTls { tls_proxy, .. } => { if dst.scheme() == Some(&Scheme::HTTPS) { use std::convert::TryFrom; use tokio_rustls::TlsConnector as RustlsConnector; let tls = tls_proxy.clone(); let host = dst.host().ok_or("no host in url")?.to_string(); let conn = socks::connect(proxy, dst, dns).await?; let server_name = rustls::ServerName::try_from(host.as_str()) .map_err(|_| "Invalid Server Name")?; let io = RustlsConnector::from(tls) .connect(server_name, conn) .await?; return Ok(Conn { inner: self.verbose.wrap(RustlsTlsConn { inner: io }), is_proxy: false, tls_info: false, }); } } #[cfg(not(feature = "__tls"))] Inner::Http(_) => (), } socks::connect(proxy, dst, dns).await.map(|tcp| Conn { inner: self.verbose.wrap(tcp), is_proxy: false, tls_info: false, }) } async fn connect_with_maybe_proxy(self, dst: Uri, is_proxy: bool) -> Result { match self.inner { #[cfg(not(feature = "__tls"))] Inner::Http(mut http) => { let io = http.call(dst).await?; Ok(Conn { inner: self.verbose.wrap(io), is_proxy, tls_info: false, }) } #[cfg(feature = "default-tls")] Inner::DefaultTls(http, tls) => { let mut http = http.clone(); // Disable Nagle's algorithm for TLS handshake // // https://www.openssl.org/docs/man1.1.1/man3/SSL_connect.html#NOTES if !self.nodelay && (dst.scheme() == Some(&Scheme::HTTPS)) { http.set_nodelay(true); } let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone()); let mut http = hyper_tls::HttpsConnector::from((http, tls_connector)); let io = http.call(dst).await?; if let hyper_tls::MaybeHttpsStream::Https(stream) = io { if !self.nodelay { stream.get_ref().get_ref().get_ref().set_nodelay(false)?; } Ok(Conn { inner: self.verbose.wrap(NativeTlsConn { inner: stream }), is_proxy, tls_info: self.tls_info, }) } else { Ok(Conn { inner: self.verbose.wrap(io), is_proxy, tls_info: false, }) } } #[cfg(feature = "__rustls")] Inner::RustlsTls { http, tls, .. } => { let mut http = http.clone(); // Disable Nagle's algorithm for TLS handshake // // https://www.openssl.org/docs/man1.1.1/man3/SSL_connect.html#NOTES if !self.nodelay && (dst.scheme() == Some(&Scheme::HTTPS)) { http.set_nodelay(true); } let mut http = hyper_rustls::HttpsConnector::from((http, tls.clone())); let io = http.call(dst).await?; if let hyper_rustls::MaybeHttpsStream::Https(stream) = io { if !self.nodelay { let (io, _) = stream.get_ref(); io.set_nodelay(false)?; } Ok(Conn { inner: self.verbose.wrap(RustlsTlsConn { inner: stream }), is_proxy, tls_info: self.tls_info, }) } else { Ok(Conn { inner: self.verbose.wrap(io), is_proxy, tls_info: false, }) } } } } async fn connect_via_proxy( self, dst: Uri, proxy_scheme: ProxyScheme, ) -> Result { log::debug!("proxy({proxy_scheme:?}) intercepts '{dst:?}'"); let (proxy_dst, _auth) = match proxy_scheme { ProxyScheme::Http { host, auth } => (into_uri(Scheme::HTTP, host), auth), ProxyScheme::Https { host, auth } => (into_uri(Scheme::HTTPS, host), auth), #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => return self.connect_socks(dst, proxy_scheme).await, }; #[cfg(feature = "__tls")] let auth = _auth; match &self.inner { #[cfg(feature = "default-tls")] Inner::DefaultTls(http, tls) => { if dst.scheme() == Some(&Scheme::HTTPS) { let host = dst.host().to_owned(); let port = dst.port().map(|p| p.as_u16()).unwrap_or(443); let http = http.clone(); let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone()); let mut http = hyper_tls::HttpsConnector::from((http, tls_connector)); let conn = http.call(proxy_dst).await?; log::trace!("tunneling HTTPS over proxy"); let tunneled = tunnel( conn, host.ok_or("no host in url")?.to_string(), port, self.user_agent.clone(), auth, ) .await?; let tls_connector = tokio_native_tls::TlsConnector::from(tls.clone()); let io = tls_connector .connect(host.ok_or("no host in url")?, tunneled) .await?; return Ok(Conn { inner: self.verbose.wrap(NativeTlsConn { inner: io }), is_proxy: false, tls_info: false, }); } } #[cfg(feature = "__rustls")] Inner::RustlsTls { http, tls, tls_proxy, } => { if dst.scheme() == Some(&Scheme::HTTPS) { use rustls::ServerName; use std::convert::TryFrom; use tokio_rustls::TlsConnector as RustlsConnector; let host = dst.host().ok_or("no host in url")?.to_string(); let port = dst.port().map(|r| r.as_u16()).unwrap_or(443); let http = http.clone(); let mut http = hyper_rustls::HttpsConnector::from((http, tls_proxy.clone())); let tls = tls.clone(); let conn = http.call(proxy_dst).await?; log::trace!("tunneling HTTPS over proxy"); let maybe_server_name = ServerName::try_from(host.as_str()).map_err(|_| "Invalid Server Name"); let tunneled = tunnel(conn, host, port, self.user_agent.clone(), auth).await?; let server_name = maybe_server_name?; let io = RustlsConnector::from(tls) .connect(server_name, tunneled) .await?; return Ok(Conn { inner: self.verbose.wrap(RustlsTlsConn { inner: io }), is_proxy: false, tls_info: false, }); } } #[cfg(not(feature = "__tls"))] Inner::Http(_) => (), } self.connect_with_maybe_proxy(proxy_dst, true).await } pub fn set_keepalive(&mut self, dur: Option) { match &mut self.inner { #[cfg(feature = "default-tls")] Inner::DefaultTls(http, _tls) => http.set_keepalive(dur), #[cfg(feature = "__rustls")] Inner::RustlsTls { http, .. } => http.set_keepalive(dur), #[cfg(not(feature = "__tls"))] Inner::Http(http) => http.set_keepalive(dur), } } } fn into_uri(scheme: Scheme, host: Authority) -> Uri { // TODO: Should the `http` crate get `From<(Scheme, Authority)> for Uri`? http::Uri::builder() .scheme(scheme) .authority(host) .path_and_query(http::uri::PathAndQuery::from_static("/")) .build() .expect("scheme and authority is valid Uri") } async fn with_timeout(f: F, timeout: Option) -> Result where F: Future>, { if let Some(to) = timeout { match tokio::time::timeout(to, f).await { Err(_elapsed) => Err(Box::new(crate::error::TimedOut) as BoxError), Ok(Ok(try_res)) => Ok(try_res), Ok(Err(e)) => Err(e), } } else { f.await } } impl Service for Connector { type Response = Conn; type Error = BoxError; type Future = Connecting; fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, dst: Uri) -> Self::Future { log::debug!("starting new connection: {dst:?}"); let timeout = self.timeout; for prox in self.proxies.iter() { if let Some(proxy_scheme) = prox.intercept(&dst) { return Box::pin(with_timeout( self.clone().connect_via_proxy(dst, proxy_scheme), timeout, )); } } Box::pin(with_timeout( self.clone().connect_with_maybe_proxy(dst, false), timeout, )) } } #[cfg(feature = "__tls")] trait TlsInfoFactory { fn tls_info(&self) -> Option; } #[cfg(feature = "__tls")] impl TlsInfoFactory for tokio::net::TcpStream { fn tls_info(&self) -> Option { None } } #[cfg(feature = "default-tls")] impl TlsInfoFactory for hyper_tls::MaybeHttpsStream { fn tls_info(&self) -> Option { match self { hyper_tls::MaybeHttpsStream::Https(tls) => tls.tls_info(), hyper_tls::MaybeHttpsStream::Http(_) => None, } } } #[cfg(feature = "default-tls")] impl TlsInfoFactory for hyper_tls::TlsStream> { fn tls_info(&self) -> Option { let peer_certificate = self .get_ref() .peer_certificate() .ok() .flatten() .and_then(|c| c.to_der().ok()); Some(crate::tls::TlsInfo { peer_certificate }) } } #[cfg(feature = "default-tls")] impl TlsInfoFactory for tokio_native_tls::TlsStream { fn tls_info(&self) -> Option { let peer_certificate = self .get_ref() .peer_certificate() .ok() .flatten() .and_then(|c| c.to_der().ok()); Some(crate::tls::TlsInfo { peer_certificate }) } } #[cfg(feature = "__rustls")] impl TlsInfoFactory for hyper_rustls::MaybeHttpsStream { fn tls_info(&self) -> Option { match self { hyper_rustls::MaybeHttpsStream::Https(tls) => tls.tls_info(), hyper_rustls::MaybeHttpsStream::Http(_) => None, } } } #[cfg(feature = "__rustls")] impl TlsInfoFactory for tokio_rustls::TlsStream { fn tls_info(&self) -> Option { let peer_certificate = self .get_ref() .1 .peer_certificates() .and_then(|certs| certs.first()) .map(|c| c.0.clone()); Some(crate::tls::TlsInfo { peer_certificate }) } } #[cfg(feature = "__rustls")] impl TlsInfoFactory for tokio_rustls::client::TlsStream> { fn tls_info(&self) -> Option { let peer_certificate = self .get_ref() .1 .peer_certificates() .and_then(|certs| certs.first()) .map(|c| c.0.clone()); Some(crate::tls::TlsInfo { peer_certificate }) } } #[cfg(feature = "__rustls")] impl TlsInfoFactory for tokio_rustls::client::TlsStream { fn tls_info(&self) -> Option { let peer_certificate = self .get_ref() .1 .peer_certificates() .and_then(|certs| certs.first()) .map(|c| c.0.clone()); Some(crate::tls::TlsInfo { peer_certificate }) } } pub(crate) trait AsyncConn: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static { } impl AsyncConn for T {} #[cfg(feature = "__tls")] trait AsyncConnWithInfo: AsyncConn + TlsInfoFactory {} #[cfg(not(feature = "__tls"))] trait AsyncConnWithInfo: AsyncConn {} #[cfg(feature = "__tls")] impl AsyncConnWithInfo for T {} #[cfg(not(feature = "__tls"))] impl AsyncConnWithInfo for T {} type BoxConn = Box; pin_project! { /// Note: the `is_proxy` member means *is plain text HTTP proxy*. /// This tells hyper whether the URI should be written in /// * origin-form (`GET /just/a/path HTTP/1.1`), when `is_proxy == false`, or /// * absolute-form (`GET http://foo.bar/and/a/path HTTP/1.1`), otherwise. pub(crate) struct Conn { #[pin] inner: BoxConn, is_proxy: bool, // Only needed for __tls, but #[cfg()] on fields breaks pin_project! tls_info: bool, } } impl Connection for Conn { fn connected(&self) -> Connected { let connected = self.inner.connected().proxy(self.is_proxy); #[cfg(feature = "__tls")] if self.tls_info { if let Some(tls_info) = self.inner.tls_info() { connected.extra(tls_info) } else { connected } } else { connected } #[cfg(not(feature = "__tls"))] connected } } impl AsyncRead for Conn { fn poll_read( self: Pin<&mut Self>, cx: &mut Context, buf: &mut ReadBuf<'_>, ) -> Poll> { let this = self.project(); AsyncRead::poll_read(this.inner, cx, buf) } } impl AsyncWrite for Conn { fn poll_write( self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { let this = self.project(); AsyncWrite::poll_write(this.inner, cx, buf) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { let this = self.project(); AsyncWrite::poll_write_vectored(this.inner, cx, bufs) } fn is_write_vectored(&self) -> bool { self.inner.is_write_vectored() } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.project(); AsyncWrite::poll_flush(this.inner, cx) } fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { let this = self.project(); AsyncWrite::poll_shutdown(this.inner, cx) } } pub(crate) type Connecting = Pin> + Send>>; #[cfg(feature = "__tls")] async fn tunnel( mut conn: T, host: String, port: u16, user_agent: Option, auth: Option, ) -> Result where T: AsyncRead + AsyncWrite + Unpin, { use tokio::io::{AsyncReadExt, AsyncWriteExt}; let mut buf = format!( "\ CONNECT {host}:{port} HTTP/1.1\r\n\ Host: {host}:{port}\r\n\ " ) .into_bytes(); // user-agent if let Some(user_agent) = user_agent { buf.extend_from_slice(b"User-Agent: "); buf.extend_from_slice(user_agent.as_bytes()); buf.extend_from_slice(b"\r\n"); } // proxy-authorization if let Some(value) = auth { log::debug!("tunnel to {host}:{port} using basic auth"); buf.extend_from_slice(b"Proxy-Authorization: "); buf.extend_from_slice(value.as_bytes()); buf.extend_from_slice(b"\r\n"); } // headers end buf.extend_from_slice(b"\r\n"); conn.write_all(&buf).await?; let mut buf = [0; 8192]; let mut pos = 0; loop { let n = conn.read(&mut buf[pos..]).await?; if n == 0 { return Err(tunnel_eof()); } pos += n; let recvd = &buf[..pos]; if recvd.starts_with(b"HTTP/1.1 200") || recvd.starts_with(b"HTTP/1.0 200") { if recvd.ends_with(b"\r\n\r\n") { return Ok(conn); } if pos == buf.len() { return Err("proxy headers too long for tunnel".into()); } // else read more } else if recvd.starts_with(b"HTTP/1.1 407") { return Err("proxy authentication required".into()); } else { return Err("unsuccessful tunnel".into()); } } } #[cfg(feature = "__tls")] fn tunnel_eof() -> BoxError { "unexpected eof while tunneling".into() } #[cfg(feature = "default-tls")] mod native_tls_conn { use super::TlsInfoFactory; use hyper::client::connect::{Connected, Connection}; use pin_project_lite::pin_project; use std::{ io::{self, IoSlice}, pin::Pin, task::{Context, Poll}, }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_native_tls::TlsStream; pin_project! { pub(super) struct NativeTlsConn { #[pin] pub(super) inner: TlsStream, } } impl Connection for NativeTlsConn { #[cfg(feature = "native-tls-alpn")] fn connected(&self) -> Connected { match self.inner.get_ref().negotiated_alpn().ok() { Some(Some(alpn_protocol)) if alpn_protocol == b"h2" => self .inner .get_ref() .get_ref() .get_ref() .connected() .negotiated_h2(), _ => self.inner.get_ref().get_ref().get_ref().connected(), } } #[cfg(not(feature = "native-tls-alpn"))] fn connected(&self) -> Connected { self.inner.get_ref().get_ref().get_ref().connected() } } impl AsyncRead for NativeTlsConn { fn poll_read( self: Pin<&mut Self>, cx: &mut Context, buf: &mut ReadBuf<'_>, ) -> Poll> { let this = self.project(); AsyncRead::poll_read(this.inner, cx, buf) } } impl AsyncWrite for NativeTlsConn { fn poll_write( self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { let this = self.project(); AsyncWrite::poll_write(this.inner, cx, buf) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { let this = self.project(); AsyncWrite::poll_write_vectored(this.inner, cx, bufs) } fn is_write_vectored(&self) -> bool { self.inner.is_write_vectored() } fn poll_flush( self: Pin<&mut Self>, cx: &mut Context, ) -> Poll> { let this = self.project(); AsyncWrite::poll_flush(this.inner, cx) } fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context, ) -> Poll> { let this = self.project(); AsyncWrite::poll_shutdown(this.inner, cx) } } impl TlsInfoFactory for NativeTlsConn { fn tls_info(&self) -> Option { self.inner.tls_info() } } impl TlsInfoFactory for NativeTlsConn> { fn tls_info(&self) -> Option { self.inner.tls_info() } } } #[cfg(feature = "__rustls")] mod rustls_tls_conn { use super::TlsInfoFactory; use hyper::client::connect::{Connected, Connection}; use pin_project_lite::pin_project; use std::{ io::{self, IoSlice}, pin::Pin, task::{Context, Poll}, }; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio_rustls::client::TlsStream; pin_project! { pub(super) struct RustlsTlsConn { #[pin] pub(super) inner: TlsStream, } } impl Connection for RustlsTlsConn { fn connected(&self) -> Connected { if self.inner.get_ref().1.alpn_protocol() == Some(b"h2") { self.inner.get_ref().0.connected().negotiated_h2() } else { self.inner.get_ref().0.connected() } } } impl AsyncRead for RustlsTlsConn { fn poll_read( self: Pin<&mut Self>, cx: &mut Context, buf: &mut ReadBuf<'_>, ) -> Poll> { let this = self.project(); AsyncRead::poll_read(this.inner, cx, buf) } } impl AsyncWrite for RustlsTlsConn { fn poll_write( self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { let this = self.project(); AsyncWrite::poll_write(this.inner, cx, buf) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { let this = self.project(); AsyncWrite::poll_write_vectored(this.inner, cx, bufs) } fn is_write_vectored(&self) -> bool { self.inner.is_write_vectored() } fn poll_flush( self: Pin<&mut Self>, cx: &mut Context, ) -> Poll> { let this = self.project(); AsyncWrite::poll_flush(this.inner, cx) } fn poll_shutdown( self: Pin<&mut Self>, cx: &mut Context, ) -> Poll> { let this = self.project(); AsyncWrite::poll_shutdown(this.inner, cx) } } impl TlsInfoFactory for RustlsTlsConn { fn tls_info(&self) -> Option { self.inner.tls_info() } } impl TlsInfoFactory for RustlsTlsConn> { fn tls_info(&self) -> Option { self.inner.tls_info() } } } #[cfg(feature = "socks")] mod socks { use std::io; use std::net::ToSocketAddrs; use http::Uri; use tokio::net::TcpStream; use tokio_socks::tcp::Socks5Stream; use super::{BoxError, Scheme}; use crate::proxy::ProxyScheme; pub(super) enum DnsResolve { Local, Proxy, } pub(super) async fn connect( proxy: ProxyScheme, dst: Uri, dns: DnsResolve, ) -> Result { let https = dst.scheme() == Some(&Scheme::HTTPS); let original_host = dst .host() .ok_or(io::Error::new(io::ErrorKind::Other, "no host in url"))?; let mut host = original_host.to_owned(); let port = match dst.port() { Some(p) => p.as_u16(), None if https => 443u16, _ => 80u16, }; if let DnsResolve::Local = dns { let maybe_new_target = (host.as_str(), port).to_socket_addrs()?.next(); if let Some(new_target) = maybe_new_target { host = new_target.ip().to_string(); } } let (socket_addr, auth) = match proxy { ProxyScheme::Socks5 { addr, auth, .. } => (addr, auth), _ => unreachable!(), }; // Get a Tokio TcpStream let stream = if let Some((username, password)) = auth { Socks5Stream::connect_with_password( socket_addr, (host.as_str(), port), &username, &password, ) .await .map_err(|e| format!("socks connect error: {e}"))? } else { Socks5Stream::connect(socket_addr, (host.as_str(), port)) .await .map_err(|e| format!("socks connect error: {e}"))? }; Ok(stream.into_inner()) } } mod verbose { use hyper::client::connect::{Connected, Connection}; use std::cmp::min; use std::fmt; use std::io::{self, IoSlice}; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; pub(super) const OFF: Wrapper = Wrapper(false); #[derive(Clone, Copy)] pub(super) struct Wrapper(pub(super) bool); impl Wrapper { pub(super) fn wrap(&self, conn: T) -> super::BoxConn { if self.0 && log::log_enabled!(log::Level::Trace) { Box::new(Verbose { // truncate is fine id: crate::util::fast_random() as u32, inner: conn, }) } else { Box::new(conn) } } } struct Verbose { id: u32, inner: T, } impl Connection for Verbose { fn connected(&self) -> Connected { self.inner.connected() } } impl AsyncRead for Verbose { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context, buf: &mut ReadBuf<'_>, ) -> Poll> { match Pin::new(&mut self.inner).poll_read(cx, buf) { Poll::Ready(Ok(())) => { log::trace!("{:08x} read: {:?}", self.id, Escape(buf.filled())); Poll::Ready(Ok(())) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Pending => Poll::Pending, } } } impl AsyncWrite for Verbose { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8], ) -> Poll> { match Pin::new(&mut self.inner).poll_write(cx, buf) { Poll::Ready(Ok(n)) => { log::trace!("{:08x} write: {:?}", self.id, Escape(&buf[..n])); Poll::Ready(Ok(n)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Pending => Poll::Pending, } } fn poll_write_vectored( mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { match Pin::new(&mut self.inner).poll_write_vectored(cx, bufs) { Poll::Ready(Ok(nwritten)) => { log::trace!( "{:08x} write (vectored): {:?}", self.id, Vectored { bufs, nwritten } ); Poll::Ready(Ok(nwritten)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), Poll::Pending => Poll::Pending, } } fn is_write_vectored(&self) -> bool { self.inner.is_write_vectored() } fn poll_flush( mut self: Pin<&mut Self>, cx: &mut Context, ) -> Poll> { Pin::new(&mut self.inner).poll_flush(cx) } fn poll_shutdown( mut self: Pin<&mut Self>, cx: &mut Context, ) -> Poll> { Pin::new(&mut self.inner).poll_shutdown(cx) } } #[cfg(feature = "__tls")] impl super::TlsInfoFactory for Verbose { fn tls_info(&self) -> Option { self.inner.tls_info() } } struct Escape<'a>(&'a [u8]); impl fmt::Debug for Escape<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "b\"")?; for &c in self.0 { // https://doc.rust-lang.org/reference.html#byte-escapes if c == b'\n' { write!(f, "\\n")?; } else if c == b'\r' { write!(f, "\\r")?; } else if c == b'\t' { write!(f, "\\t")?; } else if c == b'\\' || c == b'"' { write!(f, "\\{}", c as char)?; } else if c == b'\0' { write!(f, "\\0")?; // ASCII printable } else if c >= 0x20 && c < 0x7f { write!(f, "{}", c as char)?; } else { write!(f, "\\x{c:02x}")?; } } write!(f, "\"")?; Ok(()) } } struct Vectored<'a, 'b> { bufs: &'a [IoSlice<'b>], nwritten: usize, } impl fmt::Debug for Vectored<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut left = self.nwritten; for buf in self.bufs.iter() { if left == 0 { break; } let n = min(left, buf.len()); Escape(&buf[..n]).fmt(f)?; left -= n; } Ok(()) } } } #[cfg(feature = "__tls")] #[cfg(test)] mod tests { use super::tunnel; use crate::proxy; use std::io::{Read, Write}; use std::net::TcpListener; use std::thread; use tokio::net::TcpStream; use tokio::runtime; static TUNNEL_UA: &str = "tunnel-test/x.y"; static TUNNEL_OK: &[u8] = b"\ HTTP/1.1 200 OK\r\n\ \r\n\ "; macro_rules! mock_tunnel { () => {{ mock_tunnel!(TUNNEL_OK) }}; ($write:expr) => {{ mock_tunnel!($write, "") }}; ($write:expr, $auth:expr) => {{ let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); let connect_expected = format!( "\ CONNECT {0}:{1} HTTP/1.1\r\n\ Host: {0}:{1}\r\n\ User-Agent: {2}\r\n\ {3}\ \r\n\ ", addr.ip(), addr.port(), TUNNEL_UA, $auth ) .into_bytes(); thread::spawn(move || { let (mut sock, _) = listener.accept().unwrap(); let mut buf = [0u8; 4096]; let n = sock.read(&mut buf).unwrap(); assert_eq!(&buf[..n], &connect_expected[..]); sock.write_all($write).unwrap(); }); addr }}; } fn ua() -> Option { Some(http::header::HeaderValue::from_static(TUNNEL_UA)) } #[test] fn test_tunnel() { let addr = mock_tunnel!(); let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let f = async move { let tcp = TcpStream::connect(&addr).await?; let host = addr.ip().to_string(); let port = addr.port(); tunnel(tcp, host, port, ua(), None).await }; rt.block_on(f).unwrap(); } #[test] fn test_tunnel_eof() { let addr = mock_tunnel!(b"HTTP/1.1 200 OK"); let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let f = async move { let tcp = TcpStream::connect(&addr).await?; let host = addr.ip().to_string(); let port = addr.port(); tunnel(tcp, host, port, ua(), None).await }; rt.block_on(f).unwrap_err(); } #[test] fn test_tunnel_non_http_response() { let addr = mock_tunnel!(b"foo bar baz hallo"); let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let f = async move { let tcp = TcpStream::connect(&addr).await?; let host = addr.ip().to_string(); let port = addr.port(); tunnel(tcp, host, port, ua(), None).await }; rt.block_on(f).unwrap_err(); } #[test] fn test_tunnel_proxy_unauthorized() { let addr = mock_tunnel!( b"\ HTTP/1.1 407 Proxy Authentication Required\r\n\ Proxy-Authenticate: Basic realm=\"nope\"\r\n\ \r\n\ " ); let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let f = async move { let tcp = TcpStream::connect(&addr).await?; let host = addr.ip().to_string(); let port = addr.port(); tunnel(tcp, host, port, ua(), None).await }; let error = rt.block_on(f).unwrap_err(); assert_eq!(error.to_string(), "proxy authentication required"); } #[test] fn test_tunnel_basic_auth() { let addr = mock_tunnel!( TUNNEL_OK, "Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n" ); let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let f = async move { let tcp = TcpStream::connect(&addr).await?; let host = addr.ip().to_string(); let port = addr.port(); tunnel( tcp, host, port, ua(), Some(proxy::encode_basic_auth("Aladdin", "open sesame")), ) .await }; rt.block_on(f).unwrap(); } } reqwest-0.11.27/src/cookie.rs000064400000000000000000000130151046102023000140740ustar 00000000000000//! HTTP Cookies use std::convert::TryInto; use std::fmt; use std::sync::RwLock; use std::time::SystemTime; use crate::header::{HeaderValue, SET_COOKIE}; use bytes::Bytes; /// Actions for a persistent cookie store providing session support. pub trait CookieStore: Send + Sync { /// Store a set of Set-Cookie header values received from `url` fn set_cookies(&self, cookie_headers: &mut dyn Iterator, url: &url::Url); /// Get any Cookie values in the store for `url` fn cookies(&self, url: &url::Url) -> Option; } /// A single HTTP cookie. pub struct Cookie<'a>(cookie_crate::Cookie<'a>); /// A good default `CookieStore` implementation. /// /// This is the implementation used when simply calling `cookie_store(true)`. /// This type is exposed to allow creating one and filling it with some /// existing cookies more easily, before creating a `Client`. /// /// For more advanced scenarios, such as needing to serialize the store or /// manipulate it between requests, you may refer to the /// [reqwest_cookie_store crate](https://crates.io/crates/reqwest_cookie_store). #[derive(Debug, Default)] pub struct Jar(RwLock); // ===== impl Cookie ===== impl<'a> Cookie<'a> { fn parse(value: &'a HeaderValue) -> Result, CookieParseError> { std::str::from_utf8(value.as_bytes()) .map_err(cookie_crate::ParseError::from) .and_then(cookie_crate::Cookie::parse) .map_err(CookieParseError) .map(Cookie) } /// The name of the cookie. pub fn name(&self) -> &str { self.0.name() } /// The value of the cookie. pub fn value(&self) -> &str { self.0.value() } /// Returns true if the 'HttpOnly' directive is enabled. pub fn http_only(&self) -> bool { self.0.http_only().unwrap_or(false) } /// Returns true if the 'Secure' directive is enabled. pub fn secure(&self) -> bool { self.0.secure().unwrap_or(false) } /// Returns true if 'SameSite' directive is 'Lax'. pub fn same_site_lax(&self) -> bool { self.0.same_site() == Some(cookie_crate::SameSite::Lax) } /// Returns true if 'SameSite' directive is 'Strict'. pub fn same_site_strict(&self) -> bool { self.0.same_site() == Some(cookie_crate::SameSite::Strict) } /// Returns the path directive of the cookie, if set. pub fn path(&self) -> Option<&str> { self.0.path() } /// Returns the domain directive of the cookie, if set. pub fn domain(&self) -> Option<&str> { self.0.domain() } /// Get the Max-Age information. pub fn max_age(&self) -> Option { self.0.max_age().map(|d| { d.try_into() .expect("time::Duration into std::time::Duration") }) } /// The cookie expiration time. pub fn expires(&self) -> Option { match self.0.expires() { Some(cookie_crate::Expiration::DateTime(offset)) => Some(SystemTime::from(offset)), None | Some(cookie_crate::Expiration::Session) => None, } } } impl<'a> fmt::Debug for Cookie<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } pub(crate) fn extract_response_cookie_headers<'a>( headers: &'a hyper::HeaderMap, ) -> impl Iterator + 'a { headers.get_all(SET_COOKIE).iter() } pub(crate) fn extract_response_cookies<'a>( headers: &'a hyper::HeaderMap, ) -> impl Iterator, CookieParseError>> + 'a { headers .get_all(SET_COOKIE) .iter() .map(|value| Cookie::parse(value)) } /// Error representing a parse failure of a 'Set-Cookie' header. pub(crate) struct CookieParseError(cookie_crate::ParseError); impl<'a> fmt::Debug for CookieParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } impl<'a> fmt::Display for CookieParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } } impl std::error::Error for CookieParseError {} // ===== impl Jar ===== impl Jar { /// Add a cookie to this jar. /// /// # Example /// /// ``` /// use reqwest::{cookie::Jar, Url}; /// /// let cookie = "foo=bar; Domain=yolo.local"; /// let url = "https://yolo.local".parse::().unwrap(); /// /// let jar = Jar::default(); /// jar.add_cookie_str(cookie, &url); /// /// // and now add to a `ClientBuilder`? /// ``` pub fn add_cookie_str(&self, cookie: &str, url: &url::Url) { let cookies = cookie_crate::Cookie::parse(cookie) .ok() .map(|c| c.into_owned()) .into_iter(); self.0.write().unwrap().store_response_cookies(cookies, url); } } impl CookieStore for Jar { fn set_cookies(&self, cookie_headers: &mut dyn Iterator, url: &url::Url) { let iter = cookie_headers.filter_map(|val| Cookie::parse(val).map(|c| c.0.into_owned()).ok()); self.0.write().unwrap().store_response_cookies(iter, url); } fn cookies(&self, url: &url::Url) -> Option { let s = self .0 .read() .unwrap() .get_request_values(url) .map(|(name, value)| format!("{name}={value}")) .collect::>() .join("; "); if s.is_empty() { return None; } HeaderValue::from_maybe_shared(Bytes::from(s)).ok() } } reqwest-0.11.27/src/dns/gai.rs000064400000000000000000000014501046102023000141470ustar 00000000000000use futures_util::future::FutureExt; use hyper::client::connect::dns::{GaiResolver as HyperGaiResolver, Name}; use hyper::service::Service; use crate::dns::{Addrs, Resolve, Resolving}; use crate::error::BoxError; #[derive(Debug)] pub struct GaiResolver(HyperGaiResolver); impl GaiResolver { pub fn new() -> Self { Self(HyperGaiResolver::new()) } } impl Default for GaiResolver { fn default() -> Self { GaiResolver::new() } } impl Resolve for GaiResolver { fn resolve(&self, name: Name) -> Resolving { let this = &mut self.0.clone(); Box::pin(Service::::call(this, name).map(|result| { result .map(|addrs| -> Addrs { Box::new(addrs) }) .map_err(|err| -> BoxError { Box::new(err) }) })) } } reqwest-0.11.27/src/dns/hickory.rs000064400000000000000000000034361046102023000150650ustar 00000000000000//! DNS resolution via the [hickory-resolver](https://github.com/hickory-dns/hickory-dns) crate use hickory_resolver::{lookup_ip::LookupIpIntoIter, system_conf, TokioAsyncResolver}; use hyper::client::connect::dns::Name; use once_cell::sync::OnceCell; use std::io; use std::net::SocketAddr; use std::sync::Arc; use super::{Addrs, Resolve, Resolving}; /// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait. #[derive(Debug, Default, Clone)] pub(crate) struct HickoryDnsResolver { /// Since we might not have been called in the context of a /// Tokio Runtime in initialization, so we must delay the actual /// construction of the resolver. state: Arc>, } struct SocketAddrs { iter: LookupIpIntoIter, } impl Resolve for HickoryDnsResolver { fn resolve(&self, name: Name) -> Resolving { let resolver = self.clone(); Box::pin(async move { let resolver = resolver.state.get_or_try_init(new_resolver)?; let lookup = resolver.lookup_ip(name.as_str()).await?; let addrs: Addrs = Box::new(SocketAddrs { iter: lookup.into_iter(), }); Ok(addrs) }) } } impl Iterator for SocketAddrs { type Item = SocketAddr; fn next(&mut self) -> Option { self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0)) } } /// Create a new resolver with the default configuration, /// which reads from `/etc/resolve.conf`. fn new_resolver() -> io::Result { let (config, opts) = system_conf::read_system_conf().map_err(|e| { io::Error::new( io::ErrorKind::Other, format!("error reading DNS system conf: {e}"), ) })?; Ok(TokioAsyncResolver::tokio(config, opts)) } reqwest-0.11.27/src/dns/mod.rs000064400000000000000000000003501046102023000141640ustar 00000000000000//! DNS resolution pub use resolve::{Addrs, Resolve, Resolving}; pub(crate) use resolve::{DnsResolverWithOverrides, DynResolver}; pub(crate) mod gai; #[cfg(feature = "hickory-dns")] pub(crate) mod hickory; pub(crate) mod resolve; reqwest-0.11.27/src/dns/resolve.rs000064400000000000000000000047111046102023000150710ustar 00000000000000use hyper::client::connect::dns::Name; use hyper::service::Service; use std::collections::HashMap; use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use crate::error::BoxError; /// Alias for an `Iterator` trait object over `SocketAddr`. pub type Addrs = Box + Send>; /// Alias for the `Future` type returned by a DNS resolver. pub type Resolving = Pin> + Send>>; /// Trait for customizing DNS resolution in reqwest. pub trait Resolve: Send + Sync { /// Performs DNS resolution on a `Name`. /// The return type is a future containing an iterator of `SocketAddr`. /// /// It differs from `tower_service::Service` in several ways: /// * It is assumed that `resolve` will always be ready to poll. /// * It does not need a mutable reference to `self`. /// * Since trait objects cannot make use of associated types, it requires /// wrapping the returned `Future` and its contained `Iterator` with `Box`. fn resolve(&self, name: Name) -> Resolving; } #[derive(Clone)] pub(crate) struct DynResolver { resolver: Arc, } impl DynResolver { pub(crate) fn new(resolver: Arc) -> Self { Self { resolver } } } impl Service for DynResolver { type Response = Addrs; type Error = BoxError; type Future = Resolving; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, name: Name) -> Self::Future { self.resolver.resolve(name) } } pub(crate) struct DnsResolverWithOverrides { dns_resolver: Arc, overrides: Arc>>, } impl DnsResolverWithOverrides { pub(crate) fn new( dns_resolver: Arc, overrides: HashMap>, ) -> Self { DnsResolverWithOverrides { dns_resolver, overrides: Arc::new(overrides), } } } impl Resolve for DnsResolverWithOverrides { fn resolve(&self, name: Name) -> Resolving { match self.overrides.get(name.as_str()) { Some(dest) => { let addrs: Addrs = Box::new(dest.clone().into_iter()); Box::pin(futures_util::future::ready(Ok(addrs))) } None => self.dns_resolver.resolve(name), } } } reqwest-0.11.27/src/error.rs000064400000000000000000000243771046102023000137710ustar 00000000000000#![cfg_attr(target_arch = "wasm32", allow(unused))] use std::error::Error as StdError; use std::fmt; use std::io; use crate::{StatusCode, Url}; /// A `Result` alias where the `Err` case is `reqwest::Error`. pub type Result = std::result::Result; /// The Errors that may occur when processing a `Request`. /// /// Note: Errors may include the full URL used to make the `Request`. If the URL /// contains sensitive information (e.g. an API key as a query parameter), be /// sure to remove it ([`without_url`](Error::without_url)) pub struct Error { inner: Box, } pub(crate) type BoxError = Box; struct Inner { kind: Kind, source: Option, url: Option, } impl Error { pub(crate) fn new(kind: Kind, source: Option) -> Error where E: Into, { Error { inner: Box::new(Inner { kind, source: source.map(Into::into), url: None, }), } } /// Returns a possible URL related to this error. /// /// # Examples /// /// ``` /// # async fn run() { /// // displays last stop of a redirect loop /// let response = reqwest::get("http://site.with.redirect.loop").await; /// if let Err(e) = response { /// if e.is_redirect() { /// if let Some(final_stop) = e.url() { /// println!("redirect loop at {final_stop}"); /// } /// } /// } /// # } /// ``` pub fn url(&self) -> Option<&Url> { self.inner.url.as_ref() } /// Returns a mutable reference to the URL related to this error /// /// This is useful if you need to remove sensitive information from the URL /// (e.g. an API key in the query), but do not want to remove the URL /// entirely. pub fn url_mut(&mut self) -> Option<&mut Url> { self.inner.url.as_mut() } /// Add a url related to this error (overwriting any existing) pub fn with_url(mut self, url: Url) -> Self { self.inner.url = Some(url); self } /// Strip the related url from this error (if, for example, it contains /// sensitive information) pub fn without_url(mut self) -> Self { self.inner.url = None; self } /// Returns true if the error is from a type Builder. pub fn is_builder(&self) -> bool { matches!(self.inner.kind, Kind::Builder) } /// Returns true if the error is from a `RedirectPolicy`. pub fn is_redirect(&self) -> bool { matches!(self.inner.kind, Kind::Redirect) } /// Returns true if the error is from `Response::error_for_status`. pub fn is_status(&self) -> bool { matches!(self.inner.kind, Kind::Status(_)) } /// Returns true if the error is related to a timeout. pub fn is_timeout(&self) -> bool { let mut source = self.source(); while let Some(err) = source { if err.is::() { return true; } if let Some(io) = err.downcast_ref::() { if io.kind() == io::ErrorKind::TimedOut { return true; } } source = err.source(); } false } /// Returns true if the error is related to the request pub fn is_request(&self) -> bool { matches!(self.inner.kind, Kind::Request) } #[cfg(not(target_arch = "wasm32"))] /// Returns true if the error is related to connect pub fn is_connect(&self) -> bool { let mut source = self.source(); while let Some(err) = source { if let Some(hyper_err) = err.downcast_ref::() { if hyper_err.is_connect() { return true; } } source = err.source(); } false } /// Returns true if the error is related to the request or response body pub fn is_body(&self) -> bool { matches!(self.inner.kind, Kind::Body) } /// Returns true if the error is related to decoding the response's body pub fn is_decode(&self) -> bool { matches!(self.inner.kind, Kind::Decode) } /// Returns the status code, if the error was generated from a response. pub fn status(&self) -> Option { match self.inner.kind { Kind::Status(code) => Some(code), _ => None, } } // private #[allow(unused)] pub(crate) fn into_io(self) -> io::Error { io::Error::new(io::ErrorKind::Other, self) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("reqwest::Error"); builder.field("kind", &self.inner.kind); if let Some(ref url) = self.inner.url { builder.field("url", url); } if let Some(ref source) = self.inner.source { builder.field("source", source); } builder.finish() } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.inner.kind { Kind::Builder => f.write_str("builder error")?, Kind::Request => f.write_str("error sending request")?, Kind::Body => f.write_str("request or response body error")?, Kind::Decode => f.write_str("error decoding response body")?, Kind::Redirect => f.write_str("error following redirect")?, Kind::Upgrade => f.write_str("error upgrading connection")?, Kind::Status(ref code) => { let prefix = if code.is_client_error() { "HTTP status client error" } else { debug_assert!(code.is_server_error()); "HTTP status server error" }; write!(f, "{prefix} ({code})")?; } }; if let Some(url) = &self.inner.url { write!(f, " for url ({url})")?; } if let Some(e) = &self.inner.source { write!(f, ": {e}")?; } Ok(()) } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { self.inner.source.as_ref().map(|e| &**e as _) } } #[cfg(target_arch = "wasm32")] impl From for wasm_bindgen::JsValue { fn from(err: Error) -> wasm_bindgen::JsValue { js_sys::Error::from(err).into() } } #[cfg(target_arch = "wasm32")] impl From for js_sys::Error { fn from(err: Error) -> js_sys::Error { js_sys::Error::new(&format!("{err}")) } } #[derive(Debug)] pub(crate) enum Kind { Builder, Request, Redirect, Status(StatusCode), Body, Decode, Upgrade, } // constructors pub(crate) fn builder>(e: E) -> Error { Error::new(Kind::Builder, Some(e)) } pub(crate) fn body>(e: E) -> Error { Error::new(Kind::Body, Some(e)) } pub(crate) fn decode>(e: E) -> Error { Error::new(Kind::Decode, Some(e)) } pub(crate) fn request>(e: E) -> Error { Error::new(Kind::Request, Some(e)) } pub(crate) fn redirect>(e: E, url: Url) -> Error { Error::new(Kind::Redirect, Some(e)).with_url(url) } pub(crate) fn status_code(url: Url, status: StatusCode) -> Error { Error::new(Kind::Status(status), None::).with_url(url) } pub(crate) fn url_bad_scheme(url: Url) -> Error { Error::new(Kind::Builder, Some(BadScheme)).with_url(url) } pub(crate) fn url_invalid_uri(url: Url) -> Error { Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url) } if_wasm! { pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError { format!("{js_val:?}").into() } } pub(crate) fn upgrade>(e: E) -> Error { Error::new(Kind::Upgrade, Some(e)) } // io::Error helpers #[allow(unused)] pub(crate) fn into_io(e: Error) -> io::Error { e.into_io() } #[allow(unused)] pub(crate) fn decode_io(e: io::Error) -> Error { if e.get_ref().map(|r| r.is::()).unwrap_or(false) { *e.into_inner() .expect("io::Error::get_ref was Some(_)") .downcast::() .expect("StdError::is() was true") } else { decode(e) } } // internal Error "sources" #[derive(Debug)] pub(crate) struct TimedOut; impl fmt::Display for TimedOut { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("operation timed out") } } impl StdError for TimedOut {} #[derive(Debug)] pub(crate) struct BadScheme; impl fmt::Display for BadScheme { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("URL scheme is not allowed") } } impl StdError for BadScheme {} #[cfg(test)] mod tests { use super::*; fn assert_send() {} fn assert_sync() {} #[test] fn test_source_chain() { let root = Error::new(Kind::Request, None::); assert!(root.source().is_none()); let link = super::body(root); assert!(link.source().is_some()); assert_send::(); assert_sync::(); } #[test] fn mem_size_of() { use std::mem::size_of; assert_eq!(size_of::(), size_of::()); } #[test] fn roundtrip_io_error() { let orig = super::request("orig"); // Convert reqwest::Error into an io::Error... let io = orig.into_io(); // Convert that io::Error back into a reqwest::Error... let err = super::decode_io(io); // It should have pulled out the original, not nested it... match err.inner.kind { Kind::Request => (), _ => panic!("{err:?}"), } } #[test] fn from_unknown_io_error() { let orig = io::Error::new(io::ErrorKind::Other, "orly"); let err = super::decode_io(orig); match err.inner.kind { Kind::Decode => (), _ => panic!("{err:?}"), } } #[test] fn is_timeout() { let err = super::request(super::TimedOut); assert!(err.is_timeout()); let io = io::Error::new(io::ErrorKind::Other, err); let nested = super::request(io); assert!(nested.is_timeout()); } } reqwest-0.11.27/src/into_url.rs000064400000000000000000000055421046102023000144640ustar 00000000000000use url::Url; /// A trait to try to convert some type into a `Url`. /// /// This trait is "sealed", such that only types within reqwest can /// implement it. pub trait IntoUrl: IntoUrlSealed {} impl IntoUrl for Url {} impl IntoUrl for String {} impl<'a> IntoUrl for &'a str {} impl<'a> IntoUrl for &'a String {} pub trait IntoUrlSealed { // Besides parsing as a valid `Url`, the `Url` must be a valid // `http::Uri`, in that it makes sense to use in a network request. fn into_url(self) -> crate::Result; fn as_str(&self) -> &str; } impl IntoUrlSealed for Url { fn into_url(self) -> crate::Result { // With blob url the `self.has_host()` check is always false, so we // remove the `blob:` scheme and check again if the url is valid. #[cfg(target_arch = "wasm32")] if self.scheme() == "blob" && self.path().starts_with("http") // Check if the path starts with http or https to avoid validating a `blob:blob:...` url. && self.as_str()[5..].into_url().is_ok() { return Ok(self); } if self.has_host() { Ok(self) } else { Err(crate::error::url_bad_scheme(self)) } } fn as_str(&self) -> &str { self.as_ref() } } impl<'a> IntoUrlSealed for &'a str { fn into_url(self) -> crate::Result { Url::parse(self).map_err(crate::error::builder)?.into_url() } fn as_str(&self) -> &str { self } } impl<'a> IntoUrlSealed for &'a String { fn into_url(self) -> crate::Result { (&**self).into_url() } fn as_str(&self) -> &str { self.as_ref() } } impl<'a> IntoUrlSealed for String { fn into_url(self) -> crate::Result { (&*self).into_url() } fn as_str(&self) -> &str { self.as_ref() } } if_hyper! { pub(crate) fn try_uri(url: &Url) -> crate::Result { url.as_str() .parse() .map_err(|_| crate::error::url_invalid_uri(url.clone())) } } #[cfg(test)] mod tests { use super::*; #[test] fn into_url_file_scheme() { let err = "file:///etc/hosts".into_url().unwrap_err(); assert_eq!( err.to_string(), "builder error for url (file:///etc/hosts): URL scheme is not allowed" ); } #[test] fn into_url_blob_scheme() { let err = "blob:https://example.com".into_url().unwrap_err(); assert_eq!( err.to_string(), "builder error for url (blob:https://example.com): URL scheme is not allowed" ); } if_wasm! { use wasm_bindgen_test::*; #[wasm_bindgen_test] fn into_url_blob_scheme_wasm() { let url = "blob:http://example.com".into_url().unwrap(); assert_eq!(url.as_str(), "blob:http://example.com"); } } } reqwest-0.11.27/src/lib.rs000064400000000000000000000263521046102023000134010ustar 00000000000000#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(test, deny(warnings))] //! # reqwest //! //! The `reqwest` crate provides a convenient, higher-level HTTP //! [`Client`][client]. //! //! It handles many of the things that most people just expect an HTTP client //! to do for them. //! //! - Async and [blocking] Clients //! - Plain bodies, [JSON](#json), [urlencoded](#forms), [multipart] //! - Customizable [redirect policy](#redirect-policies) //! - HTTP [Proxies](#proxies) //! - Uses [TLS](#tls) by default //! - Cookies //! //! The [`reqwest::Client`][client] is asynchronous. For applications wishing //! to only make a few HTTP requests, the [`reqwest::blocking`](blocking) API //! may be more convenient. //! //! Additional learning resources include: //! //! - [The Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/web/clients.html) //! - [Reqwest Repository Examples](https://github.com/seanmonstar/reqwest/tree/master/examples) //! //! ## Commercial Support //! //! For private advice, support, reviews, access to the maintainer, and the //! like, reach out for [commercial support][sponsor]. //! //! ## Making a GET request //! //! For a single request, you can use the [`get`][get] shortcut method. //! //! ```rust //! # async fn run() -> Result<(), reqwest::Error> { //! let body = reqwest::get("https://www.rust-lang.org") //! .await? //! .text() //! .await?; //! //! println!("body = {body:?}"); //! # Ok(()) //! # } //! ``` //! //! **NOTE**: If you plan to perform multiple requests, it is best to create a //! [`Client`][client] and reuse it, taking advantage of keep-alive connection //! pooling. //! //! ## Making POST requests (or setting request bodies) //! //! There are several ways you can set the body of a request. The basic one is //! by using the `body()` method of a [`RequestBuilder`][builder]. This lets you set the //! exact raw bytes of what the body should be. It accepts various types, //! including `String` and `Vec`. If you wish to pass a custom //! type, you can use the `reqwest::Body` constructors. //! //! ```rust //! # use reqwest::Error; //! # //! # async fn run() -> Result<(), Error> { //! let client = reqwest::Client::new(); //! let res = client.post("http://httpbin.org/post") //! .body("the exact body that is sent") //! .send() //! .await?; //! # Ok(()) //! # } //! ``` //! //! ### Forms //! //! It's very common to want to send form data in a request body. This can be //! done with any type that can be serialized into form data. //! //! This can be an array of tuples, or a `HashMap`, or a custom type that //! implements [`Serialize`][serde]. //! //! ```rust //! # use reqwest::Error; //! # //! # async fn run() -> Result<(), Error> { //! // This will POST a body of `foo=bar&baz=quux` //! let params = [("foo", "bar"), ("baz", "quux")]; //! let client = reqwest::Client::new(); //! let res = client.post("http://httpbin.org/post") //! .form(¶ms) //! .send() //! .await?; //! # Ok(()) //! # } //! ``` //! //! ### JSON //! //! There is also a `json` method helper on the [`RequestBuilder`][builder] that works in //! a similar fashion the `form` method. It can take any value that can be //! serialized into JSON. The feature `json` is required. //! //! ```rust //! # use reqwest::Error; //! # use std::collections::HashMap; //! # //! # #[cfg(feature = "json")] //! # async fn run() -> Result<(), Error> { //! // This will POST a body of `{"lang":"rust","body":"json"}` //! let mut map = HashMap::new(); //! map.insert("lang", "rust"); //! map.insert("body", "json"); //! //! let client = reqwest::Client::new(); //! let res = client.post("http://httpbin.org/post") //! .json(&map) //! .send() //! .await?; //! # Ok(()) //! # } //! ``` //! //! ## Redirect Policies //! //! By default, a `Client` will automatically handle HTTP redirects, having a //! maximum redirect chain of 10 hops. To customize this behavior, a //! [`redirect::Policy`][redirect] can be used with a `ClientBuilder`. //! //! ## Cookies //! //! The automatic storing and sending of session cookies can be enabled with //! the [`cookie_store`][ClientBuilder::cookie_store] method on `ClientBuilder`. //! //! ## Proxies //! //! **NOTE**: System proxies are enabled by default. //! //! System proxies look in environment variables to set HTTP or HTTPS proxies. //! //! `HTTP_PROXY` or `http_proxy` provide http proxies for http connections while //! `HTTPS_PROXY` or `https_proxy` provide HTTPS proxies for HTTPS connections. //! //! These can be overwritten by adding a [`Proxy`] to `ClientBuilder` //! i.e. `let proxy = reqwest::Proxy::http("https://secure.example")?;` //! or disabled by calling `ClientBuilder::no_proxy()`. //! //! `socks` feature is required if you have configured socks proxy like this: //! //! ```bash //! export https_proxy=socks5://127.0.0.1:1086 //! ``` //! //! ## TLS //! //! A `Client` will use transport layer security (TLS) by default to connect to //! HTTPS destinations. //! //! - Additional server certificates can be configured on a `ClientBuilder` //! with the [`Certificate`] type. //! - Client certificates can be added to a `ClientBuilder` with the //! [`Identity`] type. //! - Various parts of TLS can also be configured or even disabled on the //! `ClientBuilder`. //! //! See more details in the [`tls`] module. //! //! ## WASM //! //! The Client implementation automatically switches to the WASM one when the target_arch is wasm32, //! the usage is basically the same as the async api. Some of the features are disabled in wasm //! : [`tls`], [`cookie`], [`blocking`]. //! //! //! ## Optional Features //! //! The following are a list of [Cargo features][cargo-features] that can be //! enabled or disabled: //! //! - **default-tls** *(enabled by default)*: Provides TLS support to connect //! over HTTPS. //! - **native-tls**: Enables TLS functionality provided by `native-tls`. //! - **native-tls-vendored**: Enables the `vendored` feature of `native-tls`. //! - **native-tls-alpn**: Enables the `alpn` feature of `native-tls`. //! - **rustls-tls**: Enables TLS functionality provided by `rustls`. //! Equivalent to `rustls-tls-webpki-roots`. //! - **rustls-tls-manual-roots**: Enables TLS functionality provided by `rustls`, //! without setting any root certificates. Roots have to be specified manually. //! - **rustls-tls-webpki-roots**: Enables TLS functionality provided by `rustls`, //! while using root certificates from the `webpki-roots` crate. //! - **rustls-tls-native-roots**: Enables TLS functionality provided by `rustls`, //! while using root certificates from the `rustls-native-certs` crate. //! - **blocking**: Provides the [blocking][] client API. //! - **cookies**: Provides cookie session support. //! - **gzip**: Provides response body gzip decompression. //! - **brotli**: Provides response body brotli decompression. //! - **deflate**: Provides response body deflate decompression. //! - **json**: Provides serialization and deserialization for JSON bodies. //! - **multipart**: Provides functionality for multipart forms. //! - **stream**: Adds support for `futures::Stream`. //! - **socks**: Provides SOCKS5 proxy support. //! - **hickory-dns**: Enables a hickory-dns async resolver instead of default //! threadpool using `getaddrinfo`. //! //! ## Unstable Features //! //! Some feature flags require additional opt-in by the application, by setting //! a `reqwest_unstable` flag. //! //! - **http3** *(unstable)*: Enables support for sending HTTP/3 requests. //! //! These features are unstable, and experimental. Details about them may be //! changed in patch releases. //! //! You can pass such a flag to the compiler via `.cargo/config`, or //! environment variables, such as: //! //! ```notrust //! RUSTFLAGS="--cfg reqwest_unstable" cargo build //! ``` //! //! ## Sponsors //! //! Support this project by becoming a [sponsor][]. //! //! [hyper]: https://hyper.rs //! [blocking]: ./blocking/index.html //! [client]: ./struct.Client.html //! [response]: ./struct.Response.html //! [get]: ./fn.get.html //! [builder]: ./struct.RequestBuilder.html //! [serde]: http://serde.rs //! [redirect]: crate::redirect //! [Proxy]: ./struct.Proxy.html //! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section //! [sponsor]: https://seanmonstar.com/sponsor #[cfg(all(feature = "http3", not(reqwest_unstable)))] compile_error!( "\ The `http3` feature is unstable, and requires the \ `RUSTFLAGS='--cfg reqwest_unstable'` environment variable to be set.\ " ); macro_rules! if_wasm { ($($item:item)*) => {$( #[cfg(target_arch = "wasm32")] $item )*} } macro_rules! if_hyper { ($($item:item)*) => {$( #[cfg(not(target_arch = "wasm32"))] $item )*} } pub use http::header; pub use http::Method; pub use http::{StatusCode, Version}; pub use url::Url; // universal mods #[macro_use] mod error; mod into_url; mod response; pub use self::error::{Error, Result}; pub use self::into_url::IntoUrl; pub use self::response::ResponseBuilderExt; /// Shortcut method to quickly make a `GET` request. /// /// See also the methods on the [`reqwest::Response`](./struct.Response.html) /// type. /// /// **NOTE**: This function creates a new internal `Client` on each call, /// and so should not be used if making many requests. Create a /// [`Client`](./struct.Client.html) instead. /// /// # Examples /// /// ```rust /// # async fn run() -> Result<(), reqwest::Error> { /// let body = reqwest::get("https://www.rust-lang.org").await? /// .text().await?; /// # Ok(()) /// # } /// ``` /// /// # Errors /// /// This function fails if: /// /// - native TLS backend cannot be initialized /// - supplied `Url` cannot be parsed /// - there was an error while sending request /// - redirect limit was exhausted pub async fn get(url: T) -> crate::Result { Client::builder().build()?.get(url).send().await } fn _assert_impls() { fn assert_send() {} fn assert_sync() {} fn assert_clone() {} assert_send::(); assert_sync::(); assert_clone::(); assert_send::(); assert_send::(); #[cfg(not(target_arch = "wasm32"))] { assert_send::(); } assert_send::(); assert_sync::(); assert_send::(); assert_sync::(); } if_hyper! { #[cfg(test)] #[macro_use] extern crate doc_comment; #[cfg(test)] doctest!("../README.md"); pub use self::async_impl::{ Body, Client, ClientBuilder, Request, RequestBuilder, Response, Upgraded, }; pub use self::proxy::{Proxy,NoProxy}; #[cfg(feature = "__tls")] // Re-exports, to be removed in a future release pub use tls::{Certificate, Identity}; #[cfg(feature = "multipart")] pub use self::async_impl::multipart; mod async_impl; #[cfg(feature = "blocking")] pub mod blocking; mod connect; #[cfg(feature = "cookies")] pub mod cookie; pub mod dns; mod proxy; pub mod redirect; #[cfg(feature = "__tls")] pub mod tls; mod util; } if_wasm! { mod wasm; mod util; pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response}; #[cfg(feature = "multipart")] pub use self::wasm::multipart; } reqwest-0.11.27/src/proxy.rs000064400000000000000000002030611046102023000140060ustar 00000000000000use std::fmt; #[cfg(feature = "socks")] use std::net::SocketAddr; use std::sync::Arc; use crate::into_url::{IntoUrl, IntoUrlSealed}; use crate::Url; use http::{header::HeaderValue, Uri}; use ipnet::IpNet; use once_cell::sync::Lazy; use percent_encoding::percent_decode; use std::collections::HashMap; use std::env; use std::error::Error; use std::net::IpAddr; #[cfg(target_os = "macos")] use system_configuration::{ core_foundation::{ base::CFType, dictionary::CFDictionary, number::CFNumber, string::{CFString, CFStringRef}, }, dynamic_store::SCDynamicStoreBuilder, sys::schema_definitions::kSCPropNetProxiesHTTPEnable, sys::schema_definitions::kSCPropNetProxiesHTTPPort, sys::schema_definitions::kSCPropNetProxiesHTTPProxy, sys::schema_definitions::kSCPropNetProxiesHTTPSEnable, sys::schema_definitions::kSCPropNetProxiesHTTPSPort, sys::schema_definitions::kSCPropNetProxiesHTTPSProxy, }; #[cfg(target_os = "windows")] use winreg::enums::HKEY_CURRENT_USER; #[cfg(target_os = "windows")] use winreg::RegKey; /// Configuration of a proxy that a `Client` should pass requests to. /// /// A `Proxy` has a couple pieces to it: /// /// - a URL of how to talk to the proxy /// - rules on what `Client` requests should be directed to the proxy /// /// For instance, let's look at `Proxy::http`: /// /// ```rust /// # fn run() -> Result<(), Box> { /// let proxy = reqwest::Proxy::http("https://secure.example")?; /// # Ok(()) /// # } /// ``` /// /// This proxy will intercept all HTTP requests, and make use of the proxy /// at `https://secure.example`. A request to `http://hyper.rs` will talk /// to your proxy. A request to `https://hyper.rs` will not. /// /// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will /// check each `Proxy` in the order it was added. This could mean that a /// `Proxy` added first with eager intercept rules, such as `Proxy::all`, /// would prevent a `Proxy` later in the list from ever working, so take care. /// /// By enabling the `"socks"` feature it is possible to use a socks proxy: /// ```rust /// # fn run() -> Result<(), Box> { /// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?; /// # Ok(()) /// # } /// ``` #[derive(Clone)] pub struct Proxy { intercept: Intercept, no_proxy: Option, } /// Represents a possible matching entry for an IP address #[derive(Clone, Debug)] enum Ip { Address(IpAddr), Network(IpNet), } /// A wrapper around a list of IP cidr blocks or addresses with a [IpMatcher::contains] method for /// checking if an IP address is contained within the matcher #[derive(Clone, Debug, Default)] struct IpMatcher(Vec); /// A wrapper around a list of domains with a [DomainMatcher::contains] method for checking if a /// domain is contained within the matcher #[derive(Clone, Debug, Default)] struct DomainMatcher(Vec); /// A configuration for filtering out requests that shouldn't be proxied #[derive(Clone, Debug, Default)] pub struct NoProxy { ips: IpMatcher, domains: DomainMatcher, } /// A particular scheme used for proxying requests. /// /// For example, HTTP vs SOCKS5 #[derive(Clone)] pub enum ProxyScheme { Http { auth: Option, host: http::uri::Authority, }, Https { auth: Option, host: http::uri::Authority, }, #[cfg(feature = "socks")] Socks5 { addr: SocketAddr, auth: Option<(String, String)>, remote_dns: bool, }, } impl ProxyScheme { fn maybe_http_auth(&self) -> Option<&HeaderValue> { match self { ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(), #[cfg(feature = "socks")] _ => None, } } } /// Trait used for converting into a proxy scheme. This trait supports /// parsing from a URL-like type, whilst also supporting proxy schemes /// built directly using the factory methods. pub trait IntoProxyScheme { fn into_proxy_scheme(self) -> crate::Result; } impl IntoProxyScheme for S { fn into_proxy_scheme(self) -> crate::Result { // validate the URL let url = match self.as_str().into_url() { Ok(ok) => ok, Err(e) => { let mut presumed_to_have_scheme = true; let mut source = e.source(); while let Some(err) = source { if let Some(parse_error) = err.downcast_ref::() { match parse_error { url::ParseError::RelativeUrlWithoutBase => { presumed_to_have_scheme = false; break; } _ => {} } } else if let Some(_) = err.downcast_ref::() { presumed_to_have_scheme = false; break; } source = err.source(); } if presumed_to_have_scheme { return Err(crate::error::builder(e)); } // the issue could have been caused by a missing scheme, so we try adding http:// let try_this = format!("http://{}", self.as_str()); try_this.into_url().map_err(|_| { // return the original error crate::error::builder(e) })? } }; ProxyScheme::parse(url) } } // These bounds are accidentally leaked by the blanket impl of IntoProxyScheme // for all types that implement IntoUrl. So, this function exists to detect // if we were to break those bounds for a user. fn _implied_bounds() { fn prox(_t: T) {} fn url(t: T) { prox(t); } } impl IntoProxyScheme for ProxyScheme { fn into_proxy_scheme(self) -> crate::Result { Ok(self) } } impl Proxy { /// Proxy all HTTP traffic to the passed URL. /// /// # Example /// /// ``` /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let client = reqwest::Client::builder() /// .proxy(reqwest::Proxy::http("https://my.prox")?) /// .build()?; /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn http(proxy_scheme: U) -> crate::Result { Ok(Proxy::new(Intercept::Http( proxy_scheme.into_proxy_scheme()?, ))) } /// Proxy all HTTPS traffic to the passed URL. /// /// # Example /// /// ``` /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let client = reqwest::Client::builder() /// .proxy(reqwest::Proxy::https("https://example.prox:4545")?) /// .build()?; /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn https(proxy_scheme: U) -> crate::Result { Ok(Proxy::new(Intercept::Https( proxy_scheme.into_proxy_scheme()?, ))) } /// Proxy **all** traffic to the passed URL. /// /// # Example /// /// ``` /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let client = reqwest::Client::builder() /// .proxy(reqwest::Proxy::all("http://pro.xy")?) /// .build()?; /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn all(proxy_scheme: U) -> crate::Result { Ok(Proxy::new(Intercept::All( proxy_scheme.into_proxy_scheme()?, ))) } /// Provide a custom function to determine what traffic to proxy to where. /// /// # Example /// /// ``` /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let target = reqwest::Url::parse("https://my.prox")?; /// let client = reqwest::Client::builder() /// .proxy(reqwest::Proxy::custom(move |url| { /// if url.host_str() == Some("hyper.rs") { /// Some(target.clone()) /// } else { /// None /// } /// })) /// .build()?; /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn custom(fun: F) -> Proxy where F: Fn(&Url) -> Option + Send + Sync + 'static, { Proxy::new(Intercept::Custom(Custom { auth: None, func: Arc::new(move |url| fun(url).map(IntoProxyScheme::into_proxy_scheme)), })) } pub(crate) fn system() -> Proxy { let mut proxy = if cfg!(feature = "__internal_proxy_sys_no_cache") { Proxy::new(Intercept::System(Arc::new(get_sys_proxies( get_from_platform(), )))) } else { Proxy::new(Intercept::System(SYS_PROXIES.clone())) }; proxy.no_proxy = NoProxy::from_env(); proxy } fn new(intercept: Intercept) -> Proxy { Proxy { intercept, no_proxy: None, } } /// Set the `Proxy-Authorization` header using Basic auth. /// /// # Example /// /// ``` /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let proxy = reqwest::Proxy::https("http://localhost:1234")? /// .basic_auth("Aladdin", "open sesame"); /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy { self.intercept.set_basic_auth(username, password); self } /// Set the `Proxy-Authorization` header to a specified value. /// /// # Example /// /// ``` /// # extern crate reqwest; /// # use reqwest::header::*; /// # fn run() -> Result<(), Box> { /// let proxy = reqwest::Proxy::https("http://localhost:1234")? /// .custom_http_auth(HeaderValue::from_static("justletmeinalreadyplease")); /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy { self.intercept.set_custom_http_auth(header_value); self } /// Adds a `No Proxy` exclusion list to this Proxy /// /// # Example /// /// ``` /// # extern crate reqwest; /// # fn run() -> Result<(), Box> { /// let proxy = reqwest::Proxy::https("http://localhost:1234")? /// .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld")); /// # Ok(()) /// # } /// # fn main() {} /// ``` pub fn no_proxy(mut self, no_proxy: Option) -> Proxy { self.no_proxy = no_proxy; self } pub(crate) fn maybe_has_http_auth(&self) -> bool { match &self.intercept { Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(), // Custom *may* match 'http', so assume so. Intercept::Custom(_) => true, Intercept::System(system) => system .get("http") .and_then(|s| s.maybe_http_auth()) .is_some(), Intercept::Https(_) => false, } } pub(crate) fn http_basic_auth(&self, uri: &D) -> Option { match &self.intercept { Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(), Intercept::System(system) => system .get("http") .and_then(|s| s.maybe_http_auth().cloned()), Intercept::Custom(custom) => { custom.call(uri).and_then(|s| s.maybe_http_auth().cloned()) } Intercept::Https(_) => None, } } pub(crate) fn intercept(&self, uri: &D) -> Option { let in_no_proxy = self .no_proxy .as_ref() .map_or(false, |np| np.contains(uri.host())); match self.intercept { Intercept::All(ref u) => { if !in_no_proxy { Some(u.clone()) } else { None } } Intercept::Http(ref u) => { if !in_no_proxy && uri.scheme() == "http" { Some(u.clone()) } else { None } } Intercept::Https(ref u) => { if !in_no_proxy && uri.scheme() == "https" { Some(u.clone()) } else { None } } Intercept::System(ref map) => { if in_no_proxy { None } else { map.get(uri.scheme()).cloned() } } Intercept::Custom(ref custom) => { if !in_no_proxy { custom.call(uri) } else { None } } } } pub(crate) fn is_match(&self, uri: &D) -> bool { match self.intercept { Intercept::All(_) => true, Intercept::Http(_) => uri.scheme() == "http", Intercept::Https(_) => uri.scheme() == "https", Intercept::System(ref map) => map.contains_key(uri.scheme()), Intercept::Custom(ref custom) => custom.call(uri).is_some(), } } } impl fmt::Debug for Proxy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Proxy") .field(&self.intercept) .field(&self.no_proxy) .finish() } } impl NoProxy { /// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set) /// see [self::NoProxy::from_string()] for the string format pub fn from_env() -> Option { let raw = env::var("NO_PROXY") .or_else(|_| env::var("no_proxy")) .unwrap_or_default(); Self::from_string(&raw) } /// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables /// are set) /// The rules are as follows: /// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked /// * If neither environment variable is set, `None` is returned /// * Entries are expected to be comma-separated (whitespace between entries is ignored) /// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size, /// for example "`192.168.1.0/24`"). /// * An entry "`*`" matches all hostnames (this is the only wildcard allowed) /// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com` /// and `.google.com` are equivalent) and would match both that domain AND all subdomains. /// /// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all of the following would match /// (and therefore would bypass the proxy): /// * `http://google.com/` /// * `http://www.google.com/` /// * `http://192.168.1.42/` /// /// The URL `http://notgoogle.com/` would not match. pub fn from_string(no_proxy_list: &str) -> Option { if no_proxy_list.is_empty() { return None; } let mut ips = Vec::new(); let mut domains = Vec::new(); let parts = no_proxy_list.split(',').map(str::trim); for part in parts { match part.parse::() { // If we can parse an IP net or address, then use it, otherwise, assume it is a domain Ok(ip) => ips.push(Ip::Network(ip)), Err(_) => match part.parse::() { Ok(addr) => ips.push(Ip::Address(addr)), Err(_) => domains.push(part.to_owned()), }, } } Some(NoProxy { ips: IpMatcher(ips), domains: DomainMatcher(domains), }) } fn contains(&self, host: &str) -> bool { // According to RFC3986, raw IPv6 hosts will be wrapped in []. So we need to strip those off // the end in order to parse correctly let host = if host.starts_with('[') { let x: &[_] = &['[', ']']; host.trim_matches(x) } else { host }; match host.parse::() { // If we can parse an IP addr, then use it, otherwise, assume it is a domain Ok(ip) => self.ips.contains(ip), Err(_) => self.domains.contains(host), } } } impl IpMatcher { fn contains(&self, addr: IpAddr) -> bool { for ip in &self.0 { match ip { Ip::Address(address) => { if &addr == address { return true; } } Ip::Network(net) => { if net.contains(&addr) { return true; } } } } false } } impl DomainMatcher { // The following links may be useful to understand the origin of these rules: // * https://curl.se/libcurl/c/CURLOPT_NOPROXY.html // * https://github.com/curl/curl/issues/1208 fn contains(&self, domain: &str) -> bool { let domain_len = domain.len(); for d in &self.0 { if d == domain || d.strip_prefix('.') == Some(domain) { return true; } else if domain.ends_with(d) { if d.starts_with('.') { // If the first character of d is a dot, that means the first character of domain // must also be a dot, so we are looking at a subdomain of d and that matches return true; } else if domain.as_bytes().get(domain_len - d.len() - 1) == Some(&b'.') { // Given that d is a prefix of domain, if the prior character in domain is a dot // then that means we must be matching a subdomain of d, and that matches return true; } } else if d == "*" { return true; } } false } } impl ProxyScheme { // To start conservative, keep builders private for now. /// Proxy traffic via the specified URL over HTTP fn http(host: &str) -> crate::Result { Ok(ProxyScheme::Http { auth: None, host: host.parse().map_err(crate::error::builder)?, }) } /// Proxy traffic via the specified URL over HTTPS fn https(host: &str) -> crate::Result { Ok(ProxyScheme::Https { auth: None, host: host.parse().map_err(crate::error::builder)?, }) } /// Proxy traffic via the specified socket address over SOCKS5 /// /// # Note /// /// Current SOCKS5 support is provided via blocking IO. #[cfg(feature = "socks")] fn socks5(addr: SocketAddr) -> crate::Result { Ok(ProxyScheme::Socks5 { addr, auth: None, remote_dns: false, }) } /// Proxy traffic via the specified socket address over SOCKS5H /// /// This differs from SOCKS5 in that DNS resolution is also performed via the proxy. /// /// # Note /// /// Current SOCKS5 support is provided via blocking IO. #[cfg(feature = "socks")] fn socks5h(addr: SocketAddr) -> crate::Result { Ok(ProxyScheme::Socks5 { addr, auth: None, remote_dns: true, }) } /// Use a username and password when connecting to the proxy server fn with_basic_auth, U: Into>( mut self, username: T, password: U, ) -> Self { self.set_basic_auth(username, password); self } fn set_basic_auth, U: Into>(&mut self, username: T, password: U) { match *self { ProxyScheme::Http { ref mut auth, .. } => { let header = encode_basic_auth(&username.into(), &password.into()); *auth = Some(header); } ProxyScheme::Https { ref mut auth, .. } => { let header = encode_basic_auth(&username.into(), &password.into()); *auth = Some(header); } #[cfg(feature = "socks")] ProxyScheme::Socks5 { ref mut auth, .. } => { *auth = Some((username.into(), password.into())); } } } fn set_custom_http_auth(&mut self, header_value: HeaderValue) { match *self { ProxyScheme::Http { ref mut auth, .. } => { *auth = Some(header_value); } ProxyScheme::Https { ref mut auth, .. } => { *auth = Some(header_value); } #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => { panic!("Socks is not supported for this method") } } } fn if_no_auth(mut self, update: &Option) -> Self { match self { ProxyScheme::Http { ref mut auth, .. } => { if auth.is_none() { *auth = update.clone(); } } ProxyScheme::Https { ref mut auth, .. } => { if auth.is_none() { *auth = update.clone(); } } #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => {} } self } /// Convert a URL into a proxy scheme /// /// Supported schemes: HTTP, HTTPS, (SOCKS5, SOCKS5H if `socks` feature is enabled). // Private for now... fn parse(url: Url) -> crate::Result { use url::Position; // Resolve URL to a host and port #[cfg(feature = "socks")] let to_addr = || { let addrs = url .socket_addrs(|| match url.scheme() { "socks5" | "socks5h" => Some(1080), _ => None, }) .map_err(crate::error::builder)?; addrs .into_iter() .next() .ok_or_else(|| crate::error::builder("unknown proxy scheme")) }; let mut scheme = match url.scheme() { "http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?, "https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?, #[cfg(feature = "socks")] "socks5" => Self::socks5(to_addr()?)?, #[cfg(feature = "socks")] "socks5h" => Self::socks5h(to_addr()?)?, _ => return Err(crate::error::builder("unknown proxy scheme")), }; if let Some(pwd) = url.password() { let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy(); let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy(); scheme = scheme.with_basic_auth(decoded_username, decoded_password); } Ok(scheme) } #[cfg(test)] fn scheme(&self) -> &str { match self { ProxyScheme::Http { .. } => "http", ProxyScheme::Https { .. } => "https", #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => "socks5", } } #[cfg(test)] fn host(&self) -> &str { match self { ProxyScheme::Http { host, .. } => host.as_str(), ProxyScheme::Https { host, .. } => host.as_str(), #[cfg(feature = "socks")] ProxyScheme::Socks5 { .. } => panic!("socks5"), } } } impl fmt::Debug for ProxyScheme { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ProxyScheme::Http { auth: _auth, host } => write!(f, "http://{host}"), ProxyScheme::Https { auth: _auth, host } => write!(f, "https://{host}"), #[cfg(feature = "socks")] ProxyScheme::Socks5 { addr, auth: _auth, remote_dns, } => { let h = if *remote_dns { "h" } else { "" }; write!(f, "socks5{h}://{addr}") } } } } type SystemProxyMap = HashMap; #[derive(Clone, Debug)] enum Intercept { All(ProxyScheme), Http(ProxyScheme), Https(ProxyScheme), System(Arc), Custom(Custom), } impl Intercept { fn set_basic_auth(&mut self, username: &str, password: &str) { match self { Intercept::All(ref mut s) | Intercept::Http(ref mut s) | Intercept::Https(ref mut s) => s.set_basic_auth(username, password), Intercept::System(_) => unimplemented!(), Intercept::Custom(ref mut custom) => { let header = encode_basic_auth(username, password); custom.auth = Some(header); } } } fn set_custom_http_auth(&mut self, header_value: HeaderValue) { match self { Intercept::All(ref mut s) | Intercept::Http(ref mut s) | Intercept::Https(ref mut s) => s.set_custom_http_auth(header_value), Intercept::System(_) => unimplemented!(), Intercept::Custom(ref mut custom) => { custom.auth = Some(header_value); } } } } #[derive(Clone)] struct Custom { // This auth only applies if the returned ProxyScheme doesn't have an auth... auth: Option, func: Arc Option> + Send + Sync + 'static>, } impl Custom { fn call(&self, uri: &D) -> Option { let url = format!( "{}://{}{}{}", uri.scheme(), uri.host(), uri.port().map_or("", |_| ":"), uri.port().map_or(String::new(), |p| p.to_string()) ) .parse() .expect("should be valid Url"); (self.func)(&url) .and_then(|result| result.ok()) .map(|scheme| scheme.if_no_auth(&self.auth)) } } impl fmt::Debug for Custom { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("_") } } pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue { crate::util::basic_auth(username, Some(password)) } /// A helper trait to allow testing `Proxy::intercept` without having to /// construct `hyper::client::connect::Destination`s. pub(crate) trait Dst { fn scheme(&self) -> &str; fn host(&self) -> &str; fn port(&self) -> Option; } #[doc(hidden)] impl Dst for Uri { fn scheme(&self) -> &str { self.scheme().expect("Uri should have a scheme").as_str() } fn host(&self) -> &str { Uri::host(self).expect("::host should have a str") } fn port(&self) -> Option { self.port().map(|p| p.as_u16()) } } static SYS_PROXIES: Lazy> = Lazy::new(|| Arc::new(get_sys_proxies(get_from_platform()))); /// Get system proxies information. /// /// All platforms will check for proxy settings via environment variables. /// If those aren't set, platform-wide proxy settings will be looked up on /// Windows and MacOS platforms instead. Errors encountered while discovering /// these settings are ignored. /// /// Returns: /// System proxies information as a hashmap like /// {"http": Url::parse("http://127.0.0.1:80"), "https": Url::parse("https://127.0.0.1:80")} fn get_sys_proxies( #[cfg_attr( not(any(target_os = "windows", target_os = "macos")), allow(unused_variables) )] platform_proxies: Option, ) -> SystemProxyMap { let proxies = get_from_environment(); #[cfg(any(target_os = "windows", target_os = "macos"))] if proxies.is_empty() { // if there are errors in acquiring the platform proxies, // we'll just return an empty HashMap if let Some(platform_proxies) = platform_proxies { return parse_platform_values(platform_proxies); } } proxies } fn insert_proxy(proxies: &mut SystemProxyMap, scheme: impl Into, addr: String) -> bool { if addr.trim().is_empty() { // do not accept empty or whitespace proxy address false } else if let Ok(valid_addr) = addr.into_proxy_scheme() { proxies.insert(scheme.into(), valid_addr); true } else { false } } fn get_from_environment() -> SystemProxyMap { let mut proxies = HashMap::new(); if is_cgi() { if log::log_enabled!(log::Level::Warn) && env::var_os("HTTP_PROXY").is_some() { log::warn!("HTTP_PROXY environment variable ignored in CGI"); } } else if !insert_from_env(&mut proxies, "http", "HTTP_PROXY") { insert_from_env(&mut proxies, "http", "http_proxy"); } if !insert_from_env(&mut proxies, "https", "HTTPS_PROXY") { insert_from_env(&mut proxies, "https", "https_proxy"); } if !(insert_from_env(&mut proxies, "http", "ALL_PROXY") && insert_from_env(&mut proxies, "https", "ALL_PROXY")) { insert_from_env(&mut proxies, "http", "all_proxy"); insert_from_env(&mut proxies, "https", "all_proxy"); } proxies } fn insert_from_env(proxies: &mut SystemProxyMap, scheme: &str, var: &str) -> bool { if let Ok(val) = env::var(var) { insert_proxy(proxies, scheme, val) } else { false } } /// Check if we are being executed in a CGI context. /// /// If so, a malicious client can send the `Proxy:` header, and it will /// be in the `HTTP_PROXY` env var. So we don't use it :) fn is_cgi() -> bool { env::var_os("REQUEST_METHOD").is_some() } #[cfg(target_os = "windows")] fn get_from_platform_impl() -> Result, Box> { let hkcu = RegKey::predef(HKEY_CURRENT_USER); let internet_setting: RegKey = hkcu.open_subkey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings")?; // ensure the proxy is enable, if the value doesn't exist, an error will returned. let proxy_enable: u32 = internet_setting.get_value("ProxyEnable")?; let proxy_server: String = internet_setting.get_value("ProxyServer")?; Ok((proxy_enable == 1).then_some(proxy_server)) } #[cfg(target_os = "macos")] fn parse_setting_from_dynamic_store( proxies_map: &CFDictionary, enabled_key: CFStringRef, host_key: CFStringRef, port_key: CFStringRef, scheme: &str, ) -> Option { let proxy_enabled = proxies_map .find(enabled_key) .and_then(|flag| flag.downcast::()) .and_then(|flag| flag.to_i32()) .unwrap_or(0) == 1; if proxy_enabled { let proxy_host = proxies_map .find(host_key) .and_then(|host| host.downcast::()) .map(|host| host.to_string()); let proxy_port = proxies_map .find(port_key) .and_then(|port| port.downcast::()) .and_then(|port| port.to_i32()); return match (proxy_host, proxy_port) { (Some(proxy_host), Some(proxy_port)) => { Some(format!("{scheme}={proxy_host}:{proxy_port}")) } (Some(proxy_host), None) => Some(format!("{scheme}={proxy_host}")), (None, Some(_)) => None, (None, None) => None, }; } None } #[cfg(target_os = "macos")] fn get_from_platform_impl() -> Result, Box> { let store = SCDynamicStoreBuilder::new("reqwest").build(); let proxies_map = if let Some(proxies_map) = store.get_proxies() { proxies_map } else { return Ok(None); }; let http_proxy_config = parse_setting_from_dynamic_store( &proxies_map, unsafe { kSCPropNetProxiesHTTPEnable }, unsafe { kSCPropNetProxiesHTTPProxy }, unsafe { kSCPropNetProxiesHTTPPort }, "http", ); let https_proxy_config = parse_setting_from_dynamic_store( &proxies_map, unsafe { kSCPropNetProxiesHTTPSEnable }, unsafe { kSCPropNetProxiesHTTPSProxy }, unsafe { kSCPropNetProxiesHTTPSPort }, "https", ); match http_proxy_config.as_ref().zip(https_proxy_config.as_ref()) { Some((http_config, https_config)) => Ok(Some(format!("{http_config};{https_config}"))), None => Ok(http_proxy_config.or(https_proxy_config)), } } #[cfg(any(target_os = "windows", target_os = "macos"))] fn get_from_platform() -> Option { get_from_platform_impl().ok().flatten() } #[cfg(not(any(target_os = "windows", target_os = "macos")))] fn get_from_platform() -> Option { None } #[cfg(any(target_os = "windows", target_os = "macos"))] fn parse_platform_values_impl(platform_values: String) -> SystemProxyMap { let mut proxies = HashMap::new(); if platform_values.contains("=") { // per-protocol settings. for p in platform_values.split(";") { let protocol_parts: Vec<&str> = p.split("=").collect(); match protocol_parts.as_slice() { [protocol, address] => { // If address doesn't specify an explicit protocol as protocol://address // then default to HTTP let address = if extract_type_prefix(*address).is_some() { String::from(*address) } else { format!("http://{address}") }; insert_proxy(&mut proxies, *protocol, address); } _ => { // Contains invalid protocol setting, just break the loop // And make proxies to be empty. proxies.clear(); break; } } } } else { if let Some(scheme) = extract_type_prefix(&platform_values) { // Explicit protocol has been specified insert_proxy(&mut proxies, scheme, platform_values.to_owned()); } else { // No explicit protocol has been specified, default to HTTP insert_proxy(&mut proxies, "http", format!("http://{platform_values}")); insert_proxy(&mut proxies, "https", format!("http://{platform_values}")); } } proxies } /// Extract the protocol from the given address, if present /// For example, "https://example.com" will return Some("https") #[cfg(any(target_os = "windows", target_os = "macos"))] fn extract_type_prefix(address: &str) -> Option<&str> { if let Some(indice) = address.find("://") { if indice == 0 { None } else { let prefix = &address[..indice]; let contains_banned = prefix.contains(|c| c == ':' || c == '/'); if !contains_banned { Some(prefix) } else { None } } } else { None } } #[cfg(any(target_os = "windows", target_os = "macos"))] fn parse_platform_values(platform_values: String) -> SystemProxyMap { parse_platform_values_impl(platform_values) } #[cfg(test)] mod tests { use super::*; use once_cell::sync::Lazy; use std::sync::Mutex; impl Dst for Url { fn scheme(&self) -> &str { Url::scheme(self) } fn host(&self) -> &str { Url::host_str(self).expect("::host should have a str") } fn port(&self) -> Option { Url::port(self) } } fn url(s: &str) -> Url { s.parse().unwrap() } fn intercepted_uri(p: &Proxy, s: &str) -> Uri { let (scheme, host) = match p.intercept(&url(s)).unwrap() { ProxyScheme::Http { host, .. } => ("http", host), ProxyScheme::Https { host, .. } => ("https", host), #[cfg(feature = "socks")] _ => panic!("intercepted as socks"), }; http::Uri::builder() .scheme(scheme) .authority(host) .path_and_query("/") .build() .expect("intercepted_uri") } #[test] fn test_http() { let target = "http://example.domain/"; let p = Proxy::http(target).unwrap(); let http = "http://hyper.rs"; let other = "https://hyper.rs"; assert_eq!(intercepted_uri(&p, http), target); assert!(p.intercept(&url(other)).is_none()); } #[test] fn test_https() { let target = "http://example.domain/"; let p = Proxy::https(target).unwrap(); let http = "http://hyper.rs"; let other = "https://hyper.rs"; assert!(p.intercept(&url(http)).is_none()); assert_eq!(intercepted_uri(&p, other), target); } #[test] fn test_all() { let target = "http://example.domain/"; let p = Proxy::all(target).unwrap(); let http = "http://hyper.rs"; let https = "https://hyper.rs"; let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs"; assert_eq!(intercepted_uri(&p, http), target); assert_eq!(intercepted_uri(&p, https), target); assert_eq!(intercepted_uri(&p, other), target); } #[test] fn test_custom() { let target1 = "http://example.domain/"; let target2 = "https://example.domain/"; let p = Proxy::custom(move |url| { if url.host_str() == Some("hyper.rs") { target1.parse().ok() } else if url.scheme() == "http" { target2.parse().ok() } else { None:: } }); let http = "http://seanmonstar.com"; let https = "https://hyper.rs"; let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com"; assert_eq!(intercepted_uri(&p, http), target2); assert_eq!(intercepted_uri(&p, https), target1); assert!(p.intercept(&url(other)).is_none()); } #[test] fn test_proxy_scheme_parse() { let ps = "http://foo:bar@localhost:1239".into_proxy_scheme().unwrap(); match ps { ProxyScheme::Http { auth, host } => { assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar")); assert_eq!(host, "localhost:1239"); } other => panic!("unexpected: {other:?}"), } } #[test] fn test_proxy_scheme_ip_address_default_http() { let ps = "192.168.1.1:8888".into_proxy_scheme().unwrap(); match ps { ProxyScheme::Http { auth, host } => { assert!(auth.is_none()); assert_eq!(host, "192.168.1.1:8888"); } other => panic!("unexpected: {other:?}"), } } #[test] fn test_proxy_scheme_parse_default_http_with_auth() { // this should fail because `foo` is interpreted as the scheme and no host can be found let ps = "foo:bar@localhost:1239".into_proxy_scheme().unwrap(); match ps { ProxyScheme::Http { auth, host } => { assert_eq!(auth.unwrap(), encode_basic_auth("foo", "bar")); assert_eq!(host, "localhost:1239"); } other => panic!("unexpected: {other:?}"), } } #[test] fn test_domain_matcher() { let domains = vec![".foo.bar".into(), "bar.foo".into()]; let matcher = DomainMatcher(domains); // domains match with leading `.` assert!(matcher.contains("foo.bar")); // subdomains match with leading `.` assert!(matcher.contains("www.foo.bar")); // domains match with no leading `.` assert!(matcher.contains("bar.foo")); // subdomains match with no leading `.` assert!(matcher.contains("www.bar.foo")); // non-subdomain string prefixes don't match assert!(!matcher.contains("notfoo.bar")); assert!(!matcher.contains("notbar.foo")); } // Smallest possible content for a mutex struct MutexInner; static ENVLOCK: Lazy> = Lazy::new(|| Mutex::new(MutexInner)); #[test] fn test_get_sys_proxies_parsing() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); // save system setting first. let _g1 = env_guard("HTTP_PROXY"); let _g2 = env_guard("http_proxy"); let _g3 = env_guard("ALL_PROXY"); // Mock ENV, get the results, before doing assertions // to avoid assert! -> panic! -> Mutex Poisoned. let baseline_proxies = get_sys_proxies(None); // the system proxy setting url is invalid. env::set_var("http_proxy", "file://123465"); let invalid_proxies = get_sys_proxies(None); // set valid proxy env::set_var("http_proxy", "127.0.0.1/"); let valid_proxies = get_sys_proxies(None); // set valid ALL_PROXY env::set_var("ALL_PROXY", "127.0.0.2/"); let all_proxies = get_sys_proxies(None); // reset user setting when guards drop drop(_g1); drop(_g2); // Let other threads run now drop(_lock); assert!(!baseline_proxies.contains_key("http")); assert!(!invalid_proxies.contains_key("http")); let p = &valid_proxies["http"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.1"); assert_eq!(all_proxies.len(), 2); assert!(all_proxies.values().all(|p| p.host() == "127.0.0.2")); } #[cfg(any(target_os = "windows", target_os = "macos"))] #[test] fn test_get_sys_proxies_registry_parsing() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); // save system setting first. let _g1 = env_guard("HTTP_PROXY"); let _g2 = env_guard("http_proxy"); // Mock ENV, get the results, before doing assertions // to avoid assert! -> panic! -> Mutex Poisoned. let baseline_proxies = get_sys_proxies(None); // set valid proxy let valid_proxies = get_sys_proxies(Some(String::from("http://127.0.0.1/"))); let valid_proxies_no_scheme = get_sys_proxies(Some(String::from("127.0.0.1"))); let valid_proxies_explicit_https = get_sys_proxies(Some(String::from("https://127.0.0.1/"))); let multiple_proxies = get_sys_proxies(Some(String::from( "http=127.0.0.1:8888;https=127.0.0.2:8888", ))); let multiple_proxies_explicit_scheme = get_sys_proxies(Some(String::from( "http=http://127.0.0.1:8888;https=https://127.0.0.2:8888", ))); // reset user setting when guards drop drop(_g1); drop(_g2); // Let other threads run now drop(_lock); assert_eq!(baseline_proxies.contains_key("http"), false); let p = &valid_proxies["http"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.1"); let p = &valid_proxies_no_scheme["http"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.1"); let p = &valid_proxies_no_scheme["https"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.1"); let p = &valid_proxies_explicit_https["https"]; assert_eq!(p.scheme(), "https"); assert_eq!(p.host(), "127.0.0.1"); let p = &multiple_proxies["http"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.1:8888"); let p = &multiple_proxies["https"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.2:8888"); let p = &multiple_proxies_explicit_scheme["http"]; assert_eq!(p.scheme(), "http"); assert_eq!(p.host(), "127.0.0.1:8888"); let p = &multiple_proxies_explicit_scheme["https"]; assert_eq!(p.scheme(), "https"); assert_eq!(p.host(), "127.0.0.2:8888"); } #[test] fn test_get_sys_proxies_in_cgi() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); // save system setting first. let _g1 = env_guard("REQUEST_METHOD"); let _g2 = env_guard("HTTP_PROXY"); // Mock ENV, get the results, before doing assertions // to avoid assert! -> panic! -> Mutex Poisoned. env::set_var("HTTP_PROXY", "http://evil/"); let baseline_proxies = get_sys_proxies(None); // set like we're in CGI env::set_var("REQUEST_METHOD", "GET"); let cgi_proxies = get_sys_proxies(None); // reset user setting when guards drop drop(_g1); drop(_g2); // Let other threads run now drop(_lock); // not in CGI yet assert_eq!(baseline_proxies["http"].host(), "evil"); // In CGI assert!(!cgi_proxies.contains_key("http")); } #[test] fn test_sys_no_proxy() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); // save system setting first. let _g1 = env_guard("HTTP_PROXY"); let _g2 = env_guard("NO_PROXY"); let target = "http://example.domain/"; env::set_var("HTTP_PROXY", target); env::set_var( "NO_PROXY", ".foo.bar, bar.baz,10.42.1.1/24,::1,10.124.7.8,2001::/17", ); // Manually construct this so we aren't use the cache let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None)))); p.no_proxy = NoProxy::from_env(); // random url, not in no_proxy assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target); // make sure that random non-subdomain string prefixes don't match assert_eq!(intercepted_uri(&p, "http://notfoo.bar"), target); // make sure that random non-subdomain string prefixes don't match assert_eq!(intercepted_uri(&p, "http://notbar.baz"), target); // ipv4 address out of range assert_eq!(intercepted_uri(&p, "http://10.43.1.1"), target); // ipv4 address out of range assert_eq!(intercepted_uri(&p, "http://10.124.7.7"), target); // ipv6 address out of range assert_eq!(intercepted_uri(&p, "http://[ffff:db8:a0b:12f0::1]"), target); // ipv6 address out of range assert_eq!(intercepted_uri(&p, "http://[2005:db8:a0b:12f0::1]"), target); // make sure subdomains (with leading .) match assert!(p.intercept(&url("http://hello.foo.bar")).is_none()); // make sure exact matches (without leading .) match (also makes sure spaces between entries work) assert!(p.intercept(&url("http://bar.baz")).is_none()); // check case sensitivity assert!(p.intercept(&url("http://BAR.baz")).is_none()); // make sure subdomains (without leading . in no_proxy) match assert!(p.intercept(&url("http://foo.bar.baz")).is_none()); // make sure subdomains (without leading . in no_proxy) match - this differs from cURL assert!(p.intercept(&url("http://foo.bar")).is_none()); // ipv4 address match within range assert!(p.intercept(&url("http://10.42.1.100")).is_none()); // ipv6 address exact match assert!(p.intercept(&url("http://[::1]")).is_none()); // ipv6 address match within range assert!(p.intercept(&url("http://[2001:db8:a0b:12f0::1]")).is_none()); // ipv4 address exact match assert!(p.intercept(&url("http://10.124.7.8")).is_none()); // reset user setting when guards drop drop(_g1); drop(_g2); // Let other threads run now drop(_lock); } #[test] fn test_proxy_no_proxy_interception_for_proxy_types() { let proxy_url = "http://example.domain/"; let no_proxy = ".no.proxy.tld"; // test all proxy interception let p = Proxy::all(proxy_url) .unwrap() .no_proxy(NoProxy::from_string(no_proxy)); // random url, not in no_proxy assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url); // positive match for no proxy assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none()); // test http proxy interception let p = Proxy::http(proxy_url) .unwrap() .no_proxy(NoProxy::from_string(no_proxy)); // random url, not in no_proxy assert_eq!(intercepted_uri(&p, "http://hyper.rs"), proxy_url); // positive match for no proxy assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none()); // should not be intercepted due to scheme assert!(p.intercept(&url("https://hyper.rs")).is_none()); // test https proxy interception let p = Proxy::https(proxy_url) .unwrap() .no_proxy(NoProxy::from_string(no_proxy)); // random url, not in no_proxy assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url); // positive match for no proxy assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none()); // should not be intercepted due to scheme assert!(p.intercept(&url("http://hyper.rs")).is_none()); // test custom proxy interception let p = Proxy::custom(move |_url| Some(proxy_url)).no_proxy(NoProxy::from_string(no_proxy)); // random url, not in no_proxy assert_eq!(intercepted_uri(&p, "https://hyper.rs"), proxy_url); // positive match for no proxy assert!(p.intercept(&url("https://hello.no.proxy.tld")).is_none()); assert!(p.intercept(&url("http://hello.no.proxy.tld")).is_none()); } #[test] fn test_wildcard_sys_no_proxy() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); // save system setting first. let _g1 = env_guard("HTTP_PROXY"); let _g2 = env_guard("NO_PROXY"); let target = "http://example.domain/"; env::set_var("HTTP_PROXY", target); env::set_var("NO_PROXY", "*"); // Manually construct this so we aren't use the cache let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None)))); p.no_proxy = NoProxy::from_env(); assert!(p.intercept(&url("http://foo.bar")).is_none()); // reset user setting when guards drop drop(_g1); drop(_g2); // Let other threads run now drop(_lock); } #[test] fn test_empty_sys_no_proxy() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); // save system setting first. let _g1 = env_guard("HTTP_PROXY"); let _g2 = env_guard("NO_PROXY"); let target = "http://example.domain/"; env::set_var("HTTP_PROXY", target); env::set_var("NO_PROXY", ","); // Manually construct this so we aren't use the cache let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None)))); p.no_proxy = NoProxy::from_env(); // everything should go through proxy, "effectively" nothing is in no_proxy assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target); // reset user setting when guards drop drop(_g1); drop(_g2); // Let other threads run now drop(_lock); } #[test] fn test_no_proxy_load() { // Stop other threads from modifying process-global ENV while we are. let _lock = ENVLOCK.lock(); let _g1 = env_guard("no_proxy"); let domain = "lower.case"; env::set_var("no_proxy", domain); // Manually construct this so we aren't use the cache let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None)))); p.no_proxy = NoProxy::from_env(); assert_eq!( p.no_proxy.expect("should have a no proxy set").domains.0[0], domain ); env::remove_var("no_proxy"); let _g2 = env_guard("NO_PROXY"); let domain = "upper.case"; env::set_var("NO_PROXY", domain); // Manually construct this so we aren't use the cache let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None)))); p.no_proxy = NoProxy::from_env(); assert_eq!( p.no_proxy.expect("should have a no proxy set").domains.0[0], domain ); let _g3 = env_guard("HTTP_PROXY"); env::remove_var("NO_PROXY"); env::remove_var("no_proxy"); let target = "http://example.domain/"; env::set_var("HTTP_PROXY", target); // Manually construct this so we aren't use the cache let mut p = Proxy::new(Intercept::System(Arc::new(get_sys_proxies(None)))); p.no_proxy = NoProxy::from_env(); assert!(p.no_proxy.is_none(), "NoProxy shouldn't have been created"); assert_eq!(intercepted_uri(&p, "http://hyper.rs"), target); // reset user setting when guards drop drop(_g1); drop(_g2); drop(_g3); // Let other threads run now drop(_lock); } #[cfg(any(target_os = "windows", target_os = "macos"))] #[test] fn test_type_prefix_extraction() { assert!(extract_type_prefix("test").is_none()); assert!(extract_type_prefix("://test").is_none()); assert!(extract_type_prefix("some:prefix://test").is_none()); assert!(extract_type_prefix("some/prefix://test").is_none()); assert_eq!(extract_type_prefix("http://test").unwrap(), "http"); assert_eq!(extract_type_prefix("a://test").unwrap(), "a"); } /// Guard an environment variable, resetting it to the original value /// when dropped. fn env_guard(name: impl Into) -> EnvGuard { let name = name.into(); let orig_val = env::var(&name).ok(); env::remove_var(&name); EnvGuard { name, orig_val } } struct EnvGuard { name: String, orig_val: Option, } impl Drop for EnvGuard { fn drop(&mut self) { if let Some(val) = self.orig_val.take() { env::set_var(&self.name, val); } else { env::remove_var(&self.name); } } } #[test] fn test_has_http_auth() { let http_proxy_with_auth = Proxy { intercept: Intercept::Http(ProxyScheme::Http { auth: Some(HeaderValue::from_static("auth1")), host: http::uri::Authority::from_static("authority"), }), no_proxy: None, }; assert!(http_proxy_with_auth.maybe_has_http_auth()); assert_eq!( http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")), Some(HeaderValue::from_static("auth1")) ); let http_proxy_without_auth = Proxy { intercept: Intercept::Http(ProxyScheme::Http { auth: None, host: http::uri::Authority::from_static("authority"), }), no_proxy: None, }; assert!(!http_proxy_without_auth.maybe_has_http_auth()); assert_eq!( http_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")), None ); let https_proxy_with_auth = Proxy { intercept: Intercept::Http(ProxyScheme::Https { auth: Some(HeaderValue::from_static("auth2")), host: http::uri::Authority::from_static("authority"), }), no_proxy: None, }; assert!(https_proxy_with_auth.maybe_has_http_auth()); assert_eq!( https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")), Some(HeaderValue::from_static("auth2")) ); let all_http_proxy_with_auth = Proxy { intercept: Intercept::All(ProxyScheme::Http { auth: Some(HeaderValue::from_static("auth3")), host: http::uri::Authority::from_static("authority"), }), no_proxy: None, }; assert!(all_http_proxy_with_auth.maybe_has_http_auth()); assert_eq!( all_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")), Some(HeaderValue::from_static("auth3")) ); let all_https_proxy_with_auth = Proxy { intercept: Intercept::All(ProxyScheme::Https { auth: Some(HeaderValue::from_static("auth4")), host: http::uri::Authority::from_static("authority"), }), no_proxy: None, }; assert!(all_https_proxy_with_auth.maybe_has_http_auth()); assert_eq!( all_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")), Some(HeaderValue::from_static("auth4")) ); let all_https_proxy_without_auth = Proxy { intercept: Intercept::All(ProxyScheme::Https { auth: None, host: http::uri::Authority::from_static("authority"), }), no_proxy: None, }; assert!(!all_https_proxy_without_auth.maybe_has_http_auth()); assert_eq!( all_https_proxy_without_auth.http_basic_auth(&Uri::from_static("http://example.com")), None ); let system_http_proxy_with_auth = Proxy { intercept: Intercept::System(Arc::new({ let mut m = HashMap::new(); m.insert( "http".into(), ProxyScheme::Http { auth: Some(HeaderValue::from_static("auth5")), host: http::uri::Authority::from_static("authority"), }, ); m })), no_proxy: None, }; assert!(system_http_proxy_with_auth.maybe_has_http_auth()); assert_eq!( system_http_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")), Some(HeaderValue::from_static("auth5")) ); let system_https_proxy_with_auth = Proxy { intercept: Intercept::System(Arc::new({ let mut m = HashMap::new(); m.insert( "https".into(), ProxyScheme::Https { auth: Some(HeaderValue::from_static("auth6")), host: http::uri::Authority::from_static("authority"), }, ); m })), no_proxy: None, }; assert!(!system_https_proxy_with_auth.maybe_has_http_auth()); assert_eq!( system_https_proxy_with_auth.http_basic_auth(&Uri::from_static("http://example.com")), None ); } } #[cfg(test)] mod test { mod into_proxy_scheme { use crate::Proxy; use std::error::Error; use std::mem::discriminant; fn includes(haystack: &crate::error::Error, needle: url::ParseError) -> bool { let mut source = haystack.source(); while let Some(error) = source { if let Some(parse_error) = error.downcast_ref::() { if discriminant(parse_error) == discriminant(&needle) { return true; } } source = error.source(); } false } fn check_parse_error(url: &str, needle: url::ParseError) { let error = Proxy::http(url).unwrap_err(); if !includes(&error, needle) { panic!("{needle:?} expected; {error:?}, {error} found"); } } mod when_scheme_missing { mod and_url_is_valid { use crate::Proxy; #[test] fn lookback_works() { let _ = Proxy::http("127.0.0.1").unwrap(); } #[test] fn loopback_port_works() { let _ = Proxy::http("127.0.0.1:8080").unwrap(); } #[test] fn loopback_username_works() { let _ = Proxy::http("username@127.0.0.1").unwrap(); } #[test] fn loopback_username_password_works() { let _ = Proxy::http("username:password@127.0.0.1").unwrap(); } #[test] fn loopback_username_password_port_works() { let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap(); } #[test] fn domain_works() { let _ = Proxy::http("proxy.example.com").unwrap(); } #[test] fn domain_port_works() { let _ = Proxy::http("proxy.example.com:8080").unwrap(); } #[test] fn domain_username_works() { let _ = Proxy::http("username@proxy.example.com").unwrap(); } #[test] fn domain_username_password_works() { let _ = Proxy::http("username:password@proxy.example.com").unwrap(); } #[test] fn domain_username_password_port_works() { let _ = Proxy::http("ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080").unwrap(); } } mod and_url_has_bad { use super::super::check_parse_error; #[test] fn host() { check_parse_error("username@", url::ParseError::RelativeUrlWithoutBase); } #[test] fn idna_encoding() { check_parse_error("xn---", url::ParseError::RelativeUrlWithoutBase); } #[test] fn port() { check_parse_error("127.0.0.1:808080", url::ParseError::RelativeUrlWithoutBase); } #[test] fn ip_v4_address() { check_parse_error("421.627.718.469", url::ParseError::RelativeUrlWithoutBase); } #[test] fn ip_v6_address() { check_parse_error( "[56FE::2159:5BBC::6594]", url::ParseError::RelativeUrlWithoutBase, ); } #[test] fn invalid_domain_character() { check_parse_error("abc 123", url::ParseError::RelativeUrlWithoutBase); } } } mod when_scheme_present { mod and_url_is_valid { use crate::Proxy; #[test] fn loopback_works() { let _ = Proxy::http("http://127.0.0.1").unwrap(); } #[test] fn loopback_port_works() { let _ = Proxy::http("https://127.0.0.1:8080").unwrap(); } #[test] fn loopback_username_works() { let _ = Proxy::http("http://username@127.0.0.1").unwrap(); } #[test] fn loopback_username_password_works() { let _ = Proxy::http("https://username:password@127.0.0.1").unwrap(); } #[test] fn loopback_username_password_port_works() { let _ = Proxy::http("http://ldap%5Cgremlin:pass%3Bword@127.0.0.1:8080").unwrap(); } #[test] fn domain_works() { let _ = Proxy::http("https://proxy.example.com").unwrap(); } #[test] fn domain_port_works() { let _ = Proxy::http("http://proxy.example.com:8080").unwrap(); } #[test] fn domain_username_works() { let _ = Proxy::http("https://username@proxy.example.com").unwrap(); } #[test] fn domain_username_password_works() { let _ = Proxy::http("http://username:password@proxy.example.com").unwrap(); } #[test] fn domain_username_password_port_works() { let _ = Proxy::http("https://ldap%5Cgremlin:pass%3Bword@proxy.example.com:8080") .unwrap(); } } mod and_url_has_bad { use super::super::check_parse_error; #[test] fn host() { check_parse_error("http://username@", url::ParseError::EmptyHost); } #[test] fn idna_encoding() { check_parse_error("http://xn---", url::ParseError::IdnaError); } #[test] fn port() { check_parse_error("http://127.0.0.1:808080", url::ParseError::InvalidPort); } #[test] fn ip_v4_address() { check_parse_error( "http://421.627.718.469", url::ParseError::InvalidIpv4Address, ); } #[test] fn ip_v6_address() { check_parse_error( "http://[56FE::2159:5BBC::6594]", url::ParseError::InvalidIpv6Address, ); } #[test] fn invalid_domain_character() { check_parse_error("http://abc 123/", url::ParseError::InvalidDomainCharacter); } } } } } reqwest-0.11.27/src/redirect.rs000064400000000000000000000232501046102023000144260ustar 00000000000000//! Redirect Handling //! //! By default, a `Client` will automatically handle HTTP redirects, having a //! maximum redirect chain of 10 hops. To customize this behavior, a //! `redirect::Policy` can be used with a `ClientBuilder`. use std::error::Error as StdError; use std::fmt; use crate::header::{HeaderMap, AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, WWW_AUTHENTICATE}; use hyper::StatusCode; use crate::Url; /// A type that controls the policy on how to handle the following of redirects. /// /// The default value will catch redirect loops, and has a maximum of 10 /// redirects it will follow in a chain before returning an error. /// /// - `limited` can be used have the same as the default behavior, but adjust /// the allowed maximum redirect hops in a chain. /// - `none` can be used to disable all redirect behavior. /// - `custom` can be used to create a customized policy. pub struct Policy { inner: PolicyKind, } /// A type that holds information on the next request and previous requests /// in redirect chain. #[derive(Debug)] pub struct Attempt<'a> { status: StatusCode, next: &'a Url, previous: &'a [Url], } /// An action to perform when a redirect status code is found. #[derive(Debug)] pub struct Action { inner: ActionKind, } impl Policy { /// Create a `Policy` with a maximum number of redirects. /// /// An `Error` will be returned if the max is reached. pub fn limited(max: usize) -> Self { Self { inner: PolicyKind::Limit(max), } } /// Create a `Policy` that does not follow any redirect. pub fn none() -> Self { Self { inner: PolicyKind::None, } } /// Create a custom `Policy` using the passed function. /// /// # Note /// /// The default `Policy` handles a maximum loop /// chain, but the custom variant does not do that for you automatically. /// The custom policy should have some way of handling those. /// /// Information on the next request and previous requests can be found /// on the [`Attempt`] argument passed to the closure. /// /// Actions can be conveniently created from methods on the /// [`Attempt`]. /// /// # Example /// /// ```rust /// # use reqwest::{Error, redirect}; /// # /// # fn run() -> Result<(), Error> { /// let custom = redirect::Policy::custom(|attempt| { /// if attempt.previous().len() > 5 { /// attempt.error("too many redirects") /// } else if attempt.url().host_str() == Some("example.domain") { /// // prevent redirects to 'example.domain' /// attempt.stop() /// } else { /// attempt.follow() /// } /// }); /// let client = reqwest::Client::builder() /// .redirect(custom) /// .build()?; /// # Ok(()) /// # } /// ``` /// /// [`Attempt`]: struct.Attempt.html pub fn custom(policy: T) -> Self where T: Fn(Attempt) -> Action + Send + Sync + 'static, { Self { inner: PolicyKind::Custom(Box::new(policy)), } } /// Apply this policy to a given [`Attempt`] to produce a [`Action`]. /// /// # Note /// /// This method can be used together with `Policy::custom()` /// to construct one `Policy` that wraps another. /// /// # Example /// /// ```rust /// # use reqwest::{Error, redirect}; /// # /// # fn run() -> Result<(), Error> { /// let custom = redirect::Policy::custom(|attempt| { /// eprintln!("{}, Location: {:?}", attempt.status(), attempt.url()); /// redirect::Policy::default().redirect(attempt) /// }); /// # Ok(()) /// # } /// ``` pub fn redirect(&self, attempt: Attempt) -> Action { match self.inner { PolicyKind::Custom(ref custom) => custom(attempt), PolicyKind::Limit(max) => { if attempt.previous.len() >= max { attempt.error(TooManyRedirects) } else { attempt.follow() } } PolicyKind::None => attempt.stop(), } } pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> ActionKind { self.redirect(Attempt { status, next, previous, }) .inner } pub(crate) fn is_default(&self) -> bool { matches!(self.inner, PolicyKind::Limit(10)) } } impl Default for Policy { fn default() -> Policy { // Keep `is_default` in sync Policy::limited(10) } } impl<'a> Attempt<'a> { /// Get the type of redirect. pub fn status(&self) -> StatusCode { self.status } /// Get the next URL to redirect to. pub fn url(&self) -> &Url { self.next } /// Get the list of previous URLs that have already been requested in this chain. pub fn previous(&self) -> &[Url] { self.previous } /// Returns an action meaning reqwest should follow the next URL. pub fn follow(self) -> Action { Action { inner: ActionKind::Follow, } } /// Returns an action meaning reqwest should not follow the next URL. /// /// The 30x response will be returned as the `Ok` result. pub fn stop(self) -> Action { Action { inner: ActionKind::Stop, } } /// Returns an action failing the redirect with an error. /// /// The `Error` will be returned for the result of the sent request. pub fn error>>(self, error: E) -> Action { Action { inner: ActionKind::Error(error.into()), } } } enum PolicyKind { Custom(Box Action + Send + Sync + 'static>), Limit(usize), None, } impl fmt::Debug for Policy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Policy").field(&self.inner).finish() } } impl fmt::Debug for PolicyKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { PolicyKind::Custom(..) => f.pad("Custom"), PolicyKind::Limit(max) => f.debug_tuple("Limit").field(&max).finish(), PolicyKind::None => f.pad("None"), } } } // pub(crate) #[derive(Debug)] pub(crate) enum ActionKind { Follow, Stop, Error(Box), } pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) { if let Some(previous) = previous.last() { let cross_host = next.host_str() != previous.host_str() || next.port_or_known_default() != previous.port_or_known_default(); if cross_host { headers.remove(AUTHORIZATION); headers.remove(COOKIE); headers.remove("cookie2"); headers.remove(PROXY_AUTHORIZATION); headers.remove(WWW_AUTHENTICATE); } } } #[derive(Debug)] struct TooManyRedirects; impl fmt::Display for TooManyRedirects { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("too many redirects") } } impl StdError for TooManyRedirects {} #[test] fn test_redirect_policy_limit() { let policy = Policy::default(); let next = Url::parse("http://x.y/z").unwrap(); let mut previous = (0..9) .map(|i| Url::parse(&format!("http://a.b/c/{i}")).unwrap()) .collect::>(); match policy.check(StatusCode::FOUND, &next, &previous) { ActionKind::Follow => (), other => panic!("unexpected {other:?}"), } previous.push(Url::parse("http://a.b.d/e/33").unwrap()); match policy.check(StatusCode::FOUND, &next, &previous) { ActionKind::Error(err) if err.is::() => (), other => panic!("unexpected {other:?}"), } } #[test] fn test_redirect_policy_limit_to_0() { let policy = Policy::limited(0); let next = Url::parse("http://x.y/z").unwrap(); let previous = vec![Url::parse("http://a.b/c").unwrap()]; match policy.check(StatusCode::FOUND, &next, &previous) { ActionKind::Error(err) if err.is::() => (), other => panic!("unexpected {other:?}"), } } #[test] fn test_redirect_policy_custom() { let policy = Policy::custom(|attempt| { if attempt.url().host_str() == Some("foo") { attempt.stop() } else { attempt.follow() } }); let next = Url::parse("http://bar/baz").unwrap(); match policy.check(StatusCode::FOUND, &next, &[]) { ActionKind::Follow => (), other => panic!("unexpected {other:?}"), } let next = Url::parse("http://foo/baz").unwrap(); match policy.check(StatusCode::FOUND, &next, &[]) { ActionKind::Stop => (), other => panic!("unexpected {other:?}"), } } #[test] fn test_remove_sensitive_headers() { use hyper::header::{HeaderValue, ACCEPT, AUTHORIZATION, COOKIE}; let mut headers = HeaderMap::new(); headers.insert(ACCEPT, HeaderValue::from_static("*/*")); headers.insert(AUTHORIZATION, HeaderValue::from_static("let me in")); headers.insert(COOKIE, HeaderValue::from_static("foo=bar")); let next = Url::parse("http://initial-domain.com/path").unwrap(); let mut prev = vec![Url::parse("http://initial-domain.com/new_path").unwrap()]; let mut filtered_headers = headers.clone(); remove_sensitive_headers(&mut headers, &next, &prev); assert_eq!(headers, filtered_headers); prev.push(Url::parse("http://new-domain.com/path").unwrap()); filtered_headers.remove(AUTHORIZATION); filtered_headers.remove(COOKIE); remove_sensitive_headers(&mut headers, &next, &prev); assert_eq!(headers, filtered_headers); } reqwest-0.11.27/src/response.rs000064400000000000000000000020421046102023000144570ustar 00000000000000use url::Url; #[derive(Debug, Clone, PartialEq)] pub(crate) struct ResponseUrl(pub Url); /// Extension trait for http::response::Builder objects /// /// Allows the user to add a `Url` to the http::Response pub trait ResponseBuilderExt { /// A builder method for the `http::response::Builder` type that allows the user to add a `Url` /// to the `http::Response` fn url(self, url: Url) -> Self; } impl ResponseBuilderExt for http::response::Builder { fn url(self, url: Url) -> Self { self.extension(ResponseUrl(url)) } } #[cfg(test)] mod tests { use super::{ResponseBuilderExt, ResponseUrl}; use http::response::Builder; use url::Url; #[test] fn test_response_builder_ext() { let url = Url::parse("http://example.com").unwrap(); let response = Builder::new() .status(200) .url(url.clone()) .body(()) .unwrap(); assert_eq!( response.extensions().get::(), Some(&ResponseUrl(url)) ); } } reqwest-0.11.27/src/tls.rs000064400000000000000000000512661046102023000134370ustar 00000000000000//! TLS configuration and types //! //! A `Client` will use transport layer security (TLS) by default to connect to //! HTTPS destinations. //! //! # Backends //! //! reqwest supports several TLS backends, enabled with Cargo features. //! //! ## default-tls //! //! reqwest will pick a TLS backend by default. This is true when the //! `default-tls` feature is enabled. //! //! While it currently uses `native-tls`, the feature set is designed to only //! enable configuration that is shared among available backends. This allows //! reqwest to change the default to `rustls` (or another) at some point in the //! future. //! //!

This feature is enabled by default, and takes //! precedence if any other crate enables it. This is true even if you declare //! `features = []`. You must set `default-features = false` instead.
//! //! Since Cargo features are additive, other crates in your dependency tree can //! cause the default backend to be enabled. If you wish to ensure your //! `Client` uses a specific backend, call the appropriate builder methods //! (such as [`use_rustls_tls()`][]). //! //! [`use_rustls_tls()`]: crate::ClientBuilder::use_rustls_tls() //! //! ## native-tls //! //! This backend uses the [native-tls][] crate. That will try to use the system //! TLS on Windows and Mac, and OpenSSL on Linux targets. //! //! Enabling the feature explicitly allows for `native-tls`-specific //! configuration options. //! //! [native-tls]: https://crates.io/crates/native-tls //! //! ## rustls-tls //! //! This backend uses the [rustls][] crate, a TLS library written in Rust. //! //! [rustls]: https://crates.io/crates/rustls #[cfg(feature = "__rustls")] use rustls::{ client::HandshakeSignatureValid, client::ServerCertVerified, client::ServerCertVerifier, DigitallySignedStruct, Error as TLSError, ServerName, }; use std::{ fmt, io::{BufRead, BufReader}, }; /// Represents a server X509 certificate. #[derive(Clone)] pub struct Certificate { #[cfg(feature = "native-tls-crate")] native: native_tls_crate::Certificate, #[cfg(feature = "__rustls")] original: Cert, } #[cfg(feature = "__rustls")] #[derive(Clone)] enum Cert { Der(Vec), Pem(Vec), } /// Represents a private key and X509 cert as a client certificate. #[derive(Clone)] pub struct Identity { #[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))] inner: ClientCert, } #[derive(Clone)] enum ClientCert { #[cfg(feature = "native-tls")] Pkcs12(native_tls_crate::Identity), #[cfg(feature = "native-tls")] Pkcs8(native_tls_crate::Identity), #[cfg(feature = "__rustls")] Pem { key: rustls::PrivateKey, certs: Vec, }, } impl Certificate { /// Create a `Certificate` from a binary DER encoded certificate /// /// # Examples /// /// ``` /// # use std::fs::File; /// # use std::io::Read; /// # fn cert() -> Result<(), Box> { /// let mut buf = Vec::new(); /// File::open("my_cert.der")? /// .read_to_end(&mut buf)?; /// let cert = reqwest::Certificate::from_der(&buf)?; /// # drop(cert); /// # Ok(()) /// # } /// ``` pub fn from_der(der: &[u8]) -> crate::Result { Ok(Certificate { #[cfg(feature = "native-tls-crate")] native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?, #[cfg(feature = "__rustls")] original: Cert::Der(der.to_owned()), }) } /// Create a `Certificate` from a PEM encoded certificate /// /// # Examples /// /// ``` /// # use std::fs::File; /// # use std::io::Read; /// # fn cert() -> Result<(), Box> { /// let mut buf = Vec::new(); /// File::open("my_cert.pem")? /// .read_to_end(&mut buf)?; /// let cert = reqwest::Certificate::from_pem(&buf)?; /// # drop(cert); /// # Ok(()) /// # } /// ``` pub fn from_pem(pem: &[u8]) -> crate::Result { Ok(Certificate { #[cfg(feature = "native-tls-crate")] native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?, #[cfg(feature = "__rustls")] original: Cert::Pem(pem.to_owned()), }) } /// Create a collection of `Certificate`s from a PEM encoded certificate bundle. /// Example byte sources may be `.crt`, `.cer` or `.pem` files. /// /// # Examples /// /// ``` /// # use std::fs::File; /// # use std::io::Read; /// # fn cert() -> Result<(), Box> { /// let mut buf = Vec::new(); /// File::open("ca-bundle.crt")? /// .read_to_end(&mut buf)?; /// let certs = reqwest::Certificate::from_pem_bundle(&buf)?; /// # drop(certs); /// # Ok(()) /// # } /// ``` pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result> { let mut reader = BufReader::new(pem_bundle); Self::read_pem_certs(&mut reader)? .iter() .map(|cert_vec| Certificate::from_der(&cert_vec)) .collect::>>() } #[cfg(feature = "native-tls-crate")] pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) { tls.add_root_certificate(self.native); } #[cfg(feature = "__rustls")] pub(crate) fn add_to_rustls( self, root_cert_store: &mut rustls::RootCertStore, ) -> crate::Result<()> { use std::io::Cursor; match self.original { Cert::Der(buf) => root_cert_store .add(&rustls::Certificate(buf)) .map_err(crate::error::builder)?, Cert::Pem(buf) => { let mut reader = Cursor::new(buf); let certs = Self::read_pem_certs(&mut reader)?; for c in certs { root_cert_store .add(&rustls::Certificate(c)) .map_err(crate::error::builder)?; } } } Ok(()) } fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result>> { rustls_pemfile::certs(reader) .map_err(|_| crate::error::builder("invalid certificate encoding")) } } impl Identity { /// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key. /// /// The archive should contain a leaf certificate and its private key, as well any intermediate /// certificates that allow clients to build a chain to a trusted root. /// The chain certificates should be in order from the leaf certificate towards the root. /// /// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created /// with the OpenSSL `pkcs12` tool: /// /// ```bash /// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem /// ``` /// /// # Examples /// /// ``` /// # use std::fs::File; /// # use std::io::Read; /// # fn pkcs12() -> Result<(), Box> { /// let mut buf = Vec::new(); /// File::open("my-ident.pfx")? /// .read_to_end(&mut buf)?; /// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?; /// # drop(pkcs12); /// # Ok(()) /// # } /// ``` /// /// # Optional /// /// This requires the `native-tls` Cargo feature enabled. #[cfg(feature = "native-tls")] pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result { Ok(Identity { inner: ClientCert::Pkcs12( native_tls_crate::Identity::from_pkcs12(der, password) .map_err(crate::error::builder)?, ), }) } /// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first. /// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate. /// /// The certificate chain should contain any intermediate cerficates that should be sent to /// clients to allow them to build a chain to a trusted root. /// /// A certificate chain here means a series of PEM encoded certificates concatenated together. /// /// # Examples /// /// ``` /// # use std::fs; /// # fn pkcs8() -> Result<(), Box> { /// let cert = fs::read("client.pem")?; /// let key = fs::read("key.pem")?; /// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?; /// # drop(pkcs8); /// # Ok(()) /// # } /// ``` /// /// # Optional /// /// This requires the `native-tls` Cargo feature enabled. #[cfg(feature = "native-tls")] pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result { Ok(Identity { inner: ClientCert::Pkcs8( native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?, ), }) } /// Parses PEM encoded private key and certificate. /// /// The input should contain a PEM encoded private key /// and at least one PEM encoded certificate. /// /// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format. /// /// # Examples /// /// ``` /// # use std::fs::File; /// # use std::io::Read; /// # fn pem() -> Result<(), Box> { /// let mut buf = Vec::new(); /// File::open("my-ident.pem")? /// .read_to_end(&mut buf)?; /// let id = reqwest::Identity::from_pem(&buf)?; /// # drop(id); /// # Ok(()) /// # } /// ``` /// /// # Optional /// /// This requires the `rustls-tls(-...)` Cargo feature enabled. #[cfg(feature = "__rustls")] pub fn from_pem(buf: &[u8]) -> crate::Result { use std::io::Cursor; let (key, certs) = { let mut pem = Cursor::new(buf); let mut sk = Vec::::new(); let mut certs = Vec::::new(); for item in std::iter::from_fn(|| rustls_pemfile::read_one(&mut pem).transpose()) { match item.map_err(|_| { crate::error::builder(TLSError::General(String::from( "Invalid identity PEM file", ))) })? { rustls_pemfile::Item::X509Certificate(cert) => { certs.push(rustls::Certificate(cert)) } rustls_pemfile::Item::PKCS8Key(key) => sk.push(rustls::PrivateKey(key)), rustls_pemfile::Item::RSAKey(key) => sk.push(rustls::PrivateKey(key)), rustls_pemfile::Item::ECKey(key) => sk.push(rustls::PrivateKey(key)), _ => { return Err(crate::error::builder(TLSError::General(String::from( "No valid certificate was found", )))) } } } if let (Some(sk), false) = (sk.pop(), certs.is_empty()) { (sk, certs) } else { return Err(crate::error::builder(TLSError::General(String::from( "private key or certificate not found", )))); } }; Ok(Identity { inner: ClientCert::Pem { key, certs }, }) } #[cfg(feature = "native-tls")] pub(crate) fn add_to_native_tls( self, tls: &mut native_tls_crate::TlsConnectorBuilder, ) -> crate::Result<()> { match self.inner { ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => { tls.identity(id); Ok(()) } #[cfg(feature = "__rustls")] ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")), } } #[cfg(feature = "__rustls")] pub(crate) fn add_to_rustls( self, config_builder: rustls::ConfigBuilder< rustls::ClientConfig, rustls::client::WantsTransparencyPolicyOrClientCert, >, ) -> crate::Result { match self.inner { ClientCert::Pem { key, certs } => config_builder .with_client_auth_cert(certs, key) .map_err(crate::error::builder), #[cfg(feature = "native-tls")] ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => { Err(crate::error::builder("incompatible TLS identity type")) } } } } impl fmt::Debug for Certificate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Certificate").finish() } } impl fmt::Debug for Identity { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Identity").finish() } } /// A TLS protocol version. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Version(InnerVersion); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] enum InnerVersion { Tls1_0, Tls1_1, Tls1_2, Tls1_3, } // These could perhaps be From/TryFrom implementations, but those would be // part of the public API so let's be careful impl Version { /// Version 1.0 of the TLS protocol. pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0); /// Version 1.1 of the TLS protocol. pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1); /// Version 1.2 of the TLS protocol. pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2); /// Version 1.3 of the TLS protocol. pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3); #[cfg(feature = "default-tls")] pub(crate) fn to_native_tls(self) -> Option { match self.0 { InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10), InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11), InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12), InnerVersion::Tls1_3 => None, } } #[cfg(feature = "__rustls")] pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option { match version { rustls::ProtocolVersion::SSLv2 => None, rustls::ProtocolVersion::SSLv3 => None, rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)), rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)), rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)), rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)), _ => None, } } } pub(crate) enum TlsBackend { // This is the default and HTTP/3 feature does not use it so suppress it. #[allow(dead_code)] #[cfg(feature = "default-tls")] Default, #[cfg(feature = "native-tls")] BuiltNativeTls(native_tls_crate::TlsConnector), #[cfg(feature = "__rustls")] Rustls, #[cfg(feature = "__rustls")] BuiltRustls(rustls::ClientConfig), #[cfg(any(feature = "native-tls", feature = "__rustls",))] UnknownPreconfigured, } impl fmt::Debug for TlsBackend { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { #[cfg(feature = "default-tls")] TlsBackend::Default => write!(f, "Default"), #[cfg(feature = "native-tls")] TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"), #[cfg(feature = "__rustls")] TlsBackend::Rustls => write!(f, "Rustls"), #[cfg(feature = "__rustls")] TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"), #[cfg(any(feature = "native-tls", feature = "__rustls",))] TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"), } } } impl Default for TlsBackend { fn default() -> TlsBackend { #[cfg(all(feature = "default-tls", not(feature = "http3")))] { TlsBackend::Default } #[cfg(any( all(feature = "__rustls", not(feature = "default-tls")), feature = "http3" ))] { TlsBackend::Rustls } } } #[cfg(feature = "__rustls")] pub(crate) struct NoVerifier; #[cfg(feature = "__rustls")] impl ServerCertVerifier for NoVerifier { fn verify_server_cert( &self, _end_entity: &rustls::Certificate, _intermediates: &[rustls::Certificate], _server_name: &ServerName, _scts: &mut dyn Iterator, _ocsp_response: &[u8], _now: std::time::SystemTime, ) -> Result { Ok(ServerCertVerified::assertion()) } fn verify_tls12_signature( &self, _message: &[u8], _cert: &rustls::Certificate, _dss: &DigitallySignedStruct, ) -> Result { Ok(HandshakeSignatureValid::assertion()) } fn verify_tls13_signature( &self, _message: &[u8], _cert: &rustls::Certificate, _dss: &DigitallySignedStruct, ) -> Result { Ok(HandshakeSignatureValid::assertion()) } } /// Hyper extension carrying extra TLS layer information. /// Made available to clients on responses when `tls_info` is set. #[derive(Clone)] pub struct TlsInfo { pub(crate) peer_certificate: Option>, } impl TlsInfo { /// Get the DER encoded leaf certificate of the peer. pub fn peer_certificate(&self) -> Option<&[u8]> { self.peer_certificate.as_ref().map(|der| &der[..]) } } impl std::fmt::Debug for TlsInfo { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("TlsInfo").finish() } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "default-tls")] #[test] fn certificate_from_der_invalid() { Certificate::from_der(b"not der").unwrap_err(); } #[cfg(feature = "default-tls")] #[test] fn certificate_from_pem_invalid() { Certificate::from_pem(b"not pem").unwrap_err(); } #[cfg(feature = "native-tls")] #[test] fn identity_from_pkcs12_der_invalid() { Identity::from_pkcs12_der(b"not der", "nope").unwrap_err(); } #[cfg(feature = "native-tls")] #[test] fn identity_from_pkcs8_pem_invalid() { Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err(); } #[cfg(feature = "__rustls")] #[test] fn identity_from_pem_invalid() { Identity::from_pem(b"not pem").unwrap_err(); } #[cfg(feature = "__rustls")] #[test] fn identity_from_pem_pkcs1_key() { let pem = b"-----BEGIN CERTIFICATE-----\n\ -----END CERTIFICATE-----\n\ -----BEGIN RSA PRIVATE KEY-----\n\ -----END RSA PRIVATE KEY-----\n"; Identity::from_pem(pem).unwrap(); } #[test] fn certificates_from_pem_bundle() { const PEM_BUNDLE: &[u8] = b" -----BEGIN CERTIFICATE----- MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM YyRIHN8wfdVoOw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi 9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW 1KyLa2tJElMzrdfkviT8tQp21KW8EA== -----END CERTIFICATE----- "; assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok()) } } reqwest-0.11.27/src/util.rs000064400000000000000000000050551046102023000136050ustar 00000000000000use crate::header::{Entry, HeaderMap, HeaderValue, OccupiedEntry}; pub fn basic_auth(username: U, password: Option

) -> HeaderValue where U: std::fmt::Display, P: std::fmt::Display, { use base64::prelude::BASE64_STANDARD; use base64::write::EncoderWriter; use std::io::Write; let mut buf = b"Basic ".to_vec(); { let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD); let _ = write!(encoder, "{username}:"); if let Some(password) = password { let _ = write!(encoder, "{password}"); } } let mut header = HeaderValue::from_bytes(&buf).expect("base64 is always valid HeaderValue"); header.set_sensitive(true); header } // xor-shift #[cfg(not(target_arch = "wasm32"))] pub(crate) fn fast_random() -> u64 { use std::cell::Cell; use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hasher}; use std::num::Wrapping; thread_local! { static RNG: Cell> = Cell::new(Wrapping(seed())); } fn seed() -> u64 { let seed = RandomState::new(); let mut out = 0; let mut cnt = 0; while out == 0 { cnt += 1; let mut hasher = seed.build_hasher(); hasher.write_usize(cnt); out = hasher.finish(); } out } RNG.with(|rng| { let mut n = rng.get(); debug_assert_ne!(n.0, 0); n ^= n >> 12; n ^= n << 25; n ^= n >> 27; rng.set(n); n.0.wrapping_mul(0x2545_f491_4f6c_dd1d) }) } pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) { // IntoIter of HeaderMap yields (Option, HeaderValue). // The first time a name is yielded, it will be Some(name), and if // there are more values with the same name, the next yield will be // None. let mut prev_entry: Option> = None; for (key, value) in src { match key { Some(key) => match dst.entry(key) { Entry::Occupied(mut e) => { e.insert(value); prev_entry = Some(e); } Entry::Vacant(e) => { let e = e.insert_entry(value); prev_entry = Some(e); } }, None => match prev_entry { Some(ref mut entry) => { entry.append(value); } None => unreachable!("HeaderMap::into_iter yielded None first"), }, } } } reqwest-0.11.27/src/wasm/body.rs000064400000000000000000000212111046102023000145240ustar 00000000000000#[cfg(feature = "multipart")] use super::multipart::Form; /// dox use bytes::Bytes; use js_sys::Uint8Array; use std::{borrow::Cow, fmt}; use wasm_bindgen::JsValue; /// The body of a `Request`. /// /// In most cases, this is not needed directly, as the /// [`RequestBuilder.body`][builder] method uses `Into`, which allows /// passing many things (like a string or vector of bytes). /// /// [builder]: ./struct.RequestBuilder.html#method.body pub struct Body { inner: Inner, } enum Inner { Single(Single), /// MultipartForm holds a multipart/form-data body. #[cfg(feature = "multipart")] MultipartForm(Form), } #[derive(Clone)] pub(crate) enum Single { Bytes(Bytes), Text(Cow<'static, str>), } impl Single { fn as_bytes(&self) -> &[u8] { match self { Single::Bytes(bytes) => bytes.as_ref(), Single::Text(text) => text.as_bytes(), } } pub(crate) fn to_js_value(&self) -> JsValue { match self { Single::Bytes(bytes) => { let body_bytes: &[u8] = bytes.as_ref(); let body_uint8_array: Uint8Array = body_bytes.into(); let js_value: &JsValue = body_uint8_array.as_ref(); js_value.to_owned() } Single::Text(text) => JsValue::from_str(text), } } fn is_empty(&self) -> bool { match self { Single::Bytes(bytes) => bytes.is_empty(), Single::Text(text) => text.is_empty(), } } } impl Body { /// Returns a reference to the internal data of the `Body`. /// /// `None` is returned, if the underlying data is a multipart form. #[inline] pub fn as_bytes(&self) -> Option<&[u8]> { match &self.inner { Inner::Single(single) => Some(single.as_bytes()), #[cfg(feature = "multipart")] Inner::MultipartForm(_) => None, } } pub(crate) fn to_js_value(&self) -> crate::Result { match &self.inner { Inner::Single(single) => Ok(single.to_js_value()), #[cfg(feature = "multipart")] Inner::MultipartForm(form) => { let form_data = form.to_form_data()?; let js_value: &JsValue = form_data.as_ref(); Ok(js_value.to_owned()) } } } #[cfg(feature = "multipart")] pub(crate) fn as_single(&self) -> Option<&Single> { match &self.inner { Inner::Single(single) => Some(single), Inner::MultipartForm(_) => None, } } #[inline] #[cfg(feature = "multipart")] pub(crate) fn from_form(f: Form) -> Body { Self { inner: Inner::MultipartForm(f), } } /// into_part turns a regular body into the body of a multipart/form-data part. #[cfg(feature = "multipart")] pub(crate) fn into_part(self) -> Body { match self.inner { Inner::Single(single) => Self { inner: Inner::Single(single), }, Inner::MultipartForm(form) => Self { inner: Inner::MultipartForm(form), }, } } pub(crate) fn is_empty(&self) -> bool { match &self.inner { Inner::Single(single) => single.is_empty(), #[cfg(feature = "multipart")] Inner::MultipartForm(form) => form.is_empty(), } } pub(crate) fn try_clone(&self) -> Option { match &self.inner { Inner::Single(single) => Some(Self { inner: Inner::Single(single.clone()), }), #[cfg(feature = "multipart")] Inner::MultipartForm(_) => None, } } } impl From for Body { #[inline] fn from(bytes: Bytes) -> Body { Body { inner: Inner::Single(Single::Bytes(bytes)), } } } impl From> for Body { #[inline] fn from(vec: Vec) -> Body { Body { inner: Inner::Single(Single::Bytes(vec.into())), } } } impl From<&'static [u8]> for Body { #[inline] fn from(s: &'static [u8]) -> Body { Body { inner: Inner::Single(Single::Bytes(Bytes::from_static(s))), } } } impl From for Body { #[inline] fn from(s: String) -> Body { Body { inner: Inner::Single(Single::Text(s.into())), } } } impl From<&'static str> for Body { #[inline] fn from(s: &'static str) -> Body { Body { inner: Inner::Single(Single::Text(s.into())), } } } impl fmt::Debug for Body { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Body").finish() } } #[cfg(test)] mod tests { use crate::Body; use js_sys::Uint8Array; use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen] extern "C" { // Use `js_namespace` here to bind `console.log(..)` instead of just // `log(..)` #[wasm_bindgen(js_namespace = console)] fn log(s: String); } #[wasm_bindgen_test] async fn test_body() { let body = Body::from("TEST"); assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap()); } #[wasm_bindgen_test] async fn test_body_js_static_str() { let body_value = "TEST"; let body = Body::from(body_value); let mut init = web_sys::RequestInit::new(); init.method("POST"); init.body(Some( body.to_js_value() .expect("could not convert body to JsValue") .as_ref(), )); let js_req = web_sys::Request::new_with_str_and_init("", &init) .expect("could not create JS request"); let text_promise = js_req.text().expect("could not get text promise"); let text = crate::wasm::promise::(text_promise) .await .expect("could not get request body as text"); assert_eq!(text.as_string().expect("text is not a string"), body_value); } #[wasm_bindgen_test] async fn test_body_js_string() { let body_value = "TEST".to_string(); let body = Body::from(body_value.clone()); let mut init = web_sys::RequestInit::new(); init.method("POST"); init.body(Some( body.to_js_value() .expect("could not convert body to JsValue") .as_ref(), )); let js_req = web_sys::Request::new_with_str_and_init("", &init) .expect("could not create JS request"); let text_promise = js_req.text().expect("could not get text promise"); let text = crate::wasm::promise::(text_promise) .await .expect("could not get request body as text"); assert_eq!(text.as_string().expect("text is not a string"), body_value); } #[wasm_bindgen_test] async fn test_body_js_static_u8_slice() { let body_value: &'static [u8] = b"\x00\x42"; let body = Body::from(body_value); let mut init = web_sys::RequestInit::new(); init.method("POST"); init.body(Some( body.to_js_value() .expect("could not convert body to JsValue") .as_ref(), )); let js_req = web_sys::Request::new_with_str_and_init("", &init) .expect("could not create JS request"); let array_buffer_promise = js_req .array_buffer() .expect("could not get array_buffer promise"); let array_buffer = crate::wasm::promise::(array_buffer_promise) .await .expect("could not get request body as array buffer"); let v = Uint8Array::new(&array_buffer).to_vec(); assert_eq!(v, body_value); } #[wasm_bindgen_test] async fn test_body_js_vec_u8() { let body_value = vec![0u8, 42]; let body = Body::from(body_value.clone()); let mut init = web_sys::RequestInit::new(); init.method("POST"); init.body(Some( body.to_js_value() .expect("could not convert body to JsValue") .as_ref(), )); let js_req = web_sys::Request::new_with_str_and_init("", &init) .expect("could not create JS request"); let array_buffer_promise = js_req .array_buffer() .expect("could not get array_buffer promise"); let array_buffer = crate::wasm::promise::(array_buffer_promise) .await .expect("could not get request body as array buffer"); let v = Uint8Array::new(&array_buffer).to_vec(); assert_eq!(v, body_value); } } reqwest-0.11.27/src/wasm/client.rs000064400000000000000000000315161046102023000150560ustar 00000000000000use http::header::USER_AGENT; use http::{HeaderMap, HeaderValue, Method}; use js_sys::{Promise, JSON}; use std::convert::TryInto; use std::{fmt, future::Future, sync::Arc}; use url::Url; use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _}; use super::{AbortGuard, Request, RequestBuilder, Response}; use crate::IntoUrl; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_name = fetch)] fn fetch_with_request(input: &web_sys::Request) -> Promise; } fn js_fetch(req: &web_sys::Request) -> Promise { use wasm_bindgen::{JsCast, JsValue}; let global = js_sys::global(); if let Ok(true) = js_sys::Reflect::has(&global, &JsValue::from_str("ServiceWorkerGlobalScope")) { global .unchecked_into::() .fetch_with_request(req) } else { // browser fetch_with_request(req) } } /// dox #[derive(Clone)] pub struct Client { config: Arc, } /// dox pub struct ClientBuilder { config: Config, } impl Client { /// dox pub fn new() -> Self { Client::builder().build().unwrap_throw() } /// dox pub fn builder() -> ClientBuilder { ClientBuilder::new() } /// Convenience method to make a `GET` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn get(&self, url: U) -> RequestBuilder { self.request(Method::GET, url) } /// Convenience method to make a `POST` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn post(&self, url: U) -> RequestBuilder { self.request(Method::POST, url) } /// Convenience method to make a `PUT` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn put(&self, url: U) -> RequestBuilder { self.request(Method::PUT, url) } /// Convenience method to make a `PATCH` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn patch(&self, url: U) -> RequestBuilder { self.request(Method::PATCH, url) } /// Convenience method to make a `DELETE` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn delete(&self, url: U) -> RequestBuilder { self.request(Method::DELETE, url) } /// Convenience method to make a `HEAD` request to a URL. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn head(&self, url: U) -> RequestBuilder { self.request(Method::HEAD, url) } /// Start building a `Request` with the `Method` and `Url`. /// /// Returns a `RequestBuilder`, which will allow setting headers and /// request body before sending. /// /// # Errors /// /// This method fails whenever supplied `Url` cannot be parsed. pub fn request(&self, method: Method, url: U) -> RequestBuilder { let req = url.into_url().map(move |url| Request::new(method, url)); RequestBuilder::new(self.clone(), req) } /// Executes a `Request`. /// /// A `Request` can be built manually with `Request::new()` or obtained /// from a RequestBuilder with `RequestBuilder::build()`. /// /// You should prefer to use the `RequestBuilder` and /// `RequestBuilder::send()`. /// /// # Errors /// /// This method fails if there was an error while sending request, /// redirect loop was detected or redirect limit was exhausted. pub fn execute( &self, request: Request, ) -> impl Future> { self.execute_request(request) } // merge request headers with Client default_headers, prior to external http fetch fn merge_headers(&self, req: &mut Request) { use http::header::Entry; let headers: &mut HeaderMap = req.headers_mut(); // insert default headers in the request headers // without overwriting already appended headers. for (key, value) in self.config.headers.iter() { if let Entry::Vacant(entry) = headers.entry(key) { entry.insert(value.clone()); } } } pub(super) fn execute_request( &self, mut req: Request, ) -> impl Future> { self.merge_headers(&mut req); fetch(req) } } impl Default for Client { fn default() -> Self { Self::new() } } impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("Client"); self.config.fmt_fields(&mut builder); builder.finish() } } impl fmt::Debug for ClientBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("ClientBuilder"); self.config.fmt_fields(&mut builder); builder.finish() } } async fn fetch(req: Request) -> crate::Result { // Build the js Request let mut init = web_sys::RequestInit::new(); init.method(req.method().as_str()); // convert HeaderMap to Headers let js_headers = web_sys::Headers::new() .map_err(crate::error::wasm) .map_err(crate::error::builder)?; for (name, value) in req.headers() { js_headers .append( name.as_str(), value.to_str().map_err(crate::error::builder)?, ) .map_err(crate::error::wasm) .map_err(crate::error::builder)?; } init.headers(&js_headers.into()); // When req.cors is true, do nothing because the default mode is 'cors' if !req.cors { init.mode(web_sys::RequestMode::NoCors); } if let Some(creds) = req.credentials { init.credentials(creds); } if let Some(body) = req.body() { if !body.is_empty() { init.body(Some(body.to_js_value()?.as_ref())); } } let abort = AbortGuard::new()?; init.signal(Some(&abort.signal())); let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init) .map_err(crate::error::wasm) .map_err(crate::error::builder)?; // Await the fetch() promise let p = js_fetch(&js_req); let js_resp = super::promise::(p) .await .map_err(crate::error::request)?; // Convert from the js Response let mut resp = http::Response::builder().status(js_resp.status()); let url = Url::parse(&js_resp.url()).expect_throw("url parse"); let js_headers = js_resp.headers(); let js_iter = js_sys::try_iter(&js_headers) .expect_throw("headers try_iter") .expect_throw("headers have an iterator"); for item in js_iter { let item = item.expect_throw("headers iterator doesn't throw"); let serialized_headers: String = JSON::stringify(&item) .expect_throw("serialized headers") .into(); let [name, value]: [String; 2] = serde_json::from_str(&serialized_headers) .expect_throw("deserializable serialized headers"); resp = resp.header(&name, &value); } resp.body(js_resp) .map(|resp| Response::new(resp, url, abort)) .map_err(crate::error::request) } // ===== impl ClientBuilder ===== impl ClientBuilder { /// dox pub fn new() -> Self { ClientBuilder { config: Config::default(), } } /// Returns a 'Client' that uses this ClientBuilder configuration pub fn build(mut self) -> Result { if let Some(err) = self.config.error { return Err(err); } let config = std::mem::take(&mut self.config); Ok(Client { config: Arc::new(config), }) } /// Sets the `User-Agent` header to be used by this client. pub fn user_agent(mut self, value: V) -> ClientBuilder where V: TryInto, V::Error: Into, { match value.try_into() { Ok(value) => { self.config.headers.insert(USER_AGENT, value); } Err(e) => { self.config.error = Some(crate::error::builder(e.into())); } } self } /// Sets the default headers for every request pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder { for (key, value) in headers.iter() { self.config.headers.insert(key, value.clone()); } self } } impl Default for ClientBuilder { fn default() -> Self { Self::new() } } #[derive(Debug)] struct Config { headers: HeaderMap, error: Option, } impl Default for Config { fn default() -> Config { Config { headers: HeaderMap::new(), error: None, } } } impl Config { fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) { f.field("default_headers", &self.headers); } } #[cfg(test)] mod tests { use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] async fn default_headers() { use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet")); let client = crate::Client::builder() .default_headers(headers) .build() .expect("client"); let mut req = client .get("https://www.example.com") .build() .expect("request"); // merge headers as if client were about to issue fetch client.merge_headers(&mut req); let test_headers = req.headers(); assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type"); assert!(test_headers.get("x-custom").is_some(), "custom header"); assert!(test_headers.get("accept").is_none(), "no accept header"); } #[wasm_bindgen_test] async fn default_headers_clone() { use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE}; let mut headers = HeaderMap::new(); headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet")); let client = crate::Client::builder() .default_headers(headers) .build() .expect("client"); let mut req = client .get("https://www.example.com") .header(CONTENT_TYPE, "text/plain") .build() .expect("request"); client.merge_headers(&mut req); let headers1 = req.headers(); // confirm that request headers override defaults assert_eq!( headers1.get(CONTENT_TYPE).unwrap(), "text/plain", "request headers override defaults" ); // confirm that request headers don't change client defaults let mut req2 = client .get("https://www.example.com/x") .build() .expect("req 2"); client.merge_headers(&mut req2); let headers2 = req2.headers(); assert_eq!( headers2.get(CONTENT_TYPE).unwrap(), "application/json", "request headers don't change client defaults" ); } #[wasm_bindgen_test] fn user_agent_header() { use crate::header::USER_AGENT; let client = crate::Client::builder() .user_agent("FooBar/1.2.3") .build() .expect("client"); let mut req = client .get("https://www.example.com") .build() .expect("request"); // Merge the client headers with the request's one. client.merge_headers(&mut req); let headers1 = req.headers(); // Confirm that we have the `User-Agent` header set assert_eq!( headers1.get(USER_AGENT).unwrap(), "FooBar/1.2.3", "The user-agent header was not set: {req:#?}" ); // Now we try to overwrite the `User-Agent` value let mut req2 = client .get("https://www.example.com") .header(USER_AGENT, "Another-User-Agent/42") .build() .expect("request 2"); client.merge_headers(&mut req2); let headers2 = req2.headers(); assert_eq!( headers2.get(USER_AGENT).expect("headers2 user agent"), "Another-User-Agent/42", "Was not able to overwrite the User-Agent value on the request-builder" ); } } reqwest-0.11.27/src/wasm/mod.rs000064400000000000000000000022271046102023000143540ustar 00000000000000use wasm_bindgen::JsCast; use web_sys::{AbortController, AbortSignal}; mod body; mod client; /// TODO #[cfg(feature = "multipart")] pub mod multipart; mod request; mod response; pub use self::body::Body; pub use self::client::{Client, ClientBuilder}; pub use self::request::{Request, RequestBuilder}; pub use self::response::Response; async fn promise(promise: js_sys::Promise) -> Result where T: JsCast, { use wasm_bindgen_futures::JsFuture; let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?; js_val .dyn_into::() .map_err(|_js_val| "promise resolved to unexpected type".into()) } /// A guard that cancels a fetch request when dropped. struct AbortGuard { ctrl: AbortController, } impl AbortGuard { fn new() -> crate::Result { Ok(AbortGuard { ctrl: AbortController::new() .map_err(crate::error::wasm) .map_err(crate::error::builder)?, }) } fn signal(&self) -> AbortSignal { self.ctrl.signal() } } impl Drop for AbortGuard { fn drop(&mut self) { self.ctrl.abort(); } } reqwest-0.11.27/src/wasm/multipart.rs000064400000000000000000000262221046102023000156170ustar 00000000000000//! multipart/form-data use std::borrow::Cow; use std::fmt; use http::HeaderMap; use mime_guess::Mime; use web_sys::FormData; use super::Body; /// An async multipart/form-data request. pub struct Form { inner: FormParts, } impl Form { pub(crate) fn is_empty(&self) -> bool { self.inner.fields.is_empty() } } /// A field in a multipart form. pub struct Part { meta: PartMetadata, value: Body, } pub(crate) struct FormParts

{ pub(crate) fields: Vec<(Cow<'static, str>, P)>, } pub(crate) struct PartMetadata { mime: Option, file_name: Option>, pub(crate) headers: HeaderMap, } pub(crate) trait PartProps { fn metadata(&self) -> &PartMetadata; } // ===== impl Form ===== impl Default for Form { fn default() -> Self { Self::new() } } impl Form { /// Creates a new async Form without any content. pub fn new() -> Form { Form { inner: FormParts::new(), } } /// Add a data field with supplied name and value. /// /// # Examples /// /// ``` /// let form = reqwest::multipart::Form::new() /// .text("username", "seanmonstar") /// .text("password", "secret"); /// ``` pub fn text(self, name: T, value: U) -> Form where T: Into>, U: Into>, { self.part(name, Part::text(value)) } /// Adds a customized Part. pub fn part(self, name: T, part: Part) -> Form where T: Into>, { self.with_inner(move |inner| inner.part(name, part)) } fn with_inner(self, func: F) -> Self where F: FnOnce(FormParts) -> FormParts, { Form { inner: func(self.inner), } } pub(crate) fn to_form_data(&self) -> crate::Result { let form = FormData::new() .map_err(crate::error::wasm) .map_err(crate::error::builder)?; for (name, part) in self.inner.fields.iter() { part.append_to_form(name, &form) .map_err(crate::error::wasm) .map_err(crate::error::builder)?; } Ok(form) } } impl fmt::Debug for Form { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.fmt_fields("Form", f) } } // ===== impl Part ===== impl Part { /// Makes a text parameter. pub fn text(value: T) -> Part where T: Into>, { let body = match value.into() { Cow::Borrowed(slice) => Body::from(slice), Cow::Owned(string) => Body::from(string), }; Part::new(body) } /// Makes a new parameter from arbitrary bytes. pub fn bytes(value: T) -> Part where T: Into>, { let body = match value.into() { Cow::Borrowed(slice) => Body::from(slice), Cow::Owned(vec) => Body::from(vec), }; Part::new(body) } /// Makes a new parameter from an arbitrary stream. pub fn stream>(value: T) -> Part { Part::new(value.into()) } fn new(value: Body) -> Part { Part { meta: PartMetadata::new(), value: value.into_part(), } } /// Tries to set the mime of this part. pub fn mime_str(self, mime: &str) -> crate::Result { Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) } // Re-export when mime 0.4 is available, with split MediaType/MediaRange. fn mime(self, mime: Mime) -> Part { self.with_inner(move |inner| inner.mime(mime)) } /// Sets the filename, builder style. pub fn file_name(self, filename: T) -> Part where T: Into>, { self.with_inner(move |inner| inner.file_name(filename)) } /// Sets custom headers for the part. pub fn headers(self, headers: HeaderMap) -> Part { self.with_inner(move |inner| inner.headers(headers)) } fn with_inner(self, func: F) -> Self where F: FnOnce(PartMetadata) -> PartMetadata, { Part { meta: func(self.meta), value: self.value, } } fn append_to_form( &self, name: &str, form: &web_sys::FormData, ) -> Result<(), wasm_bindgen::JsValue> { let single = self .value .as_single() .expect("A part's body can't be multipart itself"); let mut mime_type = self.metadata().mime.as_ref(); // The JS fetch API doesn't support file names and mime types for strings. So we do our best // effort to use `append_with_str` and fallback to `append_with_blob_*` if that's not // possible. if let super::body::Single::Text(text) = single { if mime_type.is_none() || mime_type == Some(&mime_guess::mime::TEXT_PLAIN) { if self.metadata().file_name.is_none() { return form.append_with_str(name, text); } } else { mime_type = Some(&mime_guess::mime::TEXT_PLAIN); } } let blob = self.blob(mime_type)?; if let Some(file_name) = &self.metadata().file_name { form.append_with_blob_and_filename(name, &blob, file_name) } else { form.append_with_blob(name, &blob) } } fn blob(&self, mime_type: Option<&Mime>) -> crate::Result { use web_sys::Blob; use web_sys::BlobPropertyBag; let mut properties = BlobPropertyBag::new(); if let Some(mime) = mime_type { properties.type_(mime.as_ref()); } let js_value = self .value .as_single() .expect("A part's body can't be set to a multipart body") .to_js_value(); let body_array = js_sys::Array::new(); body_array.push(&js_value); Blob::new_with_u8_array_sequence_and_options(body_array.as_ref(), &properties) .map_err(crate::error::wasm) .map_err(crate::error::builder) } } impl fmt::Debug for Part { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut dbg = f.debug_struct("Part"); dbg.field("value", &self.value); self.meta.fmt_fields(&mut dbg); dbg.finish() } } impl PartProps for Part { fn metadata(&self) -> &PartMetadata { &self.meta } } // ===== impl FormParts ===== impl FormParts

{ pub(crate) fn new() -> Self { FormParts { fields: Vec::new() } } /// Adds a customized Part. pub(crate) fn part(mut self, name: T, part: P) -> Self where T: Into>, { self.fields.push((name.into(), part)); self } } impl FormParts

{ pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct(ty_name) .field("parts", &self.fields) .finish() } } // ===== impl PartMetadata ===== impl PartMetadata { pub(crate) fn new() -> Self { PartMetadata { mime: None, file_name: None, headers: HeaderMap::default(), } } pub(crate) fn mime(mut self, mime: Mime) -> Self { self.mime = Some(mime); self } pub(crate) fn file_name(mut self, filename: T) -> Self where T: Into>, { self.file_name = Some(filename.into()); self } pub(crate) fn headers(mut self, headers: T) -> Self where T: Into, { self.headers = headers.into(); self } } impl PartMetadata { pub(crate) fn fmt_fields<'f, 'fa, 'fb>( &self, debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>, ) -> &'f mut fmt::DebugStruct<'fa, 'fb> { debug_struct .field("mime", &self.mime) .field("file_name", &self.file_name) .field("headers", &self.headers) } } #[cfg(test)] mod tests { use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] async fn test_multipart_js() { use super::{Form, Part}; use js_sys::Uint8Array; use wasm_bindgen::JsValue; use web_sys::{File, FormData}; let text_file_name = "test.txt"; let text_file_type = "text/plain"; let text_content = "TEST"; let text_part = Part::text(text_content) .file_name(text_file_name) .mime_str(text_file_type) .expect("invalid mime type"); let binary_file_name = "binary.bin"; let binary_file_type = "application/octet-stream"; let binary_content = vec![0u8, 42]; let binary_part = Part::bytes(binary_content.clone()) .file_name(binary_file_name) .mime_str(binary_file_type) .expect("invalid mime type"); let string_name = "string"; let string_content = "CONTENT"; let string_part = Part::text(string_content); let text_name = "text part"; let binary_name = "binary part"; let form = Form::new() .part(text_name, text_part) .part(binary_name, binary_part) .part(string_name, string_part); let mut init = web_sys::RequestInit::new(); init.method("POST"); init.body(Some( form.to_form_data() .expect("could not convert to FormData") .as_ref(), )); let js_req = web_sys::Request::new_with_str_and_init("", &init) .expect("could not create JS request"); let form_data_promise = js_req.form_data().expect("could not get form_data promise"); let form_data = crate::wasm::promise::(form_data_promise) .await .expect("could not get body as form data"); // check text part let text_file = File::from(form_data.get(text_name)); assert_eq!(text_file.name(), text_file_name); assert_eq!(text_file.type_(), text_file_type); let text_promise = text_file.text(); let text = crate::wasm::promise::(text_promise) .await .expect("could not get text body as text"); assert_eq!( text.as_string().expect("text is not a string"), text_content ); // check binary part let binary_file = File::from(form_data.get(binary_name)); assert_eq!(binary_file.name(), binary_file_name); assert_eq!(binary_file.type_(), binary_file_type); // check string part let string = form_data .get(string_name) .as_string() .expect("content is not a string"); assert_eq!(string, string_content); let binary_array_buffer_promise = binary_file.array_buffer(); let array_buffer = crate::wasm::promise::(binary_array_buffer_promise) .await .expect("could not get request body as array buffer"); let binary = Uint8Array::new(&array_buffer).to_vec(); assert_eq!(binary, binary_content); } } reqwest-0.11.27/src/wasm/request.rs000064400000000000000000000335261046102023000152730ustar 00000000000000use std::convert::TryFrom; use std::fmt; use bytes::Bytes; use http::{request::Parts, Method, Request as HttpRequest}; use serde::Serialize; #[cfg(feature = "json")] use serde_json; use url::Url; use web_sys::RequestCredentials; use super::{Body, Client, Response}; use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; /// A request which can be executed with `Client::execute()`. pub struct Request { method: Method, url: Url, headers: HeaderMap, body: Option, pub(super) cors: bool, pub(super) credentials: Option, } /// A builder to construct the properties of a `Request`. pub struct RequestBuilder { client: Client, request: crate::Result, } impl Request { /// Constructs a new request. #[inline] pub fn new(method: Method, url: Url) -> Self { Request { method, url, headers: HeaderMap::new(), body: None, cors: true, credentials: None, } } /// Get the method. #[inline] pub fn method(&self) -> &Method { &self.method } /// Get a mutable reference to the method. #[inline] pub fn method_mut(&mut self) -> &mut Method { &mut self.method } /// Get the url. #[inline] pub fn url(&self) -> &Url { &self.url } /// Get a mutable reference to the url. #[inline] pub fn url_mut(&mut self) -> &mut Url { &mut self.url } /// Get the headers. #[inline] pub fn headers(&self) -> &HeaderMap { &self.headers } /// Get a mutable reference to the headers. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.headers } /// Get the body. #[inline] pub fn body(&self) -> Option<&Body> { self.body.as_ref() } /// Get a mutable reference to the body. #[inline] pub fn body_mut(&mut self) -> &mut Option { &mut self.body } /// Attempts to clone the `Request`. /// /// None is returned if a body is which can not be cloned. pub fn try_clone(&self) -> Option { let body = match self.body.as_ref() { Some(body) => Some(body.try_clone()?), None => None, }; Some(Self { method: self.method.clone(), url: self.url.clone(), headers: self.headers.clone(), body, cors: self.cors, credentials: self.credentials, }) } } impl RequestBuilder { pub(super) fn new(client: Client, request: crate::Result) -> RequestBuilder { RequestBuilder { client, request } } /// Modify the query string of the URL. /// /// Modifies the URL of this request, adding the parameters provided. /// This method appends and does not overwrite. This means that it can /// be called multiple times and that existing query parameters are not /// overwritten if the same key is used. The key will simply show up /// twice in the query string. /// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`. /// /// # Note /// This method does not support serializing a single key-value /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such /// as `.query(&[("key", "val")])`. It's also possible to serialize structs /// and maps into a key-value pair. /// /// # Errors /// This method will fail if the object you provide cannot be serialized /// into a query string. pub fn query(mut self, query: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { let url = req.url_mut(); let mut pairs = url.query_pairs_mut(); let serializer = serde_urlencoded::Serializer::new(&mut pairs); if let Err(err) = query.serialize(serializer) { error = Some(crate::error::builder(err)); } } if let Ok(ref mut req) = self.request { if let Some("") = req.url().query() { req.url_mut().set_query(None); } } if let Some(err) = error { self.request = Err(err); } self } /// Send a form body. /// /// Sets the body to the url encoded serialization of the passed value, /// and also sets the `Content-Type: application/x-www-form-urlencoded` /// header. /// /// # Errors /// /// This method fails if the passed value cannot be serialized into /// url encoded format pub fn form(mut self, form: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { match serde_urlencoded::to_string(form) { Ok(body) => { req.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded"), ); *req.body_mut() = Some(body.into()); } Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { self.request = Err(err); } self } #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] /// Set the request json pub fn json(mut self, json: &T) -> RequestBuilder { let mut error = None; if let Ok(ref mut req) = self.request { match serde_json::to_vec(json) { Ok(body) => { req.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); *req.body_mut() = Some(body.into()); } Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { self.request = Err(err); } self } /// Enable HTTP basic authentication. pub fn basic_auth(self, username: U, password: Option

) -> RequestBuilder where U: fmt::Display, P: fmt::Display, { let header_value = crate::util::basic_auth(username, password); self.header(crate::header::AUTHORIZATION, header_value) } /// Enable HTTP bearer authentication. pub fn bearer_auth(self, token: T) -> RequestBuilder where T: fmt::Display, { let header_value = format!("Bearer {token}"); self.header(crate::header::AUTHORIZATION, header_value) } /// Set the request body. pub fn body>(mut self, body: T) -> RequestBuilder { if let Ok(ref mut req) = self.request { req.body = Some(body.into()); } self } /// TODO #[cfg(feature = "multipart")] #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))] pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder { if let Ok(ref mut req) = self.request { *req.body_mut() = Some(Body::from_form(multipart)) } self } /// Add a `Header` to this Request. pub fn header(mut self, key: K, value: V) -> RequestBuilder where HeaderName: TryFrom, >::Error: Into, HeaderValue: TryFrom, >::Error: Into, { let mut error = None; if let Ok(ref mut req) = self.request { match >::try_from(key) { Ok(key) => match >::try_from(value) { Ok(value) => { req.headers_mut().append(key, value); } Err(e) => error = Some(crate::error::builder(e.into())), }, Err(e) => error = Some(crate::error::builder(e.into())), }; } if let Some(err) = error { self.request = Err(err); } self } /// Add a set of Headers to the existing ones on this Request. /// /// The headers will be merged in to any already set. pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder { if let Ok(ref mut req) = self.request { crate::util::replace_headers(req.headers_mut(), headers); } self } /// Disable CORS on fetching the request. /// /// # WASM /// /// This option is only effective with WebAssembly target. /// /// The [request mode][mdn] will be set to 'no-cors'. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode pub fn fetch_mode_no_cors(mut self) -> RequestBuilder { if let Ok(ref mut req) = self.request { req.cors = false; } self } /// Set fetch credentials to 'same-origin' /// /// # WASM /// /// This option is only effective with WebAssembly target. /// /// The [request credentials][mdn] will be set to 'same-origin'. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder { if let Ok(ref mut req) = self.request { req.credentials = Some(RequestCredentials::SameOrigin); } self } /// Set fetch credentials to 'include' /// /// # WASM /// /// This option is only effective with WebAssembly target. /// /// The [request credentials][mdn] will be set to 'include'. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials pub fn fetch_credentials_include(mut self) -> RequestBuilder { if let Ok(ref mut req) = self.request { req.credentials = Some(RequestCredentials::Include); } self } /// Set fetch credentials to 'omit' /// /// # WASM /// /// This option is only effective with WebAssembly target. /// /// The [request credentials][mdn] will be set to 'omit'. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials pub fn fetch_credentials_omit(mut self) -> RequestBuilder { if let Ok(ref mut req) = self.request { req.credentials = Some(RequestCredentials::Omit); } self } /// Build a `Request`, which can be inspected, modified and executed with /// `Client::execute()`. pub fn build(self) -> crate::Result { self.request } /// Constructs the Request and sends it to the target URL, returning a /// future Response. /// /// # Errors /// /// This method fails if there was an error while sending request. /// /// # Example /// /// ```no_run /// # use reqwest::Error; /// # /// # async fn run() -> Result<(), Error> { /// let response = reqwest::Client::new() /// .get("https://hyper.rs") /// .send() /// .await?; /// # Ok(()) /// # } /// ``` pub async fn send(self) -> crate::Result { let req = self.request?; self.client.execute_request(req).await } /// Attempt to clone the RequestBuilder. /// /// `None` is returned if the RequestBuilder can not be cloned. /// /// # Examples /// /// ```no_run /// # use reqwest::Error; /// # /// # fn run() -> Result<(), Error> { /// let client = reqwest::Client::new(); /// let builder = client.post("http://httpbin.org/post") /// .body("from a &str!"); /// let clone = builder.try_clone(); /// assert!(clone.is_some()); /// # Ok(()) /// # } /// ``` pub fn try_clone(&self) -> Option { self.request .as_ref() .ok() .and_then(|req| req.try_clone()) .map(|req| RequestBuilder { client: self.client.clone(), request: Ok(req), }) } } impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_request_fields(&mut f.debug_struct("Request"), self).finish() } } impl fmt::Debug for RequestBuilder { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("RequestBuilder"); match self.request { Ok(ref req) => fmt_request_fields(&mut builder, req).finish(), Err(ref err) => builder.field("error", err).finish(), } } } fn fmt_request_fields<'a, 'b>( f: &'a mut fmt::DebugStruct<'a, 'b>, req: &Request, ) -> &'a mut fmt::DebugStruct<'a, 'b> { f.field("method", &req.method) .field("url", &req.url) .field("headers", &req.headers) } impl TryFrom> for Request where T: Into, { type Error = crate::Error; fn try_from(req: HttpRequest) -> crate::Result { let (parts, body) = req.into_parts(); let Parts { method, uri, headers, .. } = parts; let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?; Ok(Request { method, url, headers, body: Some(body.into()), cors: true, credentials: None, }) } } impl TryFrom for HttpRequest { type Error = crate::Error; fn try_from(req: Request) -> crate::Result { let Request { method, url, headers, body, .. } = req; let mut req = HttpRequest::builder() .method(method) .uri(url.as_str()) .body(body.unwrap_or_else(|| Body::from(Bytes::default()))) .map_err(crate::error::builder)?; *req.headers_mut() = headers; Ok(req) } } reqwest-0.11.27/src/wasm/response.rs000064400000000000000000000126351046102023000154370ustar 00000000000000use std::fmt; use bytes::Bytes; use http::{HeaderMap, StatusCode}; use js_sys::Uint8Array; use url::Url; use crate::wasm::AbortGuard; #[cfg(feature = "stream")] use wasm_bindgen::JsCast; #[cfg(feature = "stream")] use futures_util::stream::StreamExt; #[cfg(feature = "json")] use serde::de::DeserializeOwned; /// A Response to a submitted `Request`. pub struct Response { http: http::Response, _abort: AbortGuard, // Boxed to save space (11 words to 1 word), and it's not accessed // frequently internally. url: Box, } impl Response { pub(super) fn new( res: http::Response, url: Url, abort: AbortGuard, ) -> Response { Response { http: res, url: Box::new(url), _abort: abort, } } /// Get the `StatusCode` of this `Response`. #[inline] pub fn status(&self) -> StatusCode { self.http.status() } /// Get the `Headers` of this `Response`. #[inline] pub fn headers(&self) -> &HeaderMap { self.http.headers() } /// Get a mutable reference to the `Headers` of this `Response`. #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { self.http.headers_mut() } /// Get the content-length of this response, if known. /// /// Reasons it may not be known: /// /// - The server didn't send a `content-length` header. /// - The response is compressed and automatically decoded (thus changing /// the actual decoded length). pub fn content_length(&self) -> Option { self.headers() .get(http::header::CONTENT_LENGTH)? .to_str() .ok()? .parse() .ok() } /// Get the final `Url` of this `Response`. #[inline] pub fn url(&self) -> &Url { &self.url } /* It might not be possible to detect this in JS? /// Get the HTTP `Version` of this `Response`. #[inline] pub fn version(&self) -> Version { self.http.version() } */ /// Try to deserialize the response body as JSON. #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] pub async fn json(self) -> crate::Result { let full = self.bytes().await?; serde_json::from_slice(&full).map_err(crate::error::decode) } /// Get the response text. pub async fn text(self) -> crate::Result { let p = self .http .body() .text() .map_err(crate::error::wasm) .map_err(crate::error::decode)?; let js_val = super::promise::(p) .await .map_err(crate::error::decode)?; if let Some(s) = js_val.as_string() { Ok(s) } else { Err(crate::error::decode("response.text isn't string")) } } /// Get the response as bytes pub async fn bytes(self) -> crate::Result { let p = self .http .body() .array_buffer() .map_err(crate::error::wasm) .map_err(crate::error::decode)?; let buf_js = super::promise::(p) .await .map_err(crate::error::decode)?; let buffer = Uint8Array::new(&buf_js); let mut bytes = vec![0; buffer.length() as usize]; buffer.copy_to(&mut bytes); Ok(bytes.into()) } /// Convert the response into a `Stream` of `Bytes` from the body. #[cfg(feature = "stream")] pub fn bytes_stream(self) -> impl futures_core::Stream> { let web_response = self.http.into_body(); let abort = self._abort; let body = web_response .body() .expect("could not create wasm byte stream"); let body = wasm_streams::ReadableStream::from_raw(body.unchecked_into()); Box::pin(body.into_stream().map(move |buf_js| { // Keep the abort guard alive as long as this stream is. let _abort = &abort; let buffer = Uint8Array::new( &buf_js .map_err(crate::error::wasm) .map_err(crate::error::decode)?, ); let mut bytes = vec![0; buffer.length() as usize]; buffer.copy_to(&mut bytes); Ok(bytes.into()) })) } // util methods /// Turn a response into an error if the server returned an error. pub fn error_for_status(self) -> crate::Result { let status = self.status(); if status.is_client_error() || status.is_server_error() { Err(crate::error::status_code(*self.url, status)) } else { Ok(self) } } /// Turn a reference to a response into an error if the server returned an error. pub fn error_for_status_ref(&self) -> crate::Result<&Self> { let status = self.status(); if status.is_client_error() || status.is_server_error() { Err(crate::error::status_code(*self.url.clone(), status)) } else { Ok(self) } } } impl fmt::Debug for Response { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Response") //.field("url", self.url()) .field("status", &self.status()) .field("headers", self.headers()) .finish() } } reqwest-0.11.27/tests/badssl.rs000064400000000000000000000046071046102023000144550ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] #[cfg(all(feature = "__tls", not(feature = "rustls-tls-manual-roots")))] #[tokio::test] async fn test_badssl_modern() { let text = reqwest::Client::builder() .no_proxy() .build() .unwrap() .get("https://mozilla-modern.badssl.com/") .send() .await .unwrap() .text() .await .unwrap(); assert!(text.contains("mozilla-modern.badssl.com")); } #[cfg(any( feature = "rustls-tls-webpki-roots", feature = "rustls-tls-native-roots" ))] #[tokio::test] async fn test_rustls_badssl_modern() { let text = reqwest::Client::builder() .use_rustls_tls() .no_proxy() .build() .unwrap() .get("https://mozilla-modern.badssl.com/") .send() .await .unwrap() .text() .await .unwrap(); assert!(text.contains("mozilla-modern.badssl.com")); } #[cfg(feature = "__tls")] #[tokio::test] async fn test_badssl_self_signed() { let text = reqwest::Client::builder() .danger_accept_invalid_certs(true) .no_proxy() .build() .unwrap() .get("https://self-signed.badssl.com/") .send() .await .unwrap() .text() .await .unwrap(); assert!(text.contains("self-signed.badssl.com")); } #[cfg(feature = "__tls")] #[tokio::test] async fn test_badssl_no_built_in_roots() { let result = reqwest::Client::builder() .tls_built_in_root_certs(false) .no_proxy() .build() .unwrap() .get("https://mozilla-modern.badssl.com/") .send() .await; assert!(result.is_err()); } #[cfg(feature = "native-tls")] #[tokio::test] async fn test_badssl_wrong_host() { let text = reqwest::Client::builder() .danger_accept_invalid_hostnames(true) .no_proxy() .build() .unwrap() .get("https://wrong.host.badssl.com/") .send() .await .unwrap() .text() .await .unwrap(); assert!(text.contains("wrong.host.badssl.com")); let result = reqwest::Client::builder() .danger_accept_invalid_hostnames(true) .build() .unwrap() .get("https://self-signed.badssl.com/") .send() .await; assert!(result.is_err()); } reqwest-0.11.27/tests/blocking.rs000064400000000000000000000262541046102023000147770ustar 00000000000000mod support; use http::header::CONTENT_TYPE; use http::HeaderValue; use std::collections::HashMap; use support::server; #[test] fn test_response_text() { let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let url = format!("http://{}/text", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.content_length(), Some(5)); let body = res.text().unwrap(); assert_eq!(b"Hello", body.as_bytes()); } #[test] fn test_response_non_utf_8_text() { let server = server::http(move |_req| async { http::Response::builder() .header("content-type", "text/plain; charset=gbk") .body(b"\xc4\xe3\xba\xc3"[..].into()) .unwrap() }); let url = format!("http://{}/text", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.content_length(), Some(4)); let body = res.text().unwrap(); assert_eq!("你好", &body); assert_eq!(b"\xe4\xbd\xa0\xe5\xa5\xbd", body.as_bytes()); // Now it's utf-8 } #[test] #[cfg(feature = "json")] fn test_response_json() { let server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) }); let url = format!("http://{}/json", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.content_length(), Some(7)); let body = res.json::().unwrap(); assert_eq!("Hello", body); } #[test] fn test_response_copy_to() { let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let url = format!("http://{}/1", server.addr()); let mut res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); let mut dst = Vec::new(); res.copy_to(&mut dst).unwrap(); assert_eq!(dst, b"Hello"); } #[test] fn test_get() { let server = server::http(move |_req| async { http::Response::default() }); let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.remote_addr(), Some(server.addr())); assert_eq!(res.text().unwrap().len(), 0) } #[test] fn test_post() { let server = server::http(move |req| async move { assert_eq!(req.method(), "POST"); assert_eq!(req.headers()["content-length"], "5"); let data = hyper::body::to_bytes(req.into_body()).await.unwrap(); assert_eq!(&*data, b"Hello"); http::Response::default() }); let url = format!("http://{}/2", server.addr()); let res = reqwest::blocking::Client::new() .post(&url) .body("Hello") .send() .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[test] fn test_post_form() { let server = server::http(move |req| async move { assert_eq!(req.method(), "POST"); assert_eq!(req.headers()["content-length"], "24"); assert_eq!( req.headers()["content-type"], "application/x-www-form-urlencoded" ); let data = hyper::body::to_bytes(req.into_body()).await.unwrap(); assert_eq!(&*data, b"hello=world&sean=monstar"); http::Response::default() }); let form = &[("hello", "world"), ("sean", "monstar")]; let url = format!("http://{}/form", server.addr()); let res = reqwest::blocking::Client::new() .post(&url) .form(form) .send() .expect("request send"); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } /// Calling `Response::error_for_status`` on a response with status in 4xx /// returns a error. #[test] fn test_error_for_status_4xx() { let server = server::http(move |_req| async { http::Response::builder() .status(400) .body(Default::default()) .unwrap() }); let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); let err = res.error_for_status().unwrap_err(); assert!(err.is_status()); assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST)); } /// Calling `Response::error_for_status`` on a response with status in 5xx /// returns a error. #[test] fn test_error_for_status_5xx() { let server = server::http(move |_req| async { http::Response::builder() .status(500) .body(Default::default()) .unwrap() }); let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); let err = res.error_for_status().unwrap_err(); assert!(err.is_status()); assert_eq!( err.status(), Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR) ); } #[test] fn test_default_headers() { let server = server::http(move |req| async move { assert_eq!(req.headers()["reqwest-test"], "orly"); http::Response::default() }); let mut headers = http::HeaderMap::with_capacity(1); headers.insert("reqwest-test", "orly".parse().unwrap()); let client = reqwest::blocking::Client::builder() .default_headers(headers) .build() .unwrap(); let url = format!("http://{}/1", server.addr()); let res = client.get(&url).send().unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[test] fn test_override_default_headers() { let server = server::http(move |req| { async move { // not 'iamatoken' assert_eq!(req.headers()[&http::header::AUTHORIZATION], "secret"); http::Response::default() } }); let mut headers = http::HeaderMap::with_capacity(1); headers.insert( http::header::AUTHORIZATION, http::header::HeaderValue::from_static("iamatoken"), ); let client = reqwest::blocking::Client::builder() .default_headers(headers) .build() .unwrap(); let url = format!("http://{}/3", server.addr()); let res = client .get(&url) .header( http::header::AUTHORIZATION, http::header::HeaderValue::from_static("secret"), ) .send() .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[test] fn test_appended_headers_not_overwritten() { let server = server::http(move |req| async move { let mut accepts = req.headers().get_all("accept").into_iter(); assert_eq!(accepts.next().unwrap(), "application/json"); assert_eq!(accepts.next().unwrap(), "application/json+hal"); assert_eq!(accepts.next(), None); http::Response::default() }); let client = reqwest::blocking::Client::new(); let url = format!("http://{}/4", server.addr()); let res = client .get(&url) .header(header::ACCEPT, "application/json") .header(header::ACCEPT, "application/json+hal") .send() .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); // make sure this also works with default headers use reqwest::header; let mut headers = header::HeaderMap::with_capacity(1); headers.insert( header::ACCEPT, header::HeaderValue::from_static("text/html"), ); let client = reqwest::blocking::Client::builder() .default_headers(headers) .build() .unwrap(); let url = format!("http://{}/4", server.addr()); let res = client .get(&url) .header(header::ACCEPT, "application/json") .header(header::ACCEPT, "application/json+hal") .send() .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[cfg_attr(not(debug_assertions), ignore)] #[test] #[should_panic] fn test_blocking_inside_a_runtime() { let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let url = format!("http://{}/text", server.addr()); let rt = tokio::runtime::Builder::new_current_thread() .build() .expect("new rt"); rt.block_on(async move { let _should_panic = reqwest::blocking::get(&url); }); } #[cfg(feature = "default-tls")] #[test] fn test_allowed_methods_blocking() { let resp = reqwest::blocking::Client::builder() .https_only(true) .build() .expect("client builder") .get("https://google.com") .send(); assert_eq!(resp.is_err(), false); let resp = reqwest::blocking::Client::builder() .https_only(true) .build() .expect("client builder") .get("http://google.com") .send(); assert_eq!(resp.is_err(), true); } /// Test that a [`reqwest::blocking::Body`] can be created from [`bytes::Bytes`]. #[test] fn test_body_from_bytes() { let body = "abc"; // No external calls are needed. Only the request building is tested. let request = reqwest::blocking::Client::builder() .build() .expect("Could not build the client") .put("https://google.com") .body(bytes::Bytes::from(body)) .build() .expect("Invalid body"); assert_eq!(request.body().unwrap().as_bytes(), Some(body.as_bytes())); } #[test] #[cfg(feature = "json")] fn blocking_add_json_default_content_type_if_not_set_manually() { let mut map = HashMap::new(); map.insert("body", "json"); let content_type = HeaderValue::from_static("application/vnd.api+json"); let req = reqwest::blocking::Client::new() .post("https://google.com/") .header(CONTENT_TYPE, &content_type) .json(&map) .build() .expect("request is not valid"); assert_eq!(content_type, req.headers().get(CONTENT_TYPE).unwrap()); } #[test] #[cfg(feature = "json")] fn blocking_update_json_content_type_if_set_manually() { let mut map = HashMap::new(); map.insert("body", "json"); let req = reqwest::blocking::Client::new() .post("https://google.com/") .json(&map) .build() .expect("request is not valid"); assert_eq!("application/json", req.headers().get(CONTENT_TYPE).unwrap()); } #[test] fn test_response_no_tls_info_for_http() { let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let url = format!("http://{}/text", server.addr()); let client = reqwest::blocking::Client::builder() .tls_info(true) .build() .unwrap(); let res = client.get(&url).send().unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.content_length(), Some(5)); let tls_info = res.extensions().get::(); assert_eq!(tls_info.is_none(), true); let body = res.text().unwrap(); assert_eq!(b"Hello", body.as_bytes()); } reqwest-0.11.27/tests/brotli.rs000064400000000000000000000077061046102023000145030ustar 00000000000000mod support; use std::io::Read; use support::server; #[tokio::test] async fn brotli_response() { brotli_case(10_000, 4096).await; } #[tokio::test] async fn brotli_single_byte_chunks() { brotli_case(10, 1).await; } #[tokio::test] async fn test_brotli_empty_body() { let server = server::http(move |req| async move { assert_eq!(req.method(), "HEAD"); http::Response::builder() .header("content-encoding", "br") .header("content-length", 100) .body(Default::default()) .unwrap() }); let client = reqwest::Client::new(); let res = client .head(&format!("http://{}/brotli", server.addr())) .send() .await .unwrap(); let body = res.text().await.unwrap(); assert_eq!(body, ""); } #[tokio::test] async fn test_accept_header_is_not_changed_if_set() { let server = server::http(move |req| async move { assert_eq!(req.headers()["accept"], "application/json"); assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("br")); http::Response::default() }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/accept", server.addr())) .header( reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json"), ) .send() .await .unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn test_accept_encoding_header_is_not_changed_if_set() { let server = server::http(move |req| async move { assert_eq!(req.headers()["accept"], "*/*"); assert_eq!(req.headers()["accept-encoding"], "identity"); http::Response::default() }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/accept-encoding", server.addr())) .header( reqwest::header::ACCEPT_ENCODING, reqwest::header::HeaderValue::from_static("identity"), ) .send() .await .unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } async fn brotli_case(response_size: usize, chunk_size: usize) { use futures_util::stream::StreamExt; let content: String = (0..response_size) .into_iter() .map(|i| format!("test {i}")) .collect(); let mut encoder = brotli_crate::CompressorReader::new(content.as_bytes(), 4096, 5, 20); let mut brotlied_content = Vec::new(); encoder.read_to_end(&mut brotlied_content).unwrap(); let mut response = format!( "\ HTTP/1.1 200 OK\r\n\ Server: test-accept\r\n\ Content-Encoding: br\r\n\ Content-Length: {}\r\n\ \r\n", &brotlied_content.len() ) .into_bytes(); response.extend(&brotlied_content); let server = server::http(move |req| { assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("br")); let brotlied = brotlied_content.clone(); async move { let len = brotlied.len(); let stream = futures_util::stream::unfold((brotlied, 0), move |(brotlied, pos)| async move { let chunk = brotlied.chunks(chunk_size).nth(pos)?.to_vec(); Some((chunk, (brotlied, pos + 1))) }); let body = hyper::Body::wrap_stream(stream.map(Ok::<_, std::convert::Infallible>)); http::Response::builder() .header("content-encoding", "br") .header("content-length", len) .body(body) .unwrap() } }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/brotli", server.addr())) .send() .await .expect("response"); let body = res.text().await.expect("text"); assert_eq!(body, content); } reqwest-0.11.27/tests/client.rs000064400000000000000000000350461046102023000144640ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] mod support; use futures_util::stream::StreamExt; use support::delay_server; use support::server; #[cfg(feature = "json")] use http::header::CONTENT_TYPE; #[cfg(feature = "json")] use std::collections::HashMap; use reqwest::Client; #[tokio::test] async fn auto_headers() { let server = server::http(move |req| async move { assert_eq!(req.method(), "GET"); assert_eq!(req.headers()["accept"], "*/*"); assert_eq!(req.headers().get("user-agent"), None); if cfg!(feature = "gzip") { assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("gzip")); } if cfg!(feature = "brotli") { assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("br")); } if cfg!(feature = "deflate") { assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("deflate")); } http::Response::default() }); let url = format!("http://{}/1", server.addr()); let res = reqwest::Client::builder() .no_proxy() .build() .unwrap() .get(&url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.remote_addr(), Some(server.addr())); } #[tokio::test] async fn user_agent() { let server = server::http(move |req| async move { assert_eq!(req.headers()["user-agent"], "reqwest-test-agent"); http::Response::default() }); let url = format!("http://{}/ua", server.addr()); let res = reqwest::Client::builder() .user_agent("reqwest-test-agent") .build() .expect("client builder") .get(&url) .send() .await .expect("request"); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn response_text() { let _ = env_logger::try_init(); let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let client = Client::new(); let res = client .get(&format!("http://{}/text", server.addr())) .send() .await .expect("Failed to get"); assert_eq!(res.content_length(), Some(5)); let text = res.text().await.expect("Failed to get text"); assert_eq!("Hello", text); } #[tokio::test] async fn response_bytes() { let _ = env_logger::try_init(); let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let client = Client::new(); let res = client .get(&format!("http://{}/bytes", server.addr())) .send() .await .expect("Failed to get"); assert_eq!(res.content_length(), Some(5)); let bytes = res.bytes().await.expect("res.bytes()"); assert_eq!("Hello", bytes); } #[tokio::test] #[cfg(feature = "json")] async fn response_json() { let _ = env_logger::try_init(); let server = server::http(move |_req| async { http::Response::new("\"Hello\"".into()) }); let client = Client::new(); let res = client .get(&format!("http://{}/json", server.addr())) .send() .await .expect("Failed to get"); let text = res.json::().await.expect("Failed to get json"); assert_eq!("Hello", text); } #[tokio::test] async fn body_pipe_response() { let _ = env_logger::try_init(); let server = server::http(move |mut req| async move { if req.uri() == "/get" { http::Response::new("pipe me".into()) } else { assert_eq!(req.uri(), "/pipe"); assert_eq!(req.headers()["transfer-encoding"], "chunked"); let mut full: Vec = Vec::new(); while let Some(item) = req.body_mut().next().await { full.extend(&*item.unwrap()); } assert_eq!(full, b"pipe me"); http::Response::default() } }); let client = Client::new(); let res1 = client .get(&format!("http://{}/get", server.addr())) .send() .await .expect("get1"); assert_eq!(res1.status(), reqwest::StatusCode::OK); assert_eq!(res1.content_length(), Some(7)); // and now ensure we can "pipe" the response to another request let res2 = client .post(&format!("http://{}/pipe", server.addr())) .body(res1) .send() .await .expect("res2"); assert_eq!(res2.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn overridden_dns_resolution_with_gai() { let _ = env_logger::builder().is_test(true).try_init(); let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let overridden_domain = "rust-lang.org"; let url = format!( "http://{overridden_domain}:{}/domain_override", server.addr().port() ); let client = reqwest::Client::builder() .resolve(overridden_domain, server.addr()) .build() .expect("client builder"); let req = client.get(&url); let res = req.send().await.expect("request"); assert_eq!(res.status(), reqwest::StatusCode::OK); let text = res.text().await.expect("Failed to get text"); assert_eq!("Hello", text); } #[tokio::test] async fn overridden_dns_resolution_with_gai_multiple() { let _ = env_logger::builder().is_test(true).try_init(); let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let overridden_domain = "rust-lang.org"; let url = format!( "http://{overridden_domain}:{}/domain_override", server.addr().port() ); // the server runs on IPv4 localhost, so provide both IPv4 and IPv6 and let the happy eyeballs // algorithm decide which address to use. let client = reqwest::Client::builder() .resolve_to_addrs( overridden_domain, &[ std::net::SocketAddr::new( std::net::IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), server.addr().port(), ), server.addr(), ], ) .build() .expect("client builder"); let req = client.get(&url); let res = req.send().await.expect("request"); assert_eq!(res.status(), reqwest::StatusCode::OK); let text = res.text().await.expect("Failed to get text"); assert_eq!("Hello", text); } #[cfg(feature = "hickory-dns")] #[tokio::test] async fn overridden_dns_resolution_with_hickory_dns() { let _ = env_logger::builder().is_test(true).try_init(); let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let overridden_domain = "rust-lang.org"; let url = format!( "http://{overridden_domain}:{}/domain_override", server.addr().port() ); let client = reqwest::Client::builder() .resolve(overridden_domain, server.addr()) .hickory_dns(true) .build() .expect("client builder"); let req = client.get(&url); let res = req.send().await.expect("request"); assert_eq!(res.status(), reqwest::StatusCode::OK); let text = res.text().await.expect("Failed to get text"); assert_eq!("Hello", text); } #[cfg(feature = "hickory-dns")] #[tokio::test] async fn overridden_dns_resolution_with_hickory_dns_multiple() { let _ = env_logger::builder().is_test(true).try_init(); let server = server::http(move |_req| async { http::Response::new("Hello".into()) }); let overridden_domain = "rust-lang.org"; let url = format!( "http://{overridden_domain}:{}/domain_override", server.addr().port() ); // the server runs on IPv4 localhost, so provide both IPv4 and IPv6 and let the happy eyeballs // algorithm decide which address to use. let client = reqwest::Client::builder() .resolve_to_addrs( overridden_domain, &[ std::net::SocketAddr::new( std::net::IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), server.addr().port(), ), server.addr(), ], ) .hickory_dns(true) .build() .expect("client builder"); let req = client.get(&url); let res = req.send().await.expect("request"); assert_eq!(res.status(), reqwest::StatusCode::OK); let text = res.text().await.expect("Failed to get text"); assert_eq!("Hello", text); } #[cfg(any(feature = "native-tls", feature = "__rustls",))] #[test] fn use_preconfigured_tls_with_bogus_backend() { struct DefinitelyNotTls; reqwest::Client::builder() .use_preconfigured_tls(DefinitelyNotTls) .build() .expect_err("definitely is not TLS"); } #[cfg(feature = "native-tls")] #[test] fn use_preconfigured_native_tls_default() { extern crate native_tls_crate; let tls = native_tls_crate::TlsConnector::builder() .build() .expect("tls builder"); reqwest::Client::builder() .use_preconfigured_tls(tls) .build() .expect("preconfigured default tls"); } #[cfg(feature = "__rustls")] #[test] fn use_preconfigured_rustls_default() { extern crate rustls; let root_cert_store = rustls::RootCertStore::empty(); let tls = rustls::ClientConfig::builder() .with_safe_defaults() .with_root_certificates(root_cert_store) .with_no_client_auth(); reqwest::Client::builder() .use_preconfigured_tls(tls) .build() .expect("preconfigured rustls tls"); } #[cfg(feature = "__rustls")] #[tokio::test] #[ignore = "Needs TLS support in the test server"] async fn http2_upgrade() { let server = server::http(move |_| async move { http::Response::default() }); let url = format!("https://localhost:{}", server.addr().port()); let res = reqwest::Client::builder() .danger_accept_invalid_certs(true) .use_rustls_tls() .build() .expect("client builder") .get(&url) .send() .await .expect("request"); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!(res.version(), reqwest::Version::HTTP_2); } #[cfg(feature = "default-tls")] #[tokio::test] async fn test_allowed_methods() { let resp = reqwest::Client::builder() .https_only(true) .build() .expect("client builder") .get("https://google.com") .send() .await; assert!(resp.is_ok()); let resp = reqwest::Client::builder() .https_only(true) .build() .expect("client builder") .get("http://google.com") .send() .await; assert!(resp.is_err()); } #[test] #[cfg(feature = "json")] fn add_json_default_content_type_if_not_set_manually() { let mut map = HashMap::new(); map.insert("body", "json"); let content_type = http::HeaderValue::from_static("application/vnd.api+json"); let req = Client::new() .post("https://google.com/") .header(CONTENT_TYPE, &content_type) .json(&map) .build() .expect("request is not valid"); assert_eq!(content_type, req.headers().get(CONTENT_TYPE).unwrap()); } #[test] #[cfg(feature = "json")] fn update_json_content_type_if_set_manually() { let mut map = HashMap::new(); map.insert("body", "json"); let req = Client::new() .post("https://google.com/") .json(&map) .build() .expect("request is not valid"); assert_eq!("application/json", req.headers().get(CONTENT_TYPE).unwrap()); } #[cfg(all(feature = "__tls", not(feature = "rustls-tls-manual-roots")))] #[tokio::test] async fn test_tls_info() { let resp = reqwest::Client::builder() .tls_info(true) .build() .expect("client builder") .get("https://google.com") .send() .await .expect("response"); let tls_info = resp.extensions().get::(); assert!(tls_info.is_some()); let tls_info = tls_info.unwrap(); let peer_certificate = tls_info.peer_certificate(); assert!(peer_certificate.is_some()); let der = peer_certificate.unwrap(); assert_eq!(der[0], 0x30); // ASN.1 SEQUENCE let resp = reqwest::Client::builder() .build() .expect("client builder") .get("https://google.com") .send() .await .expect("response"); let tls_info = resp.extensions().get::(); assert!(tls_info.is_none()); } // NOTE: using the default "curernt_thread" runtime here would cause the test to // fail, because the only thread would block until `panic_rx` receives a // notification while the client needs to be driven to get the graceful shutdown // done. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn highly_concurrent_requests_to_http2_server_with_low_max_concurrent_streams() { let client = reqwest::Client::builder() .http2_prior_knowledge() .build() .unwrap(); let server = server::http_with_config( move |req| async move { assert_eq!(req.version(), http::Version::HTTP_2); http::Response::default() }, |builder| builder.http2_only(true).http2_max_concurrent_streams(1), ); let url = format!("http://{}", server.addr()); let futs = (0..100).map(|_| { let client = client.clone(); let url = url.clone(); async move { let res = client.get(&url).send().await.unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } }); futures_util::future::join_all(futs).await; } #[tokio::test] async fn highly_concurrent_requests_to_slow_http2_server_with_low_max_concurrent_streams() { let client = reqwest::Client::builder() .http2_prior_knowledge() .build() .unwrap(); let server = delay_server::Server::new( move |req| async move { assert_eq!(req.version(), http::Version::HTTP_2); http::Response::default() }, |mut http| { http.http2_only(true).http2_max_concurrent_streams(1); http }, std::time::Duration::from_secs(2), ) .await; let url = format!("http://{}", server.addr()); let futs = (0..100).map(|_| { let client = client.clone(); let url = url.clone(); async move { let res = client.get(&url).send().await.unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } }); futures_util::future::join_all(futs).await; server.shutdown().await; } reqwest-0.11.27/tests/cookie.rs000064400000000000000000000137531046102023000144600ustar 00000000000000mod support; use support::server; #[tokio::test] async fn cookie_response_accessor() { let server = server::http(move |_req| async move { http::Response::builder() .header("Set-Cookie", "key=val") .header( "Set-Cookie", "expires=1; Expires=Wed, 21 Oct 2015 07:28:00 GMT", ) .header("Set-Cookie", "path=1; Path=/the-path") .header("Set-Cookie", "maxage=1; Max-Age=100") .header("Set-Cookie", "domain=1; Domain=mydomain") .header("Set-Cookie", "secure=1; Secure") .header("Set-Cookie", "httponly=1; HttpOnly") .header("Set-Cookie", "samesitelax=1; SameSite=Lax") .header("Set-Cookie", "samesitestrict=1; SameSite=Strict") .body(Default::default()) .unwrap() }); let client = reqwest::Client::new(); let url = format!("http://{}/", server.addr()); let res = client.get(&url).send().await.unwrap(); let cookies = res.cookies().collect::>(); // key=val assert_eq!(cookies[0].name(), "key"); assert_eq!(cookies[0].value(), "val"); // expires assert_eq!(cookies[1].name(), "expires"); assert_eq!( cookies[1].expires().unwrap(), std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_445_412_480) ); // path assert_eq!(cookies[2].name(), "path"); assert_eq!(cookies[2].path().unwrap(), "/the-path"); // max-age assert_eq!(cookies[3].name(), "maxage"); assert_eq!( cookies[3].max_age().unwrap(), std::time::Duration::from_secs(100) ); // domain assert_eq!(cookies[4].name(), "domain"); assert_eq!(cookies[4].domain().unwrap(), "mydomain"); // secure assert_eq!(cookies[5].name(), "secure"); assert_eq!(cookies[5].secure(), true); // httponly assert_eq!(cookies[6].name(), "httponly"); assert_eq!(cookies[6].http_only(), true); // samesitelax assert_eq!(cookies[7].name(), "samesitelax"); assert!(cookies[7].same_site_lax()); // samesitestrict assert_eq!(cookies[8].name(), "samesitestrict"); assert!(cookies[8].same_site_strict()); } #[tokio::test] async fn cookie_store_simple() { let server = server::http(move |req| async move { if req.uri() == "/2" { assert_eq!(req.headers()["cookie"], "key=val"); } http::Response::builder() .header("Set-Cookie", "key=val; HttpOnly") .body(Default::default()) .unwrap() }); let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); let url = format!("http://{}/", server.addr()); client.get(&url).send().await.unwrap(); let url = format!("http://{}/2", server.addr()); client.get(&url).send().await.unwrap(); } #[tokio::test] async fn cookie_store_overwrite_existing() { let server = server::http(move |req| async move { if req.uri() == "/" { http::Response::builder() .header("Set-Cookie", "key=val") .body(Default::default()) .unwrap() } else if req.uri() == "/2" { assert_eq!(req.headers()["cookie"], "key=val"); http::Response::builder() .header("Set-Cookie", "key=val2") .body(Default::default()) .unwrap() } else { assert_eq!(req.uri(), "/3"); assert_eq!(req.headers()["cookie"], "key=val2"); http::Response::default() } }); let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); let url = format!("http://{}/", server.addr()); client.get(&url).send().await.unwrap(); let url = format!("http://{}/2", server.addr()); client.get(&url).send().await.unwrap(); let url = format!("http://{}/3", server.addr()); client.get(&url).send().await.unwrap(); } #[tokio::test] async fn cookie_store_max_age() { let server = server::http(move |req| async move { assert_eq!(req.headers().get("cookie"), None); http::Response::builder() .header("Set-Cookie", "key=val; Max-Age=0") .body(Default::default()) .unwrap() }); let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); let url = format!("http://{}/", server.addr()); client.get(&url).send().await.unwrap(); client.get(&url).send().await.unwrap(); } #[tokio::test] async fn cookie_store_expires() { let server = server::http(move |req| async move { assert_eq!(req.headers().get("cookie"), None); http::Response::builder() .header( "Set-Cookie", "key=val; Expires=Wed, 21 Oct 2015 07:28:00 GMT", ) .body(Default::default()) .unwrap() }); let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); let url = format!("http://{}/", server.addr()); client.get(&url).send().await.unwrap(); client.get(&url).send().await.unwrap(); } #[tokio::test] async fn cookie_store_path() { let server = server::http(move |req| async move { if req.uri() == "/" { assert_eq!(req.headers().get("cookie"), None); http::Response::builder() .header("Set-Cookie", "key=val; Path=/subpath") .body(Default::default()) .unwrap() } else { assert_eq!(req.uri(), "/subpath"); assert_eq!(req.headers()["cookie"], "key=val"); http::Response::default() } }); let client = reqwest::Client::builder() .cookie_store(true) .build() .unwrap(); let url = format!("http://{}/", server.addr()); client.get(&url).send().await.unwrap(); client.get(&url).send().await.unwrap(); let url = format!("http://{}/subpath", server.addr()); client.get(&url).send().await.unwrap(); } reqwest-0.11.27/tests/deflate.rs000064400000000000000000000101431046102023000146010ustar 00000000000000mod support; use std::io::Write; use support::server; #[tokio::test] async fn deflate_response() { deflate_case(10_000, 4096).await; } #[tokio::test] async fn deflate_single_byte_chunks() { deflate_case(10, 1).await; } #[tokio::test] async fn test_deflate_empty_body() { let server = server::http(move |req| async move { assert_eq!(req.method(), "HEAD"); http::Response::builder() .header("content-encoding", "deflate") .header("content-length", 100) .body(Default::default()) .unwrap() }); let client = reqwest::Client::new(); let res = client .head(&format!("http://{}/deflate", server.addr())) .send() .await .unwrap(); let body = res.text().await.unwrap(); assert_eq!(body, ""); } #[tokio::test] async fn test_accept_header_is_not_changed_if_set() { let server = server::http(move |req| async move { assert_eq!(req.headers()["accept"], "application/json"); assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("deflate")); http::Response::default() }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/accept", server.addr())) .header( reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json"), ) .send() .await .unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn test_accept_encoding_header_is_not_changed_if_set() { let server = server::http(move |req| async move { assert_eq!(req.headers()["accept"], "*/*"); assert_eq!(req.headers()["accept-encoding"], "identity"); http::Response::default() }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/accept-encoding", server.addr())) .header( reqwest::header::ACCEPT_ENCODING, reqwest::header::HeaderValue::from_static("identity"), ) .send() .await .unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } async fn deflate_case(response_size: usize, chunk_size: usize) { use futures_util::stream::StreamExt; let content: String = (0..response_size) .into_iter() .map(|i| format!("test {i}")) .collect(); let mut encoder = libflate::zlib::Encoder::new(Vec::new()).unwrap(); match encoder.write(content.as_bytes()) { Ok(n) => assert!(n > 0, "Failed to write to encoder."), _ => panic!("Failed to deflate encode string."), }; let deflated_content = encoder.finish().into_result().unwrap(); let mut response = format!( "\ HTTP/1.1 200 OK\r\n\ Server: test-accept\r\n\ Content-Encoding: deflate\r\n\ Content-Length: {}\r\n\ \r\n", &deflated_content.len() ) .into_bytes(); response.extend(&deflated_content); let server = server::http(move |req| { assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("deflate")); let deflated = deflated_content.clone(); async move { let len = deflated.len(); let stream = futures_util::stream::unfold((deflated, 0), move |(deflated, pos)| async move { let chunk = deflated.chunks(chunk_size).nth(pos)?.to_vec(); Some((chunk, (deflated, pos + 1))) }); let body = hyper::Body::wrap_stream(stream.map(Ok::<_, std::convert::Infallible>)); http::Response::builder() .header("content-encoding", "deflate") .header("content-length", len) .body(body) .unwrap() } }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/deflate", server.addr())) .send() .await .expect("response"); let body = res.text().await.expect("text"); assert_eq!(body, content); } reqwest-0.11.27/tests/gzip.rs000064400000000000000000000100601046102023000141440ustar 00000000000000mod support; use support::server; use std::io::Write; #[tokio::test] async fn gzip_response() { gzip_case(10_000, 4096).await; } #[tokio::test] async fn gzip_single_byte_chunks() { gzip_case(10, 1).await; } #[tokio::test] async fn test_gzip_empty_body() { let server = server::http(move |req| async move { assert_eq!(req.method(), "HEAD"); http::Response::builder() .header("content-encoding", "gzip") .header("content-length", 100) .body(Default::default()) .unwrap() }); let client = reqwest::Client::new(); let res = client .head(&format!("http://{}/gzip", server.addr())) .send() .await .unwrap(); let body = res.text().await.unwrap(); assert_eq!(body, ""); } #[tokio::test] async fn test_accept_header_is_not_changed_if_set() { let server = server::http(move |req| async move { assert_eq!(req.headers()["accept"], "application/json"); assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("gzip")); http::Response::default() }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/accept", server.addr())) .header( reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json"), ) .send() .await .unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn test_accept_encoding_header_is_not_changed_if_set() { let server = server::http(move |req| async move { assert_eq!(req.headers()["accept"], "*/*"); assert_eq!(req.headers()["accept-encoding"], "identity"); http::Response::default() }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/accept-encoding", server.addr())) .header( reqwest::header::ACCEPT_ENCODING, reqwest::header::HeaderValue::from_static("identity"), ) .send() .await .unwrap(); assert_eq!(res.status(), reqwest::StatusCode::OK); } async fn gzip_case(response_size: usize, chunk_size: usize) { use futures_util::stream::StreamExt; let content: String = (0..response_size) .into_iter() .map(|i| format!("test {i}")) .collect(); let mut encoder = libflate::gzip::Encoder::new(Vec::new()).unwrap(); match encoder.write(content.as_bytes()) { Ok(n) => assert!(n > 0, "Failed to write to encoder."), _ => panic!("Failed to gzip encode string."), }; let gzipped_content = encoder.finish().into_result().unwrap(); let mut response = format!( "\ HTTP/1.1 200 OK\r\n\ Server: test-accept\r\n\ Content-Encoding: gzip\r\n\ Content-Length: {}\r\n\ \r\n", &gzipped_content.len() ) .into_bytes(); response.extend(&gzipped_content); let server = server::http(move |req| { assert!(req.headers()["accept-encoding"] .to_str() .unwrap() .contains("gzip")); let gzipped = gzipped_content.clone(); async move { let len = gzipped.len(); let stream = futures_util::stream::unfold((gzipped, 0), move |(gzipped, pos)| async move { let chunk = gzipped.chunks(chunk_size).nth(pos)?.to_vec(); Some((chunk, (gzipped, pos + 1))) }); let body = hyper::Body::wrap_stream(stream.map(Ok::<_, std::convert::Infallible>)); http::Response::builder() .header("content-encoding", "gzip") .header("content-length", len) .body(body) .unwrap() } }); let client = reqwest::Client::new(); let res = client .get(&format!("http://{}/gzip", server.addr())) .send() .await .expect("response"); let body = res.text().await.expect("text"); assert_eq!(body, content); } reqwest-0.11.27/tests/multipart.rs000064400000000000000000000116511046102023000152230ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] mod support; use futures_util::stream::StreamExt; use support::server; #[tokio::test] async fn text_part() { let _ = env_logger::try_init(); let form = reqwest::multipart::Form::new().text("foo", "bar"); let expected_body = format!( "\ --{0}\r\n\ Content-Disposition: form-data; name=\"foo\"\r\n\r\n\ bar\r\n\ --{0}--\r\n\ ", form.boundary() ); let ct = format!("multipart/form-data; boundary={}", form.boundary()); let server = server::http(move |mut req| { let ct = ct.clone(); let expected_body = expected_body.clone(); async move { assert_eq!(req.method(), "POST"); assert_eq!(req.headers()["content-type"], ct); assert_eq!( req.headers()["content-length"], expected_body.len().to_string() ); let mut full: Vec = Vec::new(); while let Some(item) = req.body_mut().next().await { full.extend(&*item.unwrap()); } assert_eq!(full, expected_body.as_bytes()); http::Response::default() } }); let url = format!("http://{}/multipart/1", server.addr()); let res = reqwest::Client::new() .post(&url) .multipart(form) .send() .await .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[cfg(feature = "stream")] #[tokio::test] async fn stream_part() { use futures_util::{future, stream}; let _ = env_logger::try_init(); let stream = reqwest::Body::wrap_stream(stream::once(future::ready(Ok::<_, reqwest::Error>( "part1 part2".to_owned(), )))); let part = reqwest::multipart::Part::stream(stream); let form = reqwest::multipart::Form::new() .text("foo", "bar") .part("part_stream", part); let expected_body = format!( "\ --{0}\r\n\ Content-Disposition: form-data; name=\"foo\"\r\n\ \r\n\ bar\r\n\ --{0}\r\n\ Content-Disposition: form-data; name=\"part_stream\"\r\n\ \r\n\ part1 part2\r\n\ --{0}--\r\n\ ", form.boundary() ); let ct = format!("multipart/form-data; boundary={}", form.boundary()); let server = server::http(move |mut req| { let ct = ct.clone(); let expected_body = expected_body.clone(); async move { assert_eq!(req.method(), "POST"); assert_eq!(req.headers()["content-type"], ct); assert_eq!(req.headers()["transfer-encoding"], "chunked"); let mut full: Vec = Vec::new(); while let Some(item) = req.body_mut().next().await { full.extend(&*item.unwrap()); } assert_eq!(full, expected_body.as_bytes()); http::Response::default() } }); let url = format!("http://{}/multipart/1", server.addr()); let client = reqwest::Client::new(); let res = client .post(&url) .multipart(form) .send() .await .expect("Failed to post multipart"); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[cfg(feature = "blocking")] #[test] fn blocking_file_part() { let _ = env_logger::try_init(); let form = reqwest::blocking::multipart::Form::new() .file("foo", "Cargo.lock") .unwrap(); let fcontents = std::fs::read_to_string("Cargo.lock").unwrap(); let expected_body = format!( "\ --{0}\r\n\ Content-Disposition: form-data; name=\"foo\"; filename=\"Cargo.lock\"\r\n\ Content-Type: application/octet-stream\r\n\r\n\ {1}\r\n\ --{0}--\r\n\ ", form.boundary(), fcontents ); let ct = format!("multipart/form-data; boundary={}", form.boundary()); let server = server::http(move |mut req| { let ct = ct.clone(); let expected_body = expected_body.clone(); async move { assert_eq!(req.method(), "POST"); assert_eq!(req.headers()["content-type"], ct); // files know their exact size assert_eq!( req.headers()["content-length"], expected_body.len().to_string() ); let mut full: Vec = Vec::new(); while let Some(item) = req.body_mut().next().await { full.extend(&*item.unwrap()); } assert_eq!(full, expected_body.as_bytes()); http::Response::default() } }); let url = format!("http://{}/multipart/2", server.addr()); let res = reqwest::blocking::Client::new() .post(&url) .multipart(form) .send() .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } reqwest-0.11.27/tests/proxy.rs000064400000000000000000000135541046102023000143670ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] mod support; use support::server; use std::env; #[tokio::test] async fn http_proxy() { let url = "http://hyper.rs/prox"; let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), url); assert_eq!(req.headers()["host"], "hyper.rs"); async { http::Response::default() } }); let proxy = format!("http://{}", server.addr()); let res = reqwest::Client::builder() .proxy(reqwest::Proxy::http(&proxy).unwrap()) .build() .unwrap() .get(url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn http_proxy_basic_auth() { let url = "http://hyper.rs/prox"; let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), url); assert_eq!(req.headers()["host"], "hyper.rs"); assert_eq!( req.headers()["proxy-authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); async { http::Response::default() } }); let proxy = format!("http://{}", server.addr()); let res = reqwest::Client::builder() .proxy( reqwest::Proxy::http(&proxy) .unwrap() .basic_auth("Aladdin", "open sesame"), ) .build() .unwrap() .get(url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn http_proxy_basic_auth_parsed() { let url = "http://hyper.rs/prox"; let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), url); assert_eq!(req.headers()["host"], "hyper.rs"); assert_eq!( req.headers()["proxy-authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); async { http::Response::default() } }); let proxy = format!("http://Aladdin:open sesame@{}", server.addr()); let res = reqwest::Client::builder() .proxy(reqwest::Proxy::http(&proxy).unwrap()) .build() .unwrap() .get(url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[tokio::test] async fn system_http_proxy_basic_auth_parsed() { let url = "http://hyper.rs/prox"; let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), url); assert_eq!(req.headers()["host"], "hyper.rs"); assert_eq!( req.headers()["proxy-authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ); async { http::Response::default() } }); // save system setting first. let system_proxy = env::var("http_proxy"); // set-up http proxy. env::set_var( "http_proxy", format!("http://Aladdin:open sesame@{}", server.addr()), ); let res = reqwest::Client::builder() .build() .unwrap() .get(url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); // reset user setting. match system_proxy { Err(_) => env::remove_var("http_proxy"), Ok(proxy) => env::set_var("http_proxy", proxy), } } #[tokio::test] async fn test_no_proxy() { let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), "/4"); async { http::Response::default() } }); let proxy = format!("http://{}", server.addr()); let url = format!("http://{}/4", server.addr()); // set up proxy and use no_proxy to clear up client builder proxies. let res = reqwest::Client::builder() .proxy(reqwest::Proxy::http(&proxy).unwrap()) .no_proxy() .build() .unwrap() .get(&url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), &url); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[cfg_attr(not(feature = "__internal_proxy_sys_no_cache"), ignore)] #[tokio::test] async fn test_using_system_proxy() { let url = "http://not.a.real.sub.hyper.rs/prox"; let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), url); assert_eq!(req.headers()["host"], "not.a.real.sub.hyper.rs"); async { http::Response::default() } }); // Note: we're relying on the `__internal_proxy_sys_no_cache` feature to // check the environment every time. // save system setting first. let system_proxy = env::var("http_proxy"); // set-up http proxy. env::set_var("http_proxy", format!("http://{}", server.addr())); // system proxy is used by default let res = reqwest::get(url).await.unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); // reset user setting. match system_proxy { Err(_) => env::remove_var("http_proxy"), Ok(proxy) => env::set_var("http_proxy", proxy), } } #[tokio::test] async fn http_over_http() { let url = "http://hyper.rs/prox"; let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.uri(), url); assert_eq!(req.headers()["host"], "hyper.rs"); async { http::Response::default() } }); let proxy = format!("http://{}", server.addr()); let res = reqwest::Client::builder() .proxy(reqwest::Proxy::http(&proxy).unwrap()) .build() .unwrap() .get(url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::OK); } reqwest-0.11.27/tests/redirect.rs000064400000000000000000000257071046102023000150120ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] mod support; use futures_util::stream::StreamExt; use hyper::Body; use support::server; #[tokio::test] async fn test_redirect_301_and_302_and_303_changes_post_to_get() { let client = reqwest::Client::new(); let codes = [301u16, 302, 303]; for &code in &codes { let redirect = server::http(move |req| async move { if req.method() == "POST" { assert_eq!(req.uri(), &*format!("/{code}")); http::Response::builder() .status(code) .header("location", "/dst") .header("server", "test-redirect") .body(Body::default()) .unwrap() } else { assert_eq!(req.method(), "GET"); http::Response::builder() .header("server", "test-dst") .body(Body::default()) .unwrap() } }); let url = format!("http://{}/{}", redirect.addr(), code); let dst = format!("http://{}/{}", redirect.addr(), "dst"); let res = client.post(&url).send().await.unwrap(); assert_eq!(res.url().as_str(), dst); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!( res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst" ); } } #[tokio::test] async fn test_redirect_307_and_308_tries_to_get_again() { let client = reqwest::Client::new(); let codes = [307u16, 308]; for &code in &codes { let redirect = server::http(move |req| async move { assert_eq!(req.method(), "GET"); if req.uri() == &*format!("/{code}") { http::Response::builder() .status(code) .header("location", "/dst") .header("server", "test-redirect") .body(Body::default()) .unwrap() } else { assert_eq!(req.uri(), "/dst"); http::Response::builder() .header("server", "test-dst") .body(Body::default()) .unwrap() } }); let url = format!("http://{}/{}", redirect.addr(), code); let dst = format!("http://{}/{}", redirect.addr(), "dst"); let res = client.get(&url).send().await.unwrap(); assert_eq!(res.url().as_str(), dst); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!( res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst" ); } } #[tokio::test] async fn test_redirect_307_and_308_tries_to_post_again() { let _ = env_logger::try_init(); let client = reqwest::Client::new(); let codes = [307u16, 308]; for &code in &codes { let redirect = server::http(move |mut req| async move { assert_eq!(req.method(), "POST"); assert_eq!(req.headers()["content-length"], "5"); let data = req.body_mut().next().await.unwrap().unwrap(); assert_eq!(&*data, b"Hello"); if req.uri() == &*format!("/{code}") { http::Response::builder() .status(code) .header("location", "/dst") .header("server", "test-redirect") .body(Body::default()) .unwrap() } else { assert_eq!(req.uri(), "/dst"); http::Response::builder() .header("server", "test-dst") .body(Body::default()) .unwrap() } }); let url = format!("http://{}/{}", redirect.addr(), code); let dst = format!("http://{}/{}", redirect.addr(), "dst"); let res = client.post(&url).body("Hello").send().await.unwrap(); assert_eq!(res.url().as_str(), dst); assert_eq!(res.status(), reqwest::StatusCode::OK); assert_eq!( res.headers().get(reqwest::header::SERVER).unwrap(), &"test-dst" ); } } #[cfg(feature = "blocking")] #[test] fn test_redirect_307_does_not_try_if_reader_cannot_reset() { let client = reqwest::blocking::Client::new(); let codes = [307u16, 308]; for &code in &codes { let redirect = server::http(move |mut req| async move { assert_eq!(req.method(), "POST"); assert_eq!(req.uri(), &*format!("/{code}")); assert_eq!(req.headers()["transfer-encoding"], "chunked"); let data = req.body_mut().next().await.unwrap().unwrap(); assert_eq!(&*data, b"Hello"); http::Response::builder() .status(code) .header("location", "/dst") .header("server", "test-redirect") .body(Body::default()) .unwrap() }); let url = format!("http://{}/{}", redirect.addr(), code); let res = client .post(&url) .body(reqwest::blocking::Body::new(&b"Hello"[..])) .send() .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), code); } } #[tokio::test] async fn test_redirect_removes_sensitive_headers() { use tokio::sync::watch; let (tx, rx) = watch::channel::>(None); let end_server = server::http(move |req| { let mut rx = rx.clone(); async move { assert_eq!(req.headers().get("cookie"), None); rx.changed().await.unwrap(); let mid_addr = rx.borrow().unwrap(); assert_eq!( req.headers()["referer"], format!("http://{mid_addr}/sensitive") ); http::Response::default() } }); let end_addr = end_server.addr(); let mid_server = server::http(move |req| async move { assert_eq!(req.headers()["cookie"], "foo=bar"); http::Response::builder() .status(302) .header("location", format!("http://{end_addr}/end")) .body(Body::default()) .unwrap() }); tx.send(Some(mid_server.addr())).unwrap(); reqwest::Client::builder() .build() .unwrap() .get(&format!("http://{}/sensitive", mid_server.addr())) .header( reqwest::header::COOKIE, reqwest::header::HeaderValue::from_static("foo=bar"), ) .send() .await .unwrap(); } #[tokio::test] async fn test_redirect_policy_can_return_errors() { let server = server::http(move |req| async move { assert_eq!(req.uri(), "/loop"); http::Response::builder() .status(302) .header("location", "/loop") .body(Body::default()) .unwrap() }); let url = format!("http://{}/loop", server.addr()); let err = reqwest::get(&url).await.unwrap_err(); assert!(err.is_redirect()); } #[tokio::test] async fn test_redirect_policy_can_stop_redirects_without_an_error() { let server = server::http(move |req| async move { assert_eq!(req.uri(), "/no-redirect"); http::Response::builder() .status(302) .header("location", "/dont") .body(Body::default()) .unwrap() }); let url = format!("http://{}/no-redirect", server.addr()); let res = reqwest::Client::builder() .redirect(reqwest::redirect::Policy::none()) .build() .unwrap() .get(&url) .send() .await .unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::FOUND); } #[tokio::test] async fn test_referer_is_not_set_if_disabled() { let server = server::http(move |req| async move { if req.uri() == "/no-refer" { http::Response::builder() .status(302) .header("location", "/dst") .body(Body::default()) .unwrap() } else { assert_eq!(req.uri(), "/dst"); assert_eq!(req.headers().get("referer"), None); http::Response::default() } }); reqwest::Client::builder() .referer(false) .build() .unwrap() .get(&format!("http://{}/no-refer", server.addr())) .send() .await .unwrap(); } #[tokio::test] async fn test_invalid_location_stops_redirect_gh484() { let server = server::http(move |_req| async move { http::Response::builder() .status(302) .header("location", "http://www.yikes{KABOOM}") .body(Body::default()) .unwrap() }); let url = format!("http://{}/yikes", server.addr()); let res = reqwest::get(&url).await.unwrap(); assert_eq!(res.url().as_str(), url); assert_eq!(res.status(), reqwest::StatusCode::FOUND); } #[tokio::test] async fn test_invalid_scheme_is_rejected() { let server = server::http(move |_req| async move { http::Response::builder() .status(302) .header("location", "htt://www.yikes.com/") .body(Body::default()) .unwrap() }); let url = format!("http://{}/yikes", server.addr()); let err = reqwest::get(&url).await.unwrap_err(); assert!(err.is_builder()); } #[cfg(feature = "cookies")] #[tokio::test] async fn test_redirect_302_with_set_cookies() { let code = 302; let server = server::http(move |req| async move { if req.uri() == "/302" { http::Response::builder() .status(302) .header("location", "/dst") .header("set-cookie", "key=value") .body(Body::default()) .unwrap() } else { assert_eq!(req.uri(), "/dst"); assert_eq!(req.headers()["cookie"], "key=value"); http::Response::default() } }); let url = format!("http://{}/{}", server.addr(), code); let dst = format!("http://{}/{}", server.addr(), "dst"); let client = reqwest::ClientBuilder::new() .cookie_store(true) .build() .unwrap(); let res = client.get(&url).send().await.unwrap(); assert_eq!(res.url().as_str(), dst); assert_eq!(res.status(), reqwest::StatusCode::OK); } #[cfg(feature = "__rustls")] #[tokio::test] #[ignore = "Needs TLS support in the test server"] async fn test_redirect_https_only_enforced_gh1312() { let server = server::http(move |_req| async move { http::Response::builder() .status(302) .header("location", "http://insecure") .body(Body::default()) .unwrap() }); let url = format!("https://{}/yikes", server.addr()); let res = reqwest::Client::builder() .danger_accept_invalid_certs(true) .use_rustls_tls() .https_only(true) .build() .expect("client builder") .get(&url) .send() .await; let err = res.unwrap_err(); assert!(err.is_redirect()); } reqwest-0.11.27/tests/support/delay_server.rs000064400000000000000000000106341046102023000174020ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] use std::convert::Infallible; use std::future::Future; use std::net; use std::sync::Arc; use std::time::Duration; use futures_util::FutureExt; use http::{Request, Response}; use hyper::service::service_fn; use hyper::Body; use tokio::net::TcpListener; use tokio::select; use tokio::sync::oneshot; /// This server, unlike [`super::server::Server`], allows for delaying the /// specified amount of time after each TCP connection is established. This is /// useful for testing the behavior of the client when the server is slow. /// /// For example, in case of HTTP/2, once the TCP/TLS connection is established, /// both endpoints are supposed to send a preface and an initial `SETTINGS` /// frame (See [RFC9113 3.4] for details). What if these frames are delayed for /// whatever reason? This server allows for testing such scenarios. /// /// [RFC9113 3.4]: https://www.rfc-editor.org/rfc/rfc9113.html#name-http-2-connection-preface pub struct Server { addr: net::SocketAddr, shutdown_tx: Option>, server_terminated_rx: oneshot::Receiver<()>, } impl Server { pub async fn new(func: F1, apply_config: F2, delay: Duration) -> Self where F1: Fn(Request) -> Fut + Clone + Send + 'static, Fut: Future> + Send + 'static, F2: FnOnce(hyper::server::conn::Http) -> hyper::server::conn::Http + Send + 'static, { let (shutdown_tx, shutdown_rx) = oneshot::channel(); let (server_terminated_tx, server_terminated_rx) = oneshot::channel(); let tcp_listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = tcp_listener.local_addr().unwrap(); tokio::spawn(async move { let http = Arc::new(apply_config(hyper::server::conn::Http::new())); tokio::spawn(async move { let (connection_shutdown_tx, connection_shutdown_rx) = oneshot::channel(); let connection_shutdown_rx = connection_shutdown_rx.shared(); let mut shutdown_rx = std::pin::pin!(shutdown_rx); let mut handles = Vec::new(); loop { select! { _ = shutdown_rx.as_mut() => { connection_shutdown_tx.send(()).unwrap(); break; } res = tcp_listener.accept() => { let (stream, _) = res.unwrap(); let handle = tokio::spawn({ let connection_shutdown_rx = connection_shutdown_rx.clone(); let http = http.clone(); let func = func.clone(); async move { tokio::time::sleep(delay).await; let mut conn = std::pin::pin!(http.serve_connection( stream, service_fn(move |req| { let fut = func(req); async move { Ok::<_, Infallible>(fut.await) }}) )); select! { _ = conn.as_mut() => {} _ = connection_shutdown_rx => { conn.as_mut().graceful_shutdown(); conn.await.unwrap(); } } } }); handles.push(handle); } } } futures_util::future::join_all(handles).await; server_terminated_tx.send(()).unwrap(); }); }); Self { addr, shutdown_tx: Some(shutdown_tx), server_terminated_rx, } } pub async fn shutdown(mut self) { if let Some(tx) = self.shutdown_tx.take() { let _ = tx.send(()); } self.server_terminated_rx.await.unwrap(); } pub fn addr(&self) -> net::SocketAddr { self.addr } } reqwest-0.11.27/tests/support/mod.rs000064400000000000000000000003371046102023000154740ustar 00000000000000pub mod delay_server; pub mod server; // TODO: remove once done converting to new support server? #[allow(unused)] pub static DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); reqwest-0.11.27/tests/support/server.rs000064400000000000000000000056431046102023000162300ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] use std::convert::{identity, Infallible}; use std::future::Future; use std::net; use std::sync::mpsc as std_mpsc; use std::thread; use std::time::Duration; use hyper::server::conn::AddrIncoming; use tokio::runtime; use tokio::sync::oneshot; pub struct Server { addr: net::SocketAddr, panic_rx: std_mpsc::Receiver<()>, shutdown_tx: Option>, } impl Server { pub fn addr(&self) -> net::SocketAddr { self.addr } } impl Drop for Server { fn drop(&mut self) { if let Some(tx) = self.shutdown_tx.take() { let _ = tx.send(()); } if !::std::thread::panicking() { self.panic_rx .recv_timeout(Duration::from_secs(3)) .expect("test server should not panic"); } } } pub fn http(func: F) -> Server where F: Fn(http::Request) -> Fut + Clone + Send + 'static, Fut: Future> + Send + 'static, { http_with_config(func, identity) } pub fn http_with_config(func: F1, apply_config: F2) -> Server where F1: Fn(http::Request) -> Fut + Clone + Send + 'static, Fut: Future> + Send + 'static, F2: FnOnce(hyper::server::Builder) -> hyper::server::Builder + Send + 'static, { // Spawn new runtime in thread to prevent reactor execution context conflict thread::spawn(move || { let rt = runtime::Builder::new_current_thread() .enable_all() .build() .expect("new rt"); let srv = rt.block_on(async move { let builder = hyper::Server::bind(&([127, 0, 0, 1], 0).into()); apply_config(builder).serve(hyper::service::make_service_fn(move |_| { let func = func.clone(); async move { Ok::<_, Infallible>(hyper::service::service_fn(move |req| { let fut = func(req); async move { Ok::<_, Infallible>(fut.await) } })) } })) }); let addr = srv.local_addr(); let (shutdown_tx, shutdown_rx) = oneshot::channel(); let srv = srv.with_graceful_shutdown(async move { let _ = shutdown_rx.await; }); let (panic_tx, panic_rx) = std_mpsc::channel(); let tname = format!( "test({})-support-server", thread::current().name().unwrap_or("") ); thread::Builder::new() .name(tname) .spawn(move || { rt.block_on(srv).unwrap(); let _ = panic_tx.send(()); }) .expect("thread spawn"); Server { addr, panic_rx, shutdown_tx: Some(shutdown_tx), } }) .join() .unwrap() } reqwest-0.11.27/tests/timeouts.rs000064400000000000000000000175211046102023000150550ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] mod support; use support::server; use std::time::Duration; #[tokio::test] async fn client_timeout() { let _ = env_logger::try_init(); let server = server::http(move |_req| { async { // delay returning the response tokio::time::sleep(Duration::from_secs(2)).await; http::Response::default() } }); let client = reqwest::Client::builder() .timeout(Duration::from_millis(500)) .build() .unwrap(); let url = format!("http://{}/slow", server.addr()); let res = client.get(&url).send().await; let err = res.unwrap_err(); assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } #[tokio::test] async fn request_timeout() { let _ = env_logger::try_init(); let server = server::http(move |_req| { async { // delay returning the response tokio::time::sleep(Duration::from_secs(2)).await; http::Response::default() } }); let client = reqwest::Client::builder().build().unwrap(); let url = format!("http://{}/slow", server.addr()); let res = client .get(&url) .timeout(Duration::from_millis(500)) .send() .await; let err = res.unwrap_err(); if cfg!(not(target_arch = "wasm32")) { assert!(err.is_timeout() && !err.is_connect()); } else { assert!(err.is_timeout()); } assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } #[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn connect_timeout() { let _ = env_logger::try_init(); let client = reqwest::Client::builder() .connect_timeout(Duration::from_millis(100)) .build() .unwrap(); let url = "http://10.255.255.1:81/slow"; let res = client .get(url) .timeout(Duration::from_millis(1000)) .send() .await; let err = res.unwrap_err(); assert!(err.is_connect() && err.is_timeout()); } #[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn connect_many_timeout_succeeds() { let _ = env_logger::try_init(); let server = server::http(move |_req| async { http::Response::default() }); let port = server.addr().port(); let client = reqwest::Client::builder() .resolve_to_addrs( "many_addrs", &["10.255.255.1:81".parse().unwrap(), server.addr()], ) .connect_timeout(Duration::from_millis(100)) .build() .unwrap(); let url = format!("http://many_addrs:{port}/eventual"); let _res = client .get(url) .timeout(Duration::from_millis(1000)) .send() .await .unwrap(); } #[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn connect_many_timeout() { let _ = env_logger::try_init(); let client = reqwest::Client::builder() .resolve_to_addrs( "many_addrs", &[ "10.255.255.1:81".parse().unwrap(), "10.255.255.2:81".parse().unwrap(), ], ) .connect_timeout(Duration::from_millis(100)) .build() .unwrap(); let url = "http://many_addrs:81/slow".to_string(); let res = client .get(url) .timeout(Duration::from_millis(1000)) .send() .await; let err = res.unwrap_err(); assert!(err.is_connect() && err.is_timeout()); } #[tokio::test] async fn response_timeout() { let _ = env_logger::try_init(); let server = server::http(move |_req| { async { // immediate response, but delayed body let body = hyper::Body::wrap_stream(futures_util::stream::once(async { tokio::time::sleep(Duration::from_secs(2)).await; Ok::<_, std::convert::Infallible>("Hello") })); http::Response::new(body) } }); let client = reqwest::Client::builder() .timeout(Duration::from_millis(500)) .no_proxy() .build() .unwrap(); let url = format!("http://{}/slow", server.addr()); let res = client.get(&url).send().await.expect("Failed to get"); let body = res.text().await; let err = body.unwrap_err(); assert!(err.is_timeout()); } /// Tests that internal client future cancels when the oneshot channel /// is canceled. #[cfg(feature = "blocking")] #[test] fn timeout_closes_connection() { let _ = env_logger::try_init(); // Make Client drop *after* the Server, so the background doesn't // close too early. let client = reqwest::blocking::Client::builder() .timeout(Duration::from_millis(500)) .build() .unwrap(); let server = server::http(move |_req| { async { // delay returning the response tokio::time::sleep(Duration::from_secs(2)).await; http::Response::default() } }); let url = format!("http://{}/closes", server.addr()); let err = client.get(&url).send().unwrap_err(); assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } #[cfg(feature = "blocking")] #[test] fn timeout_blocking_request() { let _ = env_logger::try_init(); // Make Client drop *after* the Server, so the background doesn't // close too early. let client = reqwest::blocking::Client::builder().build().unwrap(); let server = server::http(move |_req| { async { // delay returning the response tokio::time::sleep(Duration::from_secs(2)).await; http::Response::default() } }); let url = format!("http://{}/closes", server.addr()); let err = client .get(&url) .timeout(Duration::from_millis(500)) .send() .unwrap_err(); assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } #[cfg(feature = "blocking")] #[test] fn blocking_request_timeout_body() { let _ = env_logger::try_init(); let client = reqwest::blocking::Client::builder() // this should be overridden .connect_timeout(Duration::from_millis(200)) // this should be overridden .timeout(Duration::from_millis(200)) .build() .unwrap(); let server = server::http(move |_req| { async { // immediate response, but delayed body let body = hyper::Body::wrap_stream(futures_util::stream::once(async { tokio::time::sleep(Duration::from_secs(1)).await; Ok::<_, std::convert::Infallible>("Hello") })); http::Response::new(body) } }); let url = format!("http://{}/closes", server.addr()); let res = client .get(&url) // longer than client timeout .timeout(Duration::from_secs(5)) .send() .expect("get response"); let text = res.text().unwrap(); assert_eq!(text, "Hello"); } #[cfg(feature = "blocking")] #[test] fn write_timeout_large_body() { let _ = env_logger::try_init(); let body = vec![b'x'; 20_000]; let len = 8192; // Make Client drop *after* the Server, so the background doesn't // close too early. let client = reqwest::blocking::Client::builder() .timeout(Duration::from_millis(500)) .build() .unwrap(); let server = server::http(move |_req| { async { // delay returning the response tokio::time::sleep(Duration::from_secs(2)).await; http::Response::default() } }); let cursor = std::io::Cursor::new(body); let url = format!("http://{}/write-timeout", server.addr()); let err = client .post(&url) .body(reqwest::blocking::Body::sized(cursor, len as u64)) .send() .unwrap_err(); assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } reqwest-0.11.27/tests/upgrade.rs000064400000000000000000000030361046102023000146270ustar 00000000000000#![cfg(not(target_arch = "wasm32"))] mod support; use support::server; use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[tokio::test] async fn http_upgrade() { let server = server::http(move |req| { assert_eq!(req.method(), "GET"); assert_eq!(req.headers()["connection"], "upgrade"); assert_eq!(req.headers()["upgrade"], "foobar"); tokio::spawn(async move { let mut upgraded = hyper::upgrade::on(req).await.unwrap(); let mut buf = vec![0; 7]; upgraded.read_exact(&mut buf).await.unwrap(); assert_eq!(buf, b"foo=bar"); upgraded.write_all(b"bar=foo").await.unwrap(); }); async { http::Response::builder() .status(http::StatusCode::SWITCHING_PROTOCOLS) .header(http::header::CONNECTION, "upgrade") .header(http::header::UPGRADE, "foobar") .body(hyper::Body::empty()) .unwrap() } }); let res = reqwest::Client::builder() .build() .unwrap() .get(format!("http://{}", server.addr())) .header(http::header::CONNECTION, "upgrade") .header(http::header::UPGRADE, "foobar") .send() .await .unwrap(); assert_eq!(res.status(), http::StatusCode::SWITCHING_PROTOCOLS); let mut upgraded = res.upgrade().await.unwrap(); upgraded.write_all(b"foo=bar").await.unwrap(); let mut buf = vec![]; upgraded.read_to_end(&mut buf).await.unwrap(); assert_eq!(buf, b"bar=foo"); } reqwest-0.11.27/tests/wasm_simple.rs000064400000000000000000000012041046102023000155130ustar 00000000000000#![cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen] extern "C" { // Use `js_namespace` here to bind `console.log(..)` instead of just // `log(..)` #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } #[wasm_bindgen_test] async fn simple_example() { let res = reqwest::get("https://hyper.rs") .await .expect("http get example"); log(&format!("Status: {}", res.status())); let body = res.text().await.expect("response to utf-8 text"); log(&format!("Body:\n\n{body}")); }