minreq-2.13.4/.cargo_vcs_info.json0000644000000001360000000000100124160ustar { "git": { "sha1": "c641f93d25b1654daeab7f5cea635e7633ab7a71" }, "path_in_vcs": "" }minreq-2.13.4/.github/workflows/lint.yml000064400000000000000000000022011046102023000162670ustar 00000000000000name: lint on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout Crate uses: actions/checkout@v3 - name: Set Toolchain # https://github.com/dtolnay/rust-toolchain uses: dtolnay/rust-toolchain@stable - name: Run rustfmt run: rustfmt --check src/lib.rs - name: Run cargo doc run: cargo doc --features "punycode proxy json-using-serde https" - name: Run clippy run: | cargo clippy --all-targets --features "punycode proxy json-using-serde https-rustls" -- --no-deps -D warnings cargo clippy --all-targets --features "punycode proxy json-using-serde https-rustls-probe" -- --no-deps -D warnings cargo clippy --all-targets --features "punycode proxy json-using-serde https-bundled" -- --no-deps -D warnings cargo clippy --all-targets --features "punycode proxy json-using-serde https-bundled-probe" -- --no-deps -D warnings cargo clippy --all-targets --features "punycode proxy json-using-serde https-native" -- --no-deps -D warnings minreq-2.13.4/.github/workflows/msrv.yml000064400000000000000000000017711046102023000163230ustar 00000000000000name: msrv on: push: branches: [ master ] pull_request: branches: [ master ] schedule: - cron: "47 5 * * 6" env: CARGO_TERM_COLOR: always jobs: test: runs-on: ubuntu-latest steps: - name: Checkout Crate uses: actions/checkout@v3 - name: Checkout Toolchain uses: dtolnay/rust-toolchain@1.48 - name: Running test script run: | cargo update --package log --precise=0.4.18 cargo update --package httpdate --precise=1.0.2 cargo update --package serde_json --precise=1.0.100 cargo update --package chrono --precise=0.4.23 cargo update --package num-traits --precise=0.2.18 cargo update --package tempfile --precise=3.17.1 cargo update --package libc --precise=0.2.163 cargo update --package iana-time-zone --precise=0.1.61 cargo update --package httpdate --precise=1.0.2 cargo test cargo test --features "json-using-serde proxy punycode" minreq-2.13.4/.github/workflows/unit-tests.yml000064400000000000000000000042521046102023000174500ustar 00000000000000name: unit-tests on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: test-linux: runs-on: ubuntu-latest steps: - name: Checkout Crate uses: actions/checkout@v3 - name: Set Toolchain # https://github.com/dtolnay/rust-toolchain uses: dtolnay/rust-toolchain@stable - name: Build run: cargo build - name: Test run: | cargo test cargo test --features punycode cargo test --features proxy cargo test --features json-using-serde cargo test --features urlencoding cargo test --features https cargo test --features "punycode proxy json-using-serde urlencoding https-rustls" cargo test --features "punycode proxy json-using-serde urlencoding https-rustls-probe" cargo test --features "punycode proxy json-using-serde urlencoding https-bundled" cargo test --features "punycode proxy json-using-serde urlencoding https-bundled-probe" cargo test --features "punycode proxy json-using-serde urlencoding https-native" test-windows: runs-on: windows-latest steps: - name: Checkout Crate uses: actions/checkout@v3 - name: Set Toolchain uses: dtolnay/rust-toolchain@stable - name: Build run: cargo build - name: Test run: | cargo test cargo test --features punycode cargo test --features proxy cargo test --features json-using-serde cargo test --features urlencoding cargo test --features https cargo test --features "punycode proxy json-using-serde urlencoding https" test-macos: runs-on: macos-latest steps: - name: Checkout Crate uses: actions/checkout@v3 - name: Set Toolchain uses: dtolnay/rust-toolchain@stable - name: Build run: cargo build - name: Test run: | cargo test cargo test --features punycode cargo test --features proxy cargo test --features json-using-serde cargo test --features urlencoding cargo test --features https cargo test --features "punycode proxy json-using-serde urlencoding https" minreq-2.13.4/.gitignore000064400000000000000000000000401046102023000131700ustar 00000000000000 /target/ **/*.rs.bk Cargo.lock minreq-2.13.4/CHANGELOG.md000064400000000000000000000331171046102023000130240ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [2.13.4] - 2025-04-11 ### Fixed - Updated the base64 dependency, only used by the `proxy` feature, to its newest minor version. Thanks for the report, @Jackhr-arch! ([#119](https://github.com/neonmoe/minreq/issues/119)) ## [2.13.3] - 2025-03-11 ### Fixed - Removed the `once_cell` dependency by making use of the new `std::sync::OnceLock` type. This change only affects the rustls-based https features. Thanks for the PR, @LyonSyonII! ([#115](https://github.com/neonmoe/minreq/pull/115)) - MSRV builds that got broken due to a `rustix` update. Now `tempfile` is pinned as well. ## [2.13.2] - 2025-01-29 ### Fixed - Reverted a part of 2.13.1, accidentally removed some code that wasn't actually dead code. ## [2.13.1] - 2025-01-29 ### Fixed - Usage of an openssl-probe function that's deprecated due to safety issues. See [rustsec/advisory-db#2209](https://github.com/rustsec/advisory-db/pull/2209) for further info. ## [2.13.0] - 2024-12-04 ### Changed - The `https-rustls-probe` feature no longer brings in the `webpki-roots` and `rustls-webpki` crates. Thanks for the report, @polarathene! ([#111](https://github.com/neonmoe/minreq/issues/111)) ### Fixed - Cleaned up an unnecessary `format!()` in `Connection::connect`. Thanks for the PR, @melotic! ([#112](https://github.com/neonmoe/minreq/pull/112)) - Fixed some msrv and lint issues introduced by libc and clippy updates respectively. ## [2.12.0] - 2024-07-16 ### Added - Request::with_headers, to allow passing in many headers at a time. Thanks for the idea and PR, @rawhuul! ([#110](https://github.com/neonmoe/minreq/pull/110)) ## [2.11.2] - 2024-04-26 ### Fixed - The dev dependency tiny_http's version up to 0.12. Thanks for the PR, @davide125! ([#107](https://github.com/neonmoe/minreq/pull/107)) ## [2.11.1] - 2024-02-04 ### Fixed - Unnecessary buffering causing performance problems. Thanks for the PRs, @mrkline! ([#102](https://github.com/neonmoe/minreq/pull/102), [#103](https://github.com/neonmoe/minreq/pull/103)) - Connections failing if the first resolved address fails to connect (even if there's more to try). Thanks for the PR, @darosior! ([#106](https://github.com/neonmoe/minreq/pull/106)) ## [2.11.0] - 2023-10-17 ### Changed - Removed upper bounds on the `serde_json`, `log` and `chrono` dependencies (dev-dependency in the case of `chrono`). If you were depending on minreq compiling with the MSRV compiler without any issues, check out the MSRV section in the readme, it's been updated with additional instructions. Thanks for the report, @RCasatta! ([#99](https://github.com/neonmoe/minreq/issues/99)) ## [2.10.0] - 2023-09-05 ### Fixed - Fragment handling, once again. Turns out you're not supposed to include fragments in the request. This may break usage with servers that are written with the wrong assumptions. Thanks for the report, @rawhuul! ([#100](https://github.com/neonmoe/minreq/issues/100)) ### Added - `Response::url` and `ResponseLazy::url` fields, to contain the final URL after redirects and fragment replacement semantics. ## [2.9.1] - 2023-08-28 ### Changed - Loosened the rustls version requirement from 0.21.6 to 0.21.1. ## [2.9.0] - 2023-08-24 ### Changed - From webpki to rustls-webpki. Thanks for the heads-up about webpki not being maintained, @RCasatta! ([#98](https://github.com/neonmoe/minreq/issues/98)) - Updated rustls and webpki-roots to their most recent versions. - Maximum versions for the following dependencies to keep minreq compiling on Rust 1.48: - serde_json (`>=1.0.0, <1.0.101`) - log (`>=0.4.0, <0.4.19`) - chrono (dev-dependency, `>=0.4.0, <0.4.24`) ### Fixed - Errors when using an IP address as the host with HTTPS (tested with ). ([#34](https://github.com/neonmoe/minreq/issues/34)) ## [2.8.1] - 2023-05-20 ### Fixed - Proxy strings with the protocol included not working. Thanks for the report, @tkkcc! ([#95](https://github.com/neonmoe/minreq/issues/95)) ## [2.8.0] - 2023-05-13 ### Added - Default proxy from environment variables when the `proxy` feature is enabled, based on what curl does. Thanks for the PR, @krypt0nn! ([#94](https://github.com/neonmoe/minreq/pull/94)) ## [2.7.0] - 2023-03-19 ### Changed - From lazy_static to once_cell for library internals. Thanks for the PR, @alpha-tango-kilo! ([#80](https://github.com/neonmoe/minreq/pull/80)) ### Added - A Read impl for ResponseLazy. Thanks for the PR, @Luro02! ([#81](https://github.com/neonmoe/minreq/pull/81)) - Building with `--all-features`, with the `send_https` function defaulting to the rustls-based implementation. Thanks for the PR, @tcharding! ([#89](https://github.com/neonmoe/minreq/pull/89)) - An explicit minimum supported rust version policy. The MSRV for versions 2.x is 1.48. Thanks for the suggestion and PR, @tcharding! ([#90](https://github.com/neonmoe/minreq/pull/90)) - Performance improvements, test fixes, CI updates. ## [2.6.0] - 2022-02-23 ### Changed - The error returned when the request url does not start with `https://` or `http://` now is now a slightly different IoError, with a clearer message. This will be changed to a proper minreq-specific error in 3.0, but for now it's an IoError to avoid breaking the Error type. ### Added - The `urlencoding` feature for automatically percent-encoding urls. Thanks for the idea and PR, @alpha-tango-kilo! ([#67](https://github.com/neonmoe/minreq/issues/67), [#68](https://github.com/neonmoe/minreq/pull/68)) ## [2.5.1] - 2022-01-07 ### Fixed - GitHub API requests without User-Agent returning an IoError. Thanks for the report, @tech-ticks! ([#66](https://github.com/neonmoe/minreq/issues/66)) ## [2.5.0] - 2022-01-06 ### Fixed - Returning the wrong status code when the response was missing a status phrase. Thanks for the PR, @richarddd! ([#64](https://github.com/neonmoe/minreq/issues/64)) - Non-lazy requests crashing if the request had a very big Content-Length header. Thanks for the report, @Shnatsel! ([#63](https://github.com/neonmoe/minreq/issues/63)) ## [2.4.2] - 2021-06-11 ### Fixed - A regression in 2.4.1 where the port is no longer included in the `Host`, even if it's a non-standard port. Now the port is always included if it's in the request URL, and omitted if the port is implied. Thanks for the report, @ollpu! ([#61](https://github.com/neonmoe/minreq/issues/61)) ## [2.4.1] - 2021-06-05 ### Fixed - The port is no longer included in the `Host` header when sending requests, and port handling was cleaned up overall. This fixes issues with infinite redirections and https handshakes for some websites. Thanks to @Shnatsel for reporting the issues, and @joeried for debugging and figuring out the root cause of these problems! ([#48](https://github.com/neonmoe/minreq/issues/48), [#49](https://github.com/neonmoe/minreq/issues/49)) ## [2.4.0] - 2021-05-27 ### Added - `Request::with_param` for more ergonomic query parameter usage. Thanks for the PR, @sjvignesh! ([#54](https://github.com/neonmoe/minreq/pull/54)) - `Request::with_max_headers_size` and `Request::with_max_status_line_length` for avoiding DoS when the server sends large headers or status lines. Thanks for the report, @Shnatsel! ([#55](https://github.com/neonmoe/minreq/issues/55)) - Support for the `rustls-native-certs` crate via a new `https-rustls-probe` feature. Thanks for the PR, @joeried! ([#59](https://github.com/neonmoe/minreq/pull/59)) ### Fixed - Chunk length handling for some servers with slightly off-spec chunk lengths. Thanks for the report, @Shnatsel! ([#50](https://github.com/neonmoe/minreq/issues/50)) - Timeouts not always being properly enforced. Thanks for the report, @Shnatsel! ([#52](https://github.com/neonmoe/minreq/issues/52)) ## [2.3.1] - 2021-02-10 ### Fixed - Removed some leftover printlns from the redirection update in 2.3.0 and ensured there's no printlns in the library anymore. Thanks for reporting the issue @Shnatsel! [#45](https://github.com/neonmoe/minreq/issues/45) - Fixed the timeout not being respected during the initial TCP connect. Thanks for the report and fix @KarthikNedunchezhiyan! [#46](https://github.com/neonmoe/minreq/issues/46), [#47](https://github.com/neonmoe/minreq/pull/47) ## [2.3.0] - 2021-01-04 ### Changed - **Breaking (sort of):** the redirection code was improved to match [RFC 7231 section 7.1.2](https://tools.ietf.org/html/rfc7231#section-7.1.2), which could subtly break some programs relying on very specific redirects, which is why this should be investigated if you come across weird behaviour after updating. No API changes though, so only a minor version bump. The following two points are now fixed when redirecting: - Fragments, the bit after a #-character in the url. If the redirecting url has a fragment, and the one in `Location` does not, the original fragment should be included in the new url. If `Location` does have a fragment, it should override the one in the redirecting url. - Relative urls. Minreq now properly redirects when `Location` is relative, e.g. `/Foo.html` instead of `https://example.com/Foo.html`. Thanks, @fjt523! ### Fixed - The `Content-Length: 0` header is now inserted into requests that should have it. Thanks, @KarthikNedunchezhiyan! - Status line parsing is now fixed, so "400 Bad Request" is not parsed as "400 Bad". Thanks, @KarthikNedunchezhiyan! ### Added - M1 Mac support by bumping the ring dependency. Thanks, @ryanmcgrath! ## [2.2.1] - 2020-08-22 ### Fixed - Some documentation which has been long due for an update. I just always forget when writing an actual update. No code changes! ## [2.2.0] - 2020-06-18 ### Added - Support for `native-tls` and `openssl-sys` via new features, in addition to `rustls`. Thanks to @dubiousjim! ## [2.1.1] - 2020-05-01 ### Fixed - Handling of status codes 204 and 304. Thanks to @Mubelotix! ## [2.1.0] - 2020-03-14 ### Added - Proxy support via the `proxy` feature. Thanks to @rustysec! ## [2.0.3] - 2020-01-15 ### Fixed - Fixed regression in header parsing caused by 2.0.2, which was yanked. ## [2.0.2] - 2020-01-15 ### Fixed - Fixed a panic when sending a request to an invalid domain via https. - Fixed a panic when parsing headers that have >1 byte unicode characters right after the ":" in the response. ## [2.0.1] - 2020-01-11 ### Fixed - Made timeouts work as described in the documentation. Fixed issue #22. ## [2.0.0] - 2019-11-23 ### Added - API for loading the HTTP response body through an iterator, allowing for processing of the data during the download. - See the `ResponseLazy` documentation for more information. - Error type for all the errors that this crate can run into for easier `?` usage and better debuggability. - Punycode support for non-ascii hostnames via the `punycode` feature. - Trailer header support. - Examples [`hello`](examples/hello.rs), [`iterator`](examples/iterator.rs), and [`json`](examples/json.rs). ### Changed - **Breaking, will cause problems not detectable by the compiler:** Response headers' field names are now in lowercase, as they are case-insensitive and this makes getting header values easier. The values are unaffected. So if your code has `response.headers.get("Content-Type")`, you have to change it to `response.headers.get("content-type")`, or it will not return what you want. - **Breaking**: Restructure the `Response` struct: - Removed `bytes` and `body_bytes`. - Added `as_bytes()`, `into_bytes()`, and `as_str()` in their place. - **Breaking**: Changed the `with_body` parameter type to `Into>` from `Into`. - `String`s implement `Into>`, so this shouldn't cause any problems, unless you're using some interesting types that implement `Into` but not `Into>`. - Clean up the crate internals overall. **Note**: This might cause instability, if you're very concerned about stability, please hold off upgrading for a while. - Remove `panic!` when trying to make an `https://` request without the `https` feature. The request will now return an error instead. The library should not panic anymore. - Audit the remaining `unwrap()`s from library code, none of them should actually ever cause a panic now. ### Removed - `create_request` in favor of just using `Response::new`. ## [1.4.1] - 2019-10-13 ### Changed - Updated dependencies. ### Fixed - Tests on Windows by changing the ip in tests from `0.0.0.0` to `localhost`. - Reuse `rustls::ClientConfig` between requests. - `Content-Length` and `Transfer-Encoding` detection failing because of case-sensitiveness. ## [1.4.0] - 2019-07-13 ### Added - `json-using-serde` feature. ## [1.3.0] - 2019-06-04 ### Added - The `body_bytes` field to Response, containing the body in raw bytes. ### Fixed - Some clippy warnings. - Panic when getting a non-UTF-8 response, instead setting the `body` string to an empty string, for now. ## [1.2.1] - 2019-05-24 ### Fixed - HTTP response body handling. ## [1.2.0] - 2019-05-23 ### Added - Support for the HTTP status codes 301, 302, 303, and 307. ### Fixed - Less .clones()s. ## [1.1.2] - 2019-04-14 ### Fixed - Fix response handling when `Transfer-Encoding` is `chunked`. ## [1.1.1] - 2019-03-28 ### Changed - Moved to 2018 edition. ### Fixed - HEAD requests and ones that receive a 1xx, 204, or 304 status code as a response. ## [1.1.0] - 2019-03-24 ### Changed - Timeout made optional. - Updated dependencies. ### Fixed - Improved performance for HTTP (not HTTPS) requests. minreq-2.13.4/COPYING.md000064400000000000000000000013511046102023000126400ustar 00000000000000ISC License Copyright (c) 2018, Jens Pitkanen Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. minreq-2.13.4/Cargo.lock0000644000000521570000000000100104030ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "ascii" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cc" version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "chunked_transfer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" [[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.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "errno" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[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 = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minreq" version = "2.13.4" dependencies = [ "base64 0.22.1", "chrono", "log", "native-tls", "openssl", "openssl-probe", "punycode", "rustls", "rustls-native-certs", "rustls-webpki", "serde", "serde_json", "tiny_http", "urlencoding", "webpki-roots", ] [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags", "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.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" version = "300.5.0+3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" dependencies = [ "cc", ] [[package]] name = "openssl-sys" version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", "openssl-src", "pkg-config", "vcpkg", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "punycode" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustix" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustls" version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", "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 0.21.7", ] [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "sct" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", ] [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tiny_http" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" dependencies = [ "ascii", "chunked_transfer", "httpdate", "log", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] minreq-2.13.4/Cargo.toml0000644000000056100000000000100104160ustar # 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 = "2018" name = "minreq" version = "2.13.4" authors = ["Jens Pitkanen "] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Simple, minimal-dependency HTTP client" homepage = "https://github.com/neonmoe/minreq" documentation = "https://docs.rs/minreq" readme = "README.md" keywords = [ "http", "https", "client", "request", "json", ] categories = ["web-programming::http-client"] license = "ISC" repository = "https://github.com/neonmoe/minreq" [package.metadata.docs.rs] features = [ "json-using-serde", "proxy", "https", "punycode", ] [badges.maintenance] status = "passively-maintained" [badges.travis-ci] repository = "neonmoe/minreq" [features] https = ["https-rustls"] https-bundled = ["openssl/vendored"] https-bundled-probe = [ "https-bundled", "openssl-probe", ] https-native = ["native-tls"] https-rustls = [ "rustls", "webpki-roots", "rustls-webpki", ] https-rustls-probe = [ "rustls", "rustls-native-certs", ] json-using-serde = [ "serde", "serde_json", ] proxy = ["base64"] [lib] name = "minreq" path = "src/lib.rs" [[example]] name = "hello" path = "examples/hello.rs" [[example]] name = "iterator" path = "examples/iterator.rs" [[example]] name = "json" path = "examples/json.rs" required-features = ["json-using-serde"] [[test]] name = "main" path = "tests/main.rs" [[test]] name = "setup" path = "tests/setup.rs" [dependencies.base64] version = "0.22" optional = true [dependencies.log] version = "0.4.0" [dependencies.native-tls] version = "0.2" optional = true [dependencies.openssl] version = "0.10.29" optional = true [dependencies.openssl-probe] version = "0.1" optional = true [dependencies.punycode] version = "0.4.1" optional = true [dependencies.rustls] version = "0.21.1" optional = true [dependencies.rustls-native-certs] version = "0.6.1" optional = true [dependencies.rustls-webpki] version = "0.101.0" optional = true [dependencies.serde] version = "1.0.101" optional = true [dependencies.serde_json] version = "1.0.0" optional = true [dependencies.urlencoding] version = "2.1.0" optional = true [dependencies.webpki-roots] version = "0.25.2" optional = true [dev-dependencies.chrono] version = "0.4.0" [dev-dependencies.tiny_http] version = "0.12" [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(have_min_max_version)"] minreq-2.13.4/Cargo.toml.orig000064400000000000000000000040421046102023000140750ustar 00000000000000[package] name = "minreq" version = "2.13.4" authors = ["Jens Pitkanen "] description = "Simple, minimal-dependency HTTP client" documentation = "https://docs.rs/minreq" homepage = "https://github.com/neonmoe/minreq" repository = "https://github.com/neonmoe/minreq" readme = "README.md" keywords = ["http", "https", "client", "request", "json"] categories = ["web-programming::http-client"] license = "ISC" edition = "2018" [badges] travis-ci = { repository = "neonmoe/minreq" } maintenance = { status = "passively-maintained" } [dependencies] # For the urlencoding feature: urlencoding = { version = "2.1.0", optional = true } # For the punycode feature: punycode = { version = "0.4.1", optional = true } # For the json-using-serde feature: serde = { version = "1.0.101", optional = true } serde_json = { version = "1.0.0", optional = true } # For the proxy feature: base64 = { version = "0.22", optional = true } # For the https features: rustls = { version = "0.21.1", optional = true } rustls-native-certs = { version = "0.6.1", optional = true } webpki-roots = { version = "0.25.2", optional = true } rustls-webpki = { version = "0.101.0", optional = true } openssl = { version = "0.10.29", optional = true } log = { version = "0.4.0" } openssl-probe = { version = "0.1", optional = true } native-tls = { version = "0.2", optional = true } [dev-dependencies] tiny_http = "0.12" chrono = "0.4.0" [package.metadata.docs.rs] features = ["json-using-serde", "proxy", "https", "punycode"] [features] https = ["https-rustls"] https-rustls = ["rustls", "webpki-roots", "rustls-webpki"] https-rustls-probe = ["rustls", "rustls-native-certs"] https-bundled = ["openssl/vendored"] https-bundled-probe = ["https-bundled", "openssl-probe"] https-native = ["native-tls"] json-using-serde = ["serde", "serde_json"] proxy = ["base64"] [[example]] name = "hello" [[example]] name = "iterator" [[example]] name = "json" required-features = ["json-using-serde"] [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(have_min_max_version)'] } # vim: ft=conf minreq-2.13.4/README.md000064400000000000000000000066771046102023000125050ustar 00000000000000# minreq [![Crates.io](https://img.shields.io/crates/d/minreq.svg)](https://crates.io/crates/minreq) [![Documentation](https://docs.rs/minreq/badge.svg)](https://docs.rs/minreq) ![Unit tests](https://github.com/neonmoe/minreq/actions/workflows/unit-tests.yml/badge.svg) ![MSRV](https://github.com/neonmoe/minreq/actions/workflows/msrv.yml/badge.svg) Simple, minimal-dependency HTTP client. Optional features for json responses (`json-using-serde`), unicode domains (`punycode`), http proxies (`proxy`), and https with various TLS implementations (`https-rustls`, `https-rustls-probe`, `https-bundled`, `https-bundled-probe`,`https-native`, and `https` which is an alias for `https-rustls`). Without any optional features, my casual testing indicates about 100 KB additional executable size for stripped release builds using this crate. Compiled with rustc 1.45.2, `println!("Hello, World!");` is 239 KB on my machine, where the [hello](examples/hello.rs) example is 347 KB. Both are pure Rust, so aside from `libc`, everything is statically linked. Note: some of the dependencies of this crate (especially `serde` and the various `https` libraries) are a lot more complicated than this library, and their impact on executable size reflects that. ## [Documentation](https://docs.rs/minreq) ## Planned for 3.0.0 This is a list of features I'll implement once it gets long enough, or a severe enough issue is found that there's good reason to make a major version bump. - Change the response/request structs to allow multiple headers with the same name. - Set sane defaults for maximum header size and status line length. The ability to add maximums was added in response to [#55](https://github.com/neonmoe/minreq/issues/55), but defaults for the limits is a breaking change. - Clearer error when making a request to an url that does not start with `http://` or `https://`. - Non-exhaustive error type? - Change default proxy port to 1080 (from 8080). Curl uses 1080, so it's a sane default. - Bump MSRV enough to compile the latest versions of all dependencies, and add the `rust-version` (at least 1.56) and `edition` (at least 2021) fields to Cargo.toml. ## Minimum Supported Rust Version (MSRV) If you don't care about the MSRV, you can ignore this section entirely, including the commands instructed. We use an MSRV per major release, i.e., with a new major release we reserve the right to change the MSRV. The current major version (v2) of this library should always compile with any combination of features excluding the TLS and urlencoding features on **Rust 1.48**. This is because those dependencies themselves have a higher MSRV. That said, the crate does still require forcing some dependencies to lower-than-latest versions to actually compile with the older compiler, as these dependencies have upped their MSRV in a patch version. This can be achieved with the following (these just update your Cargo.lock): ```sh cargo update --package=log --precise=0.4.18 cargo update --package=httpdate --precise=1.0.2 cargo update --package=serde_json --precise=1.0.100 cargo update --package=chrono --precise=0.4.23 cargo update --package=num-traits --precise=0.2.18 cargo update --package=tempfile --precise=3.17.1 cargo update --package=libc --precise=0.2.163 cargo update --package=iana-time-zone --precise=0.1.61 # This again, for some reason. cargo update --package=httpdate --precise=1.0.2 ``` ## License This crate is distributed under the terms of the [ISC license](COPYING.md). minreq-2.13.4/build.rs000064400000000000000000000011751046102023000126570ustar 00000000000000use std::env; fn main() { if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); #[allow(clippy::unusual_byte_groupings)] if version >= 0x1_01_00_00_0 { println!("cargo:rustc-cfg=have_min_max_version"); } } if let Ok(version) = env::var("DEP_OPENSSL_LIBRESSL_VERSION_NUMBER") { let version = u64::from_str_radix(&version, 16).unwrap(); #[allow(clippy::unusual_byte_groupings)] if version >= 0x2_06_01_00_0 { println!("cargo:rustc-cfg=have_min_max_version"); } } } minreq-2.13.4/examples/hello.rs000064400000000000000000000003711046102023000144760ustar 00000000000000//! This is a simple example to demonstrate the usage of this library. fn main() -> Result<(), minreq::Error> { let response = minreq::get("http://example.com").send()?; let html = response.as_str()?; println!("{}", html); Ok(()) } minreq-2.13.4/examples/iterator.rs000064400000000000000000000026021046102023000152230ustar 00000000000000//! This example demonstrates probably the most complicated part of //! `minreq`. Useful when making loading bars, for example. fn main() -> Result<(), minreq::Error> { let mut buffer = Vec::new(); for byte in minreq::get("http://example.com").send_lazy()? { // The connection could have a problem at any point during the // download, so each byte needs to be unwrapped. let (byte, len) = byte?; // The `byte` is the current u8 of data we're iterating // through. print!("{}", byte as char); // The `len` is the expected amount of incoming bytes // including the current one: this will be the rest of the // body if the server provided a Content-Length header, or // just the size of the remaining chunk in chunked transfers. buffer.reserve(len); buffer.push(byte); // Flush the printed text so each char appears on your // terminal right away. flush(); // Wait for 50ms so the data doesn't appear instantly fast // internet connections, to demonstrate that the body is being // printed char-by-char. sleep(); } Ok(()) } // Helper functions fn flush() { use std::io::{stdout, Write}; stdout().lock().flush().ok(); } fn sleep() { use std::thread::sleep; use std::time::Duration; sleep(Duration::from_millis(2)); } minreq-2.13.4/examples/json.rs000064400000000000000000000006351046102023000143470ustar 00000000000000//! This example demonstrates the `json-using-serde` feature. fn main() -> Result<(), minreq::Error> { let response = minreq::get("http://httpbin.org/anything") .with_body("Hello, world!") .send()?; // httpbin.org/anything returns the body in the json field "data": let json: serde_json::Value = response.json()?; println!("\"Hello, world!\" == {}", json["data"]); Ok(()) } minreq-2.13.4/src/connection.rs000064400000000000000000000406621046102023000145120ustar 00000000000000#[cfg(all( not(feature = "rustls"), any(feature = "openssl", feature = "native-tls") ))] use crate::native_tls::{TlsConnector, TlsStream}; use crate::request::ParsedRequest; use crate::{Error, Method, ResponseLazy}; #[cfg(feature = "rustls")] use rustls::{self, ClientConfig, ClientConnection, RootCertStore, ServerName, StreamOwned}; #[cfg(feature = "rustls")] use std::convert::TryFrom; use std::env; use std::io::{self, Read, Write}; use std::net::{TcpStream, ToSocketAddrs}; #[cfg(feature = "rustls")] use std::sync::Arc; use std::time::{Duration, Instant}; #[cfg(feature = "rustls-webpki")] use webpki_roots::TLS_SERVER_ROOTS; #[cfg(feature = "rustls")] static CONFIG: std::sync::LazyLock> = std::sync::LazyLock::new(|| { let mut root_certificates = RootCertStore::empty(); // Try to load native certs #[cfg(feature = "https-rustls-probe")] if let Ok(os_roots) = rustls_native_certs::load_native_certs() { for root_cert in os_roots { // Ignore erroneous OS certificates, there's nothing // to do differently in that situation anyways. let _ = root_certificates.add(&rustls::Certificate(root_cert.0)); } } #[cfg(feature = "rustls-webpki")] #[allow(deprecated)] // Need to use add_server_trust_anchors to compile with rustls 0.21.1 root_certificates.add_server_trust_anchors(TLS_SERVER_ROOTS.iter().map(|ta| { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) })); let config = ClientConfig::builder() .with_safe_defaults() .with_root_certificates(root_certificates) .with_no_client_auth(); Arc::new(config) }); type UnsecuredStream = TcpStream; #[cfg(feature = "rustls")] type SecuredStream = StreamOwned; #[cfg(all( not(feature = "rustls"), any(feature = "openssl", feature = "native-tls") ))] type SecuredStream = TlsStream; pub(crate) enum HttpStream { Unsecured(UnsecuredStream, Option), #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] Secured(Box, Option), } impl HttpStream { fn create_unsecured(reader: UnsecuredStream, timeout_at: Option) -> HttpStream { HttpStream::Unsecured(reader, timeout_at) } #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] fn create_secured(reader: SecuredStream, timeout_at: Option) -> HttpStream { HttpStream::Secured(Box::new(reader), timeout_at) } } fn timeout_err() -> io::Error { io::Error::new( io::ErrorKind::TimedOut, "the timeout of the request was reached", ) } fn timeout_at_to_duration(timeout_at: Option) -> Result, io::Error> { if let Some(timeout_at) = timeout_at { if let Some(duration) = timeout_at.checked_duration_since(Instant::now()) { Ok(Some(duration)) } else { Err(timeout_err()) } } else { Ok(None) } } impl Read for HttpStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { let timeout = |tcp: &TcpStream, timeout_at: Option| -> io::Result<()> { let _ = tcp.set_read_timeout(timeout_at_to_duration(timeout_at)?); Ok(()) }; let result = match self { HttpStream::Unsecured(inner, timeout_at) => { timeout(inner, *timeout_at)?; inner.read(buf) } #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] HttpStream::Secured(inner, timeout_at) => { timeout(inner.get_ref(), *timeout_at)?; inner.read(buf) } }; match result { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { // We're a blocking socket, so EWOULDBLOCK indicates a timeout Err(timeout_err()) } r => r, } } } /// A connection to the server for sending /// [`Request`](struct.Request.html)s. pub struct Connection { request: ParsedRequest, timeout_at: Option, } impl Connection { /// Creates a new `Connection`. See [Request] and [ParsedRequest] /// for specifics about *what* is being sent. pub(crate) fn new(request: ParsedRequest) -> Connection { let timeout = request .config .timeout .or_else(|| match env::var("MINREQ_TIMEOUT") { Ok(t) => t.parse::().ok(), Err(_) => None, }); let timeout_at = timeout.map(|t| Instant::now() + Duration::from_secs(t)); Connection { request, timeout_at, } } /// Returns the timeout duration for operations that should end at /// timeout and are starting "now". /// /// The Result will be Err if the timeout has already passed. fn timeout(&self) -> Result, io::Error> { let timeout = timeout_at_to_duration(self.timeout_at); log::trace!("Timeout requested, it is currently: {:?}", timeout); timeout } /// Sends the [`Request`](struct.Request.html), consumes this /// connection, and returns a [`Response`](struct.Response.html). #[cfg(feature = "rustls")] pub(crate) fn send_https(mut self) -> Result { enforce_timeout(self.timeout_at, move || { self.request.url.host = ensure_ascii_host(self.request.url.host)?; let bytes = self.request.as_bytes(); // Rustls setup log::trace!("Setting up TLS parameters for {}.", self.request.url.host); let dns_name = match ServerName::try_from(&*self.request.url.host) { Ok(result) => result, Err(err) => return Err(Error::IoError(io::Error::new(io::ErrorKind::Other, err))), }; let sess = ClientConnection::new(CONFIG.clone(), dns_name) .map_err(Error::RustlsCreateConnection)?; log::trace!("Establishing TCP connection to {}.", self.request.url.host); let tcp = self.connect()?; // Send request log::trace!("Establishing TLS session to {}.", self.request.url.host); let mut tls = StreamOwned::new(sess, tcp); // I don't think this actually does any communication. log::trace!("Writing HTTPS request to {}.", self.request.url.host); let _ = tls.get_ref().set_write_timeout(self.timeout()?); tls.write_all(&bytes)?; // Receive request log::trace!("Reading HTTPS response from {}.", self.request.url.host); let response = ResponseLazy::from_stream( HttpStream::create_secured(tls, self.timeout_at), self.request.config.max_headers_size, self.request.config.max_status_line_len, )?; handle_redirects(self, response) }) } /// Sends the [`Request`](struct.Request.html), consumes this /// connection, and returns a [`Response`](struct.Response.html). #[cfg(all( not(feature = "rustls"), any(feature = "openssl", feature = "native-tls") ))] pub(crate) fn send_https(mut self) -> Result { enforce_timeout(self.timeout_at, move || { self.request.url.host = ensure_ascii_host(self.request.url.host)?; let bytes = self.request.as_bytes(); log::trace!("Setting up TLS parameters for {}.", self.request.url.host); let dns_name = &self.request.url.host; /* let mut builder = TlsConnector::builder(); ... let sess = match builder.build() { */ let sess = match TlsConnector::new() { Ok(sess) => sess, Err(err) => return Err(Error::IoError(io::Error::new(io::ErrorKind::Other, err))), }; log::trace!("Establishing TCP connection to {}.", self.request.url.host); let tcp = self.connect()?; // Send request log::trace!("Establishing TLS session to {}.", self.request.url.host); let mut tls = match sess.connect(dns_name, tcp) { Ok(tls) => tls, Err(err) => return Err(Error::IoError(io::Error::new(io::ErrorKind::Other, err))), }; log::trace!("Writing HTTPS request to {}.", self.request.url.host); let _ = tls.get_ref().set_write_timeout(self.timeout()?); tls.write_all(&bytes)?; // Receive request log::trace!("Reading HTTPS response from {}.", self.request.url.host); let response = ResponseLazy::from_stream( HttpStream::create_secured(tls, self.timeout_at), self.request.config.max_headers_size, self.request.config.max_status_line_len, )?; handle_redirects(self, response) }) } /// Sends the [`Request`](struct.Request.html), consumes this /// connection, and returns a [`Response`](struct.Response.html). pub(crate) fn send(mut self) -> Result { enforce_timeout(self.timeout_at, move || { self.request.url.host = ensure_ascii_host(self.request.url.host)?; let bytes = self.request.as_bytes(); log::trace!("Establishing TCP connection to {}.", self.request.url.host); let mut tcp = self.connect()?; // Send request log::trace!("Writing HTTP request."); let _ = tcp.set_write_timeout(self.timeout()?); tcp.write_all(&bytes)?; // Receive response log::trace!("Reading HTTP response."); let stream = HttpStream::create_unsecured(tcp, self.timeout_at); let response = ResponseLazy::from_stream( stream, self.request.config.max_headers_size, self.request.config.max_status_line_len, )?; handle_redirects(self, response) }) } fn connect(&self) -> Result { let tcp_connect = |host: &str, port: u32| -> Result { let addrs = (host, port as u16) .to_socket_addrs() .map_err(Error::IoError)?; let addrs_count = addrs.len(); // Try all resolved addresses. Return the first one to which we could connect. If all // failed return the last error encountered. for (i, addr) in addrs.enumerate() { let stream = if let Some(timeout) = self.timeout()? { TcpStream::connect_timeout(&addr, timeout) } else { TcpStream::connect(addr) }; if stream.is_ok() || i == addrs_count - 1 { return stream.map_err(Error::from); } } Err(Error::AddressNotFound) }; #[cfg(feature = "proxy")] match self.request.config.proxy { Some(ref proxy) => { // do proxy things let mut tcp = tcp_connect(&proxy.server, proxy.port)?; write!(tcp, "{}", proxy.connect(&self.request)).unwrap(); tcp.flush()?; let mut proxy_response = Vec::new(); loop { let mut buf = vec![0; 256]; let total = tcp.read(&mut buf)?; proxy_response.append(&mut buf); if total < 256 { break; } } crate::Proxy::verify_response(&proxy_response)?; Ok(tcp) } None => tcp_connect(&self.request.url.host, self.request.url.port.port()), } #[cfg(not(feature = "proxy"))] tcp_connect(&self.request.url.host, self.request.url.port.port()) } } fn handle_redirects( connection: Connection, mut response: ResponseLazy, ) -> Result { let status_code = response.status_code; let url = response.headers.get("location"); match get_redirect(connection, status_code, url) { NextHop::Redirect(connection) => { let connection = connection?; if connection.request.url.https { #[cfg(not(any( feature = "rustls", feature = "openssl", feature = "native-tls" )))] return Err(Error::HttpsFeatureNotEnabled); #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] return connection.send_https(); } else { connection.send() } } NextHop::Destination(connection) => { let dst_url = connection.request.url; dst_url.write_base_url_to(&mut response.url).unwrap(); dst_url.write_resource_to(&mut response.url).unwrap(); Ok(response) } } } enum NextHop { Redirect(Result), Destination(Connection), } fn get_redirect(mut connection: Connection, status_code: i32, url: Option<&String>) -> NextHop { match status_code { 301 | 302 | 303 | 307 => { let url = match url { Some(url) => url, None => return NextHop::Redirect(Err(Error::RedirectLocationMissing)), }; log::debug!("Redirecting ({}) to: {}", status_code, url); match connection.request.redirect_to(url.as_str()) { Ok(()) => { if status_code == 303 { match connection.request.config.method { Method::Post | Method::Put | Method::Delete => { connection.request.config.method = Method::Get; } _ => {} } } NextHop::Redirect(Ok(connection)) } Err(err) => NextHop::Redirect(Err(err)), } } _ => NextHop::Destination(connection), } } fn ensure_ascii_host(host: String) -> Result { if host.is_ascii() { Ok(host) } else { #[cfg(not(feature = "punycode"))] { Err(Error::PunycodeFeatureNotEnabled) } #[cfg(feature = "punycode")] { let mut result = String::with_capacity(host.len() * 2); for s in host.split('.') { if s.is_ascii() { result += s; } else { match punycode::encode(s) { Ok(s) => result = result + "xn--" + &s, Err(_) => return Err(Error::PunycodeConversionFailed), } } result += "."; } result.truncate(result.len() - 1); // Remove the trailing dot Ok(result) } } } /// Enforce the timeout by running the function in a new thread and /// parking the current one with a timeout. /// /// While minreq does use timeouts (somewhat) properly, some /// interfaces such as [ToSocketAddrs] don't allow for specifying the /// timeout. Hence this. fn enforce_timeout(timeout_at: Option, f: F) -> Result where F: 'static + Send + FnOnce() -> Result, R: 'static + Send, { use std::sync::mpsc::{channel, RecvTimeoutError}; match timeout_at { Some(deadline) => { let (sender, receiver) = channel(); let thread = std::thread::spawn(move || { let result = f(); let _ = sender.send(()); result }); if let Some(timeout_duration) = deadline.checked_duration_since(Instant::now()) { match receiver.recv_timeout(timeout_duration) { Ok(()) => thread.join().unwrap(), Err(err) => match err { RecvTimeoutError::Timeout => Err(Error::IoError(timeout_err())), RecvTimeoutError::Disconnected => { Err(Error::Other("request connection paniced")) } }, } } else { Err(Error::IoError(timeout_err())) } } None => f(), } } minreq-2.13.4/src/error.rs000064400000000000000000000152361046102023000135030ustar 00000000000000use std::{error, fmt, io, str}; /// Represents an error while sending, receiving, or parsing an HTTP response. #[derive(Debug)] // TODO: Make non-exhaustive for 3.0? pub enum Error { #[cfg(feature = "json-using-serde")] /// Ran into a Serde error. SerdeJsonError(serde_json::Error), /// The response body contains invalid UTF-8, so the `as_str()` /// conversion failed. InvalidUtf8InBody(str::Utf8Error), #[cfg(feature = "rustls")] /// Ran into a rustls error while creating the connection. RustlsCreateConnection(rustls::Error), /// Ran into an IO problem while loading the response. IoError(io::Error), /// Couldn't parse the incoming chunk's length while receiving a /// response with the header `Transfer-Encoding: chunked`. MalformedChunkLength, /// The chunk did not end after reading the previously read amount /// of bytes. MalformedChunkEnd, /// Couldn't parse the `Content-Length` header's value as an /// `usize`. MalformedContentLength, /// The response contains headers whose total size surpasses /// [Request::with_max_headers_size](crate::request::Request::with_max_headers_size). HeadersOverflow, /// The response's status line length surpasses /// [Request::with_max_status_line_size](crate::request::Request::with_max_status_line_length). StatusLineOverflow, /// [ToSocketAddrs](std::net::ToSocketAddrs) did not resolve to an /// address. AddressNotFound, /// The response was a redirection, but the `Location` header is /// missing. RedirectLocationMissing, /// The response redirections caused an infinite redirection loop. InfiniteRedirectionLoop, /// Followed /// [`max_redirections`](struct.Request.html#method.with_max_redirections) /// redirections, won't follow any more. TooManyRedirections, /// The response contained invalid UTF-8 where it should be valid /// (eg. headers), so the response cannot interpreted correctly. InvalidUtf8InResponse, /// The provided url contained a domain that has non-ASCII /// characters, and could not be converted into punycode. It is /// probably not an actual domain. PunycodeConversionFailed, /// Tried to send a secure request (ie. the url started with /// `https://`), but the crate's `https` feature was not enabled, /// and as such, a connection cannot be made. HttpsFeatureNotEnabled, /// The provided url contained a domain that has non-ASCII /// characters, but it could not be converted into punycode /// because the `punycode` feature was not enabled. PunycodeFeatureNotEnabled, /// The provided proxy information was not properly formatted. See /// [Proxy::new](crate::Proxy::new) for the valid format. BadProxy, /// The provided credentials were rejected by the proxy server. BadProxyCreds, /// The provided proxy credentials were malformed. ProxyConnect, /// The provided credentials were rejected by the proxy server. InvalidProxyCreds, // TODO: Uncomment these two for 3.0 // /// The URL does not start with http:// or https://. // InvalidProtocol, // /// The URL ended up redirecting to an URL that does not start // /// with http:// or https://. // InvalidProtocolInRedirect, /// This is a special error case, one that should never be /// returned! Think of this as a cleaner alternative to calling /// `unreachable!()` inside the library. If you come across this, /// please open an issue, and include the string inside this /// error, as it can be used to locate the problem. Other(&'static str), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Error::*; match self { #[cfg(feature = "json-using-serde")] SerdeJsonError(err) => write!(f, "{}", err), IoError(err) => write!(f, "{}", err), InvalidUtf8InBody(err) => write!(f, "{}", err), #[cfg(feature = "rustls")] RustlsCreateConnection(err) => write!(f, "error creating rustls connection: {}", err), MalformedChunkLength => write!(f, "non-usize chunk length with transfer-encoding: chunked"), MalformedChunkEnd => write!(f, "chunk did not end after reading the expected amount of bytes"), MalformedContentLength => write!(f, "non-usize content length"), HeadersOverflow => write!(f, "the headers' total size surpassed max_headers_size"), StatusLineOverflow => write!(f, "the status line length surpassed max_status_line_length"), AddressNotFound => write!(f, "could not resolve host to a socket address"), RedirectLocationMissing => write!(f, "redirection location header missing"), InfiniteRedirectionLoop => write!(f, "infinite redirection loop detected"), TooManyRedirections => write!(f, "too many redirections (over the max)"), InvalidUtf8InResponse => write!(f, "response contained invalid utf-8 where valid utf-8 was expected"), HttpsFeatureNotEnabled => write!(f, "request url contains https:// but the https feature is not enabled"), PunycodeFeatureNotEnabled => write!(f, "non-ascii urls needs to be converted into punycode, and the feature is missing"), PunycodeConversionFailed => write!(f, "non-ascii url conversion to punycode failed"), BadProxy => write!(f, "the provided proxy information is malformed"), BadProxyCreds => write!(f, "the provided proxy credentials are malformed"), ProxyConnect => write!(f, "could not connect to the proxy server"), InvalidProxyCreds => write!(f, "the provided proxy credentials are invalid"), // TODO: Uncomment these two for 3.0 // InvalidProtocol => write!(f, "the url does not start with http:// or https://"), // InvalidProtocolInRedirect => write!(f, "got redirected to an absolute url which does not start with http:// or https://"), Other(msg) => write!(f, "error in minreq: please open an issue in the minreq repo, include the following: '{}'", msg), } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { use Error::*; match self { #[cfg(feature = "json-using-serde")] SerdeJsonError(err) => Some(err), IoError(err) => Some(err), InvalidUtf8InBody(err) => Some(err), #[cfg(feature = "rustls")] RustlsCreateConnection(err) => Some(err), _ => None, } } } impl From for Error { fn from(other: io::Error) -> Error { Error::IoError(other) } } minreq-2.13.4/src/http_url.rs000064400000000000000000000165551046102023000142200ustar 00000000000000use std::fmt::{self, Write}; use crate::Error; #[derive(Clone, Copy, PartialEq)] pub(crate) enum Port { ImplicitHttp, ImplicitHttps, Explicit(u32), } impl Port { pub(crate) fn port(self) -> u32 { match self { Port::ImplicitHttp => 80, Port::ImplicitHttps => 443, Port::Explicit(port) => port, } } } /// URL split into its parts. See [RFC 3986 section /// 3](https://datatracker.ietf.org/doc/html/rfc3986#section-3). Note that the /// userinfo component is not allowed since [RFC /// 7230](https://datatracker.ietf.org/doc/html/rfc7230#section-2.7.1). /// /// ```text /// scheme "://" host [ ":" port ] path [ "?" query ] [ "#" fragment ] /// ``` #[derive(Clone, PartialEq)] pub(crate) struct HttpUrl { /// If scheme is "https", true, if "http", false. pub(crate) https: bool, /// `host` pub(crate) host: String, /// `[":" port]` pub(crate) port: Port, /// `path ["?" query]` including the `?`. pub(crate) path_and_query: String, /// `["#" fragment]` without the `#`. pub(crate) fragment: Option, } impl HttpUrl { pub(crate) fn parse(url: &str, redirected_from: Option<&HttpUrl>) -> Result { enum UrlParseStatus { Host, Port, PathAndQuery, Fragment, } let (url, https) = if let Some(after_protocol) = url.strip_prefix("http://") { (after_protocol, false) } else if let Some(after_protocol) = url.strip_prefix("https://") { (after_protocol, true) } else { // TODO: Uncomment this for 3.0 // return Err(Error::InvalidProtocol); return Err(Error::IoError(std::io::Error::new( std::io::ErrorKind::Other, "was redirected to an absolute url with an invalid protocol", ))); }; let mut host = String::new(); let mut port = String::new(); let mut resource = String::new(); // At first this is the path and query, after # this becomes fragment. let mut path_and_query = None; let mut status = UrlParseStatus::Host; for c in url.chars() { match status { UrlParseStatus::Host => { match c { '/' | '?' => { // Tolerate typos like: www.example.com?some=params status = UrlParseStatus::PathAndQuery; resource.push(c); } ':' => status = UrlParseStatus::Port, _ => host.push(c), } } UrlParseStatus::Port => match c { '/' | '?' => { status = UrlParseStatus::PathAndQuery; resource.push(c); } _ => port.push(c), }, UrlParseStatus::PathAndQuery if c == '#' => { status = UrlParseStatus::Fragment; path_and_query = Some(resource); resource = String::new(); } #[cfg(not(feature = "urlencoding"))] UrlParseStatus::PathAndQuery | UrlParseStatus::Fragment => resource.push(c), #[cfg(feature = "urlencoding")] UrlParseStatus::PathAndQuery | UrlParseStatus::Fragment => match c { // All URL-'safe' characters, plus URL 'special // characters' like &, #, =, / ,? '0'..='9' | 'A'..='Z' | 'a'..='z' | '-' | '.' | '_' | '~' | '&' | '#' | '=' | '/' | '?' => { resource.push(c); } // There is probably a simpler way to do this, but this // method avoids any heap allocations (except extending // `resource`) _ => { // Any UTF-8 character can fit in 4 bytes let mut utf8_buf = [0u8; 4]; // Bytes fill buffer from the front c.encode_utf8(&mut utf8_buf); // Slice disregards the unused portion of the buffer utf8_buf[..c.len_utf8()].iter().for_each(|byte| { // Convert byte to URL escape, e.g. %21 for b'!' let rem = *byte % 16; let right_char = to_hex_digit(rem); let left_char = to_hex_digit((*byte - rem) >> 4); resource.push('%'); resource.push(left_char); resource.push(right_char); }); } }, } } let (mut path_and_query, mut fragment) = if let Some(path_and_query) = path_and_query { (path_and_query, Some(resource)) } else { (resource, None) }; // If a redirected resource does not have a fragment, but the original // URL did, the fragment should be preserved over redirections. See RFC // 7231 section 7.1.2. if fragment.is_none() { if let Some(old_fragment) = redirected_from.and_then(|url| url.fragment.clone()) { fragment = Some(old_fragment); } } // Ensure the resource is *something* if path_and_query.is_empty() { path_and_query.push('/'); } // Set appropriate port let port = port.parse::().map(Port::Explicit).unwrap_or_else(|_| { if https { Port::ImplicitHttps } else { Port::ImplicitHttp } }); Ok(HttpUrl { https, host, port, path_and_query, fragment, }) } /// Writes the `scheme "://" host [ ":" port ]` part to the destination. pub(crate) fn write_base_url_to(&self, dst: &mut W) -> fmt::Result { write!( dst, "http{s}://{host}", s = if self.https { "s" } else { "" }, host = &self.host, )?; if let Port::Explicit(port) = self.port { write!(dst, ":{}", port)?; } Ok(()) } /// Writes the `path [ "?" query ] [ "#" fragment ]` part to the destination. pub(crate) fn write_resource_to(&self, dst: &mut W) -> fmt::Result { write!( dst, "{path_and_query}{maybe_hash}{maybe_fragment}", path_and_query = &self.path_and_query, maybe_hash = if self.fragment.is_some() { "#" } else { "" }, maybe_fragment = self.fragment.as_deref().unwrap_or(""), ) } } // https://github.com/kornelski/rust_urlencoding/blob/a4df8027ab34a86a63f1be727965cf101556403f/src/enc.rs#L130-L136 // Converts a UTF-8 byte to a single hexadecimal character #[cfg(feature = "urlencoding")] fn to_hex_digit(digit: u8) -> char { match digit { 0..=9 => (b'0' + digit) as char, 10..=255 => (b'A' - 10 + digit) as char, } } minreq-2.13.4/src/lib.rs000064400000000000000000000206341046102023000131160ustar 00000000000000//! # Minreq //! //! Simple, minimal-dependency HTTP client. The library has a very //! minimal API, so you'll probably know everything you need to after //! reading a few examples. //! //! Note: as a minimal library, minreq has been written with the //! assumption that servers are well-behaved. This means that there is //! little error-correction for incoming data, which may cause some //! requests to fail unexpectedly. If you're writing an application or //! library that connects to servers you can't test beforehand, //! consider using a more robust library, such as //! [curl](https://crates.io/crates/curl). //! //! # Additional features //! //! Since the crate is supposed to be minimal in terms of //! dependencies, there are no default features, and optional //! functionality can be enabled by specifying features for `minreq` //! dependency in `Cargo.toml`: //! //! ```toml //! [dependencies] //! minreq = { version = "2.13.4", features = ["punycode"] } //! ``` //! //! Below is the list of all available features. //! //! ## `https` or `https-rustls` //! //! This feature uses the (very good) //! [`rustls`](https://crates.io/crates/rustls) crate to secure the //! connection when needed. Note that if this feature is not enabled //! (and it is not by default), requests to urls that start with //! `https://` will fail and return a //! [`HttpsFeatureNotEnabled`](enum.Error.html#variant.HttpsFeatureNotEnabled) //! error. `https` was the name of this feature until the other https //! feature variants were added, and is now an alias for //! `https-rustls`. //! //! ## `https-rustls-probe` //! //! Like `https-rustls`, but also includes the //! [`rustls-native-certs`](https://crates.io/crates/rustls-native-certs) //! crate to auto-detect root certificates installed in common //! locations. //! //! ## `https-native` //! //! Like `https`, but uses //! [`tls-native`](https://crates.io/crates/native-tls) instead of //! `rustls`. //! //! ## `https-bundled` //! //! Like `https`, but uses a statically linked copy of the OpenSSL //! library (provided by //! [`openssl-sys`](https://crates.io/crates/openssl-sys) with //! features = "vendored"). This feature on its own doesn't provide //! any detection of where your root certificates are installed. They //! can be specified via the environment variables `SSL_CERT_FILE` or //! `SSL_CERT_DIR`. //! //! ## `https-bundled-probe` //! //! Like `https-bundled`, but also includes the //! [`openssl-probe`](https://crates.io/crates/openssl-probe) crate to //! auto-detect root certificates installed in common locations. //! //! ## `json-using-serde` //! //! This feature allows both serialize and deserialize JSON payload //! using the [`serde_json`](https://crates.io/crates/serde_json) //! crate. //! //! [`Request`](struct.Request.html) and //! [`Response`](struct.Response.html) expose //! [`with_json()`](struct.Request.html#method.with_json) and //! [`json()`](struct.Response.html#method.json) for constructing the //! struct from JSON and extracting the JSON body out, respectively. //! //! ## `punycode` //! //! This feature enables requests to non-ascii domains: the //! [`punycode`](https://crates.io/crates/punycode) crate is used to //! convert the non-ascii parts into their punycode representations //! before making the request. If you try to make a request to 㯙㯜㯙 //! 㯟.net or i❤.ws for example, with this feature disabled (as it is //! by default), your request will fail with a //! [`PunycodeFeatureNotEnabled`](enum.Error.html#variant.PunycodeFeatureNotEnabled) //! error. //! //! ## `proxy` //! //! This feature enables HTTP proxy support. See [Proxy]. //! //! ## `urlencoding` //! //! This feature enables percent-encoding for the URL resource when //! creating a request and any subsequently added parameters from //! [`Request::with_param`]. //! //! # Examples //! //! ## Get //! //! This is a simple example of sending a GET request and printing out //! the response's body, status code, and reason phrase. The `?` are //! needed because the server could return invalid UTF-8 in the body, //! or something could go wrong during the download. //! //! ``` //! # fn main() -> Result<(), Box> { //! let response = minreq::get("http://example.com").send()?; //! assert!(response.as_str()?.contains("")); //! assert_eq!(200, response.status_code); //! assert_eq!("OK", response.reason_phrase); //! # Ok(()) } //! ``` //! //! Note: you could change the `get` function to `head` or `put` or //! any other HTTP request method: the api is the same for all of //! them, it just changes what is sent to the server. //! //! ## Body (sending) //! //! To include a body, add `with_body("")` before //! `send()`. //! //! ``` //! # fn main() -> Result<(), Box> { //! let response = minreq::post("http://example.com") //! .with_body("Foobar") //! .send()?; //! # Ok(()) } //! ``` //! //! ## Headers (sending) //! //! To add a header, add `with_header("Key", "Value")` before //! `send()`. //! //! ``` //! # fn main() -> Result<(), Box> { //! let response = minreq::get("http://example.com") //! .with_header("Accept", "text/html") //! .send()?; //! # Ok(()) } //! ``` //! //! ## Headers (receiving) //! //! Reading the headers sent by the servers is done via the //! [`headers`](struct.Response.html#structfield.headers) field of the //! [`Response`](struct.Response.html). Note: the header field names //! (that is, the *keys* of the `HashMap`) are all lowercase: this is //! because the names are case-insensitive according to the spec, and //! this unifies the casings for easier `get()`ing. //! //! ``` //! # fn main() -> Result<(), Box> { //! let response = minreq::get("http://example.com").send()?; //! assert!(response.headers.get("content-type").unwrap().starts_with("text/html")); //! # Ok(()) } //! ``` //! //! ## Timeouts //! //! To avoid timing out, or limit the request's response time, use //! `with_timeout(n)` before `send()`. The given value is in seconds. //! //! NOTE: There is no timeout by default. //! //! ```no_run //! # fn main() -> Result<(), Box> { //! let response = minreq::post("http://example.com") //! .with_timeout(10) //! .send()?; //! # Ok(()) } //! ``` //! //! ## Proxy //! //! To use a proxy server, simply create a `Proxy` instance and use //! `.with_proxy()` on your request. //! //! Supported proxy formats are `host:port` and //! `user:password@proxy:host`. Only HTTP CONNECT proxies are //! supported at this time. //! //! ```no_run //! # fn main() -> Result<(), Box> { //! #[cfg(feature = "proxy")] //! { //! let proxy = minreq::Proxy::new("localhost:8080")?; //! let response = minreq::post("http://example.com") //! .with_proxy(proxy) //! .send()?; //! println!("{}", response.as_str()?); //! } //! # Ok(()) } //! ``` //! //! # Timeouts //! //! By default, a request has no timeout. You can change this in two //! ways: //! //! - Use [`with_timeout`](struct.Request.html#method.with_timeout) on //! your request to set the timeout per-request like so: //! ``` //! minreq::get("/").with_timeout(8).send(); //! ``` //! - Set the environment variable `MINREQ_TIMEOUT` to the desired //! amount of seconds until timeout. Ie. if you have a program called //! `foo` that uses minreq, and you want all the requests made by that //! program to timeout in 8 seconds, you launch the program like so: //! ```text,ignore //! $ MINREQ_TIMEOUT=8 ./foo //! ``` //! Or add the following somewhere before the requests in the code. //! ``` //! std::env::set_var("MINREQ_TIMEOUT", "8"); //! ``` //! If the timeout is set with `with_timeout`, the environment //! variable will be ignored. #![deny(missing_docs)] #[cfg(feature = "rustls")] extern crate rustls; #[cfg(feature = "openssl")] mod native_tls; #[cfg(feature = "openssl")] #[macro_use] extern crate log; #[cfg(all(feature = "native-tls", not(feature = "openssl")))] extern crate native_tls; #[cfg(feature = "openssl-probe")] extern crate openssl_probe; #[cfg(feature = "webpki-roots")] extern crate webpki; #[cfg(feature = "webpki-roots")] extern crate webpki_roots; #[cfg(feature = "json-using-serde")] extern crate serde; #[cfg(feature = "json-using-serde")] extern crate serde_json; mod connection; mod error; mod http_url; #[cfg(feature = "proxy")] mod proxy; mod request; mod response; pub use error::*; #[cfg(feature = "proxy")] pub use proxy::*; pub use request::*; pub use response::*; minreq-2.13.4/src/native_tls/LICENSE-MIT000064400000000000000000000020621046102023000156010ustar 00000000000000Copyright (c) 2016 The rust-native-tls Developers 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. minreq-2.13.4/src/native_tls/mod.rs000064400000000000000000000425251046102023000153020ustar 00000000000000// Derived from https://lib.rs/crates/native-tls. #![allow(dead_code)] use std::any::Any; use std::error; use std::fmt; use std::io; use std::result; // moved to ../lib.rs // #[macro_use] // extern crate log; #[path = "openssl.rs"] mod imp; /// A typedef of the result-type returned by many methods. pub type Result = result::Result; /// An error returned from the TLS implementation. pub struct Error(imp::Error); impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { error::Error::source(&self.0) } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, fmt) } } impl fmt::Debug for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl From for Error { fn from(err: imp::Error) -> Error { Error(err) } } /// A cryptographic identity. /// /// An identity is an X509 certificate along with its corresponding private key and chain of certificates to a trusted /// root. #[derive(Clone)] pub struct Identity(imp::Identity); /* 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 should be sent to clients to allow them 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 /// ``` pub fn from_pkcs12(der: &[u8], password: &str) -> Result { let identity = imp::Identity::from_pkcs12(der, password)?; Ok(Identity(identity)) } } */ /// An X509 certificate. #[derive(Clone)] pub struct Certificate(imp::Certificate); /* impl Certificate { /// Parses a DER-formatted X509 certificate. pub fn from_der(der: &[u8]) -> Result { let cert = imp::Certificate::from_der(der)?; Ok(Certificate(cert)) } /// Parses a PEM-formatted X509 certificate. pub fn from_pem(pem: &[u8]) -> Result { let cert = imp::Certificate::from_pem(pem)?; Ok(Certificate(cert)) } /// Returns the DER-encoded representation of this certificate. pub fn to_der(&self) -> Result> { let der = self.0.to_der()?; Ok(der) } } */ /// A TLS stream which has been interrupted midway through the handshake process. pub struct MidHandshakeTlsStream(imp::MidHandshakeTlsStream); impl fmt::Debug for MidHandshakeTlsStream where S: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } /* impl MidHandshakeTlsStream { /// Returns a shared reference to the inner stream. pub fn get_ref(&self) -> &S { self.0.get_ref() } /// Returns a mutable reference to the inner stream. pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl MidHandshakeTlsStream where S: io::Read + io::Write, { /// Restarts the handshake process. /// /// If the handshake completes successfully then the negotiated stream is /// returned. If there is a problem, however, then an error is returned. /// Note that the error may not be fatal. For example if the underlying /// stream is an asynchronous one then `HandshakeError::WouldBlock` may /// just mean to wait for more I/O to happen later. pub fn handshake(self) -> result::Result, HandshakeError> { match self.0.handshake() { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } */ /// An error returned from `ClientBuilder::handshake`. #[derive(Debug)] pub enum HandshakeError { /// A fatal error. Failure(Error), /// A stream interrupted midway through the handshake process due to a /// `WouldBlock` error. /// /// Note that this is not a fatal error and it should be safe to call /// `handshake` at a later time once the stream is ready to perform I/O /// again. WouldBlock(MidHandshakeTlsStream), } impl error::Error for HandshakeError where S: Any + fmt::Debug, { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { HandshakeError::Failure(ref e) => Some(e), HandshakeError::WouldBlock(_) => None, } } } impl fmt::Display for HandshakeError where S: Any + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { HandshakeError::Failure(ref e) => fmt::Display::fmt(e, fmt), HandshakeError::WouldBlock(_) => fmt.write_str("the handshake process was interrupted"), } } } impl From> for HandshakeError { fn from(e: imp::HandshakeError) -> HandshakeError { match e { imp::HandshakeError::Failure(e) => HandshakeError::Failure(Error(e)), imp::HandshakeError::WouldBlock(s) => { HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) } } } } /// SSL/TLS protocol versions. #[derive(Debug, Copy, Clone)] #[allow(dead_code, clippy::manual_non_exhaustive)] pub enum Protocol { /// The SSL 3.0 protocol. /// /// # Warning /// /// SSL 3.0 has severe security flaws, and should not be used unless absolutely necessary. If /// you are not sure if you need to enable this protocol, you should not. Sslv3, /// The TLS 1.0 protocol. Tlsv10, /// The TLS 1.1 protocol. Tlsv11, /// The TLS 1.2 protocol. Tlsv12, #[doc(hidden)] __NonExhaustive, } /// A builder for `TlsConnector`s. pub struct TlsConnectorBuilder { identity: Option, min_protocol: Option, max_protocol: Option, root_certificates: Vec, accept_invalid_certs: bool, accept_invalid_hostnames: bool, use_sni: bool, disable_built_in_roots: bool, } impl TlsConnectorBuilder { /* /// Sets the identity to be used for client certificate authentication. pub fn identity(&mut self, identity: Identity) -> &mut TlsConnectorBuilder { self.identity = Some(identity); self } /// Sets the minimum supported protocol version. /// /// A value of `None` enables support for the oldest protocols supported by the implementation. /// /// Defaults to `Some(Protocol::Tlsv10)`. pub fn min_protocol_version(&mut self, protocol: Option) -> &mut TlsConnectorBuilder { self.min_protocol = protocol; self } /// Sets the maximum supported protocol version. /// /// A value of `None` enables support for the newest protocols supported by the implementation. /// /// Defaults to `None`. pub fn max_protocol_version(&mut self, protocol: Option) -> &mut TlsConnectorBuilder { self.max_protocol = protocol; self } /// Adds a certificate to the set of roots that the connector will trust. /// /// The connector will use the system's trust root by default. This method can be used to add /// to that set when communicating with servers not trusted by the system. /// /// Defaults to an empty set. pub fn add_root_certificate(&mut self, cert: Certificate) -> &mut TlsConnectorBuilder { self.root_certificates.push(cert); self } /// Controls the use of built-in system certificates during certificate validation. /// /// Defaults to `false` -- built-in system certs will be used. pub fn disable_built_in_roots(&mut self, disable: bool) -> &mut TlsConnectorBuilder { self.disable_built_in_roots = disable; 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. pub fn danger_accept_invalid_certs( &mut self, accept_invalid_certs: bool, ) -> &mut TlsConnectorBuilder { self.accept_invalid_certs = accept_invalid_certs; self } /// Controls the use of Server Name Indication (SNI). /// /// Defaults to `true`. pub fn use_sni(&mut self, use_sni: bool) -> &mut TlsConnectorBuilder { self.use_sni = use_sni; self } /// Controls the use of hostname verification. /// /// Defaults to `false`. /// /// # Warning /// /// You should think very carefully before using this method. If invalid hostnames are trusted, *any* valid /// certificate for *any* site will be trusted for use. This introduces significant vulnerabilities, and should /// only be used as a last resort. pub fn danger_accept_invalid_hostnames( &mut self, accept_invalid_hostnames: bool, ) -> &mut TlsConnectorBuilder { self.accept_invalid_hostnames = accept_invalid_hostnames; self } */ /// Creates a new `TlsConnector`. pub fn build(&self) -> Result { let connector = imp::TlsConnector::new(self)?; Ok(TlsConnector(connector)) } } /// A builder for client-side TLS connections. /// /// # Examples /// /// ```rust,ignore /// use native_tls::TlsConnector; /// use std::io::{Read, Write}; /// use std::net::TcpStream; /// /// let connector = TlsConnector::new().unwrap(); /// /// let stream = TcpStream::connect("google.com:443").unwrap(); /// let mut stream = connector.connect("google.com", stream).unwrap(); /// /// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap(); /// let mut res = vec![]; /// stream.read_to_end(&mut res).unwrap(); /// println!("{}", String::from_utf8_lossy(&res)); /// ``` #[derive(Clone, Debug)] pub struct TlsConnector(imp::TlsConnector); impl TlsConnector { /// Returns a new connector with default settings. pub fn new() -> Result { TlsConnector::builder().build() } /// Returns a new builder for a `TlsConnector`. pub fn builder() -> TlsConnectorBuilder { TlsConnectorBuilder { identity: None, min_protocol: Some(Protocol::Tlsv10), max_protocol: None, root_certificates: vec![], use_sni: true, accept_invalid_certs: false, accept_invalid_hostnames: false, disable_built_in_roots: false, } } /// Initiates a TLS handshake. /// /// The provided domain will be used for both SNI and certificate hostname /// validation. /// /// If the socket is nonblocking and a `WouldBlock` error is returned during /// the handshake, a `HandshakeError::WouldBlock` error will be returned /// which can be used to restart the handshake when the socket is ready /// again. /// /// The domain is ignored if both SNI and hostname verification are /// disabled. pub fn connect( &self, domain: &str, stream: S, ) -> result::Result, HandshakeError> where S: io::Read + io::Write, { let s = self.0.connect(domain, stream)?; Ok(TlsStream(s)) } } /* /// A builder for `TlsAcceptor`s. pub struct TlsAcceptorBuilder { identity: Identity, min_protocol: Option, max_protocol: Option, } impl TlsAcceptorBuilder { /// Sets the minimum supported protocol version. /// /// A value of `None` enables support for the oldest protocols supported by the implementation. /// /// Defaults to `Some(Protocol::Tlsv10)`. pub fn min_protocol_version(&mut self, protocol: Option) -> &mut TlsAcceptorBuilder { self.min_protocol = protocol; self } /// Sets the maximum supported protocol version. /// /// A value of `None` enables support for the newest protocols supported by the implementation. /// /// Defaults to `None`. pub fn max_protocol_version(&mut self, protocol: Option) -> &mut TlsAcceptorBuilder { self.max_protocol = protocol; self } /// Creates a new `TlsAcceptor`. pub fn build(&self) -> Result { let acceptor = imp::TlsAcceptor::new(self)?; Ok(TlsAcceptor(acceptor)) } } */ /// A builder for server-side TLS connections. /// /// # Examples /// /// ```rust,ignore /// use native_tls::{Identity, TlsAcceptor, TlsStream}; /// use std::fs::File; /// use std::io::{Read}; /// use std::net::{TcpListener, TcpStream}; /// use std::sync::Arc; /// use std::thread; /// /// let mut file = File::open("identity.pfx").unwrap(); /// let mut identity = vec![]; /// file.read_to_end(&mut identity).unwrap(); /// let identity = Identity::from_pkcs12(&identity, "hunter2").unwrap(); /// /// let listener = TcpListener::bind("0.0.0.0:8443").unwrap(); /// let acceptor = TlsAcceptor::new(identity).unwrap(); /// let acceptor = Arc::new(acceptor); /// /// fn handle_client(stream: TlsStream) { /// // ... /// } /// /// for stream in listener.incoming() { /// match stream { /// Ok(stream) => { /// let acceptor = acceptor.clone(); /// thread::spawn(move || { /// let stream = acceptor.accept(stream).unwrap(); /// handle_client(stream); /// }); /// } /// Err(e) => { /* connection failed */ } /// } /// } /// ``` #[derive(Clone)] pub struct TlsAcceptor(imp::TlsAcceptor); /* impl TlsAcceptor { /// Creates a acceptor with default settings. /// /// The identity acts as the server's private key/certificate chain. pub fn new(identity: Identity) -> Result { TlsAcceptor::builder(identity).build() } /// Returns a new builder for a `TlsAcceptor`. /// /// The identity acts as the server's private key/certificate chain. pub fn builder(identity: Identity) -> TlsAcceptorBuilder { TlsAcceptorBuilder { identity, min_protocol: Some(Protocol::Tlsv10), max_protocol: None, } } /// Initiates a TLS handshake. /// /// If the socket is nonblocking and a `WouldBlock` error is returned during /// the handshake, a `HandshakeError::WouldBlock` error will be returned /// which can be used to restart the handshake when the socket is ready /// again. pub fn accept(&self, stream: S) -> result::Result, HandshakeError> where S: io::Read + io::Write, { match self.0.accept(stream) { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } */ /// A stream managing a TLS session. pub struct TlsStream(imp::TlsStream); impl fmt::Debug for TlsStream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl TlsStream { /// Returns a shared reference to the inner stream. pub fn get_ref(&self) -> &S { self.0.get_ref() } /// Returns a mutable reference to the inner stream. #[allow(dead_code)] pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } /* impl TlsStream { /// Returns the number of bytes that can be read without resulting in any /// network calls. pub fn buffered_read_size(&self) -> Result { Ok(self.0.buffered_read_size()?) } /// Returns the peer's leaf certificate, if available. pub fn peer_certificate(&self) -> Result> { Ok(self.0.peer_certificate()?.map(Certificate)) } /// Returns the tls-server-end-point channel binding data as defined in [RFC 5929]. /// /// [RFC 5929]: https://tools.ietf.org/html/rfc5929 pub fn tls_server_end_point(&self) -> Result>> { Ok(self.0.tls_server_end_point()?) } /// Shuts down the TLS session. pub fn shutdown(&mut self) -> io::Result<()> { self.0.shutdown()?; Ok(()) } } */ impl io::Read for TlsStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl io::Write for TlsStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.flush() } } fn _check_kinds() { use std::net::TcpStream; fn is_sync() {} fn is_send() {} is_sync::(); is_send::(); is_sync::(); is_send::(); is_sync::(); is_send::(); /* is_sync::(); is_send::(); */ is_sync::(); is_send::(); is_sync::>(); is_send::>(); is_sync::>(); is_send::>(); } minreq-2.13.4/src/native_tls/openssl.rs000064400000000000000000000301151046102023000161760ustar 00000000000000use openssl::error::ErrorStack; /* use ::openssl::hash::MessageDigest; use ::openssl::nid::Nid; use ::openssl::pkcs12::Pkcs12; */ use openssl::pkey::PKey; use openssl::ssl::{ self, MidHandshakeSslStream, SslAcceptor, SslConnector, SslContextBuilder, SslMethod, SslVerifyMode, }; use openssl::x509::{store::X509StoreBuilder, X509VerifyResult, X509}; use std::error; use std::fmt; use std::io; use super::{Protocol, TlsConnectorBuilder}; /* use super::{Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; */ use openssl::pkey::Private; #[cfg(have_min_max_version)] fn supported_protocols( min: Option, max: Option, ctx: &mut SslContextBuilder, ) -> Result<(), ErrorStack> { use openssl::ssl::SslVersion; fn cvt(p: Protocol) -> SslVersion { match p { Protocol::Sslv3 => SslVersion::SSL3, Protocol::Tlsv10 => SslVersion::TLS1, Protocol::Tlsv11 => SslVersion::TLS1_1, Protocol::Tlsv12 => SslVersion::TLS1_2, Protocol::__NonExhaustive => unreachable!(), } } ctx.set_min_proto_version(min.map(cvt))?; ctx.set_max_proto_version(max.map(cvt))?; Ok(()) } #[cfg(not(have_min_max_version))] fn supported_protocols( min: Option, max: Option, ctx: &mut SslContextBuilder, ) -> Result<(), ErrorStack> { use openssl::ssl::SslOptions; let no_ssl_mask = SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2; ctx.clear_options(no_ssl_mask); let mut options = SslOptions::empty(); options |= match min { None => SslOptions::empty(), Some(Protocol::Sslv3) => SslOptions::NO_SSLV2, Some(Protocol::Tlsv10) => SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3, Some(Protocol::Tlsv11) => { SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 } Some(Protocol::Tlsv12) => { SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 } Some(Protocol::__NonExhaustive) => unreachable!(), }; options |= match max { None | Some(Protocol::Tlsv12) => SslOptions::empty(), Some(Protocol::Tlsv11) => SslOptions::NO_TLSV1_2, Some(Protocol::Tlsv10) => SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2, Some(Protocol::Sslv3) => { SslOptions::NO_TLSV1 | SslOptions::NO_TLSV1_1 | SslOptions::NO_TLSV1_2 } Some(Protocol::__NonExhaustive) => unreachable!(), }; ctx.set_options(options); Ok(()) } #[cfg(target_os = "android")] fn load_android_root_certs(connector: &mut SslContextBuilder) -> Result<(), Error> { use std::fs; if let Ok(dir) = fs::read_dir("/system/etc/security/cacerts") { let certs = dir .filter_map(|r| r.ok()) .filter_map(|e| fs::read(e.path()).ok()) .filter_map(|b| X509::from_pem(&b).ok()); for cert in certs { if let Err(err) = connector.cert_store_mut().add_cert(cert) { debug!("load_android_root_certs error: {:?}", err); } } } Ok(()) } #[derive(Debug)] pub enum Error { Normal(ErrorStack), Ssl(ssl::Error, X509VerifyResult), } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { Error::Normal(ref e) => error::Error::source(e), Error::Ssl(ref e, _) => error::Error::source(e), } } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { Error::Normal(ref e) => fmt::Display::fmt(e, fmt), Error::Ssl(ref e, X509VerifyResult::OK) => fmt::Display::fmt(e, fmt), Error::Ssl(ref e, v) => write!(fmt, "{} ({})", e, v), } } } impl From for Error { fn from(err: ErrorStack) -> Error { Error::Normal(err) } } #[derive(Clone)] pub struct Identity { pkey: PKey, cert: X509, chain: Vec, } /* impl Identity { pub fn from_pkcs12(buf: &[u8], pass: &str) -> Result { let pkcs12 = Pkcs12::from_der(buf)?; let parsed = pkcs12.parse(pass)?; Ok(Identity { pkey: parsed.pkey, cert: parsed.cert, chain: parsed.chain.into_iter().flatten().collect(), }) } } */ #[derive(Clone)] pub struct Certificate(X509); /* impl Certificate { pub fn from_der(buf: &[u8]) -> Result { let cert = X509::from_der(buf)?; Ok(Certificate(cert)) } pub fn from_pem(buf: &[u8]) -> Result { let cert = X509::from_pem(buf)?; Ok(Certificate(cert)) } pub fn to_der(&self) -> Result, Error> { let der = self.0.to_der()?; Ok(der) } } */ pub struct MidHandshakeTlsStream(MidHandshakeSslStream); impl fmt::Debug for MidHandshakeTlsStream where S: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } /* impl MidHandshakeTlsStream { pub fn get_ref(&self) -> &S { self.0.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } impl MidHandshakeTlsStream where S: io::Read + io::Write, { pub fn handshake(self) -> Result, HandshakeError> { match self.0.handshake() { Ok(s) => Ok(TlsStream(s)), Err(e) => Err(e.into()), } } } */ pub enum HandshakeError { Failure(Error), WouldBlock(MidHandshakeTlsStream), } impl From> for HandshakeError { fn from(e: ssl::HandshakeError) -> HandshakeError { match e { ssl::HandshakeError::SetupFailure(e) => HandshakeError::Failure(e.into()), ssl::HandshakeError::Failure(e) => { let v = e.ssl().verify_result(); HandshakeError::Failure(Error::Ssl(e.into_error(), v)) } ssl::HandshakeError::WouldBlock(s) => { HandshakeError::WouldBlock(MidHandshakeTlsStream(s)) } } } } impl From for HandshakeError { fn from(e: ErrorStack) -> HandshakeError { HandshakeError::Failure(e.into()) } } #[derive(Clone)] pub struct TlsConnector { connector: SslConnector, use_sni: bool, accept_invalid_hostnames: bool, accept_invalid_certs: bool, } impl TlsConnector { pub fn new(builder: &TlsConnectorBuilder) -> Result { let mut connector = SslConnector::builder(SslMethod::tls())?; #[cfg(feature = "openssl-probe")] { let probe = openssl_probe::probe(); connector .load_verify_locations(probe.cert_file.as_deref(), probe.cert_dir.as_deref())?; } if let Some(ref identity) = builder.identity { connector.set_certificate(&identity.0.cert)?; connector.set_private_key(&identity.0.pkey)?; for cert in identity.0.chain.iter().rev() { connector.add_extra_chain_cert(cert.to_owned())?; } } supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?; if builder.disable_built_in_roots { connector.set_cert_store(X509StoreBuilder::new()?.build()); } for cert in &builder.root_certificates { if let Err(err) = connector.cert_store_mut().add_cert((cert.0).0.clone()) { debug!("add_cert error: {:?}", err); } } #[cfg(target_os = "android")] load_android_root_certs(&mut connector)?; Ok(TlsConnector { connector: connector.build(), use_sni: builder.use_sni, accept_invalid_hostnames: builder.accept_invalid_hostnames, accept_invalid_certs: builder.accept_invalid_certs, }) } pub fn connect(&self, domain: &str, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let mut ssl = self .connector .configure()? .use_server_name_indication(self.use_sni) .verify_hostname(!self.accept_invalid_hostnames); if self.accept_invalid_certs { ssl.set_verify(SslVerifyMode::NONE); } let s = ssl.connect(domain, stream)?; Ok(TlsStream(s)) } } impl fmt::Debug for TlsConnector { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("TlsConnector") // n.b. SslConnector is a newtype on SslContext which implements a noop Debug so it's omitted .field("use_sni", &self.use_sni) .field("accept_invalid_hostnames", &self.accept_invalid_hostnames) .field("accept_invalid_certs", &self.accept_invalid_certs) .finish() } } #[derive(Clone)] pub struct TlsAcceptor(SslAcceptor); /* impl TlsAcceptor { pub fn new(builder: &TlsAcceptorBuilder) -> Result { let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; acceptor.set_private_key(&builder.identity.0.pkey)?; acceptor.set_certificate(&builder.identity.0.cert)?; for cert in builder.identity.0.chain.iter().rev() { acceptor.add_extra_chain_cert(cert.to_owned())?; } supported_protocols(builder.min_protocol, builder.max_protocol, &mut acceptor)?; Ok(TlsAcceptor(acceptor.build())) } pub fn accept(&self, stream: S) -> Result, HandshakeError> where S: io::Read + io::Write, { let s = self.0.accept(stream)?; Ok(TlsStream(s)) } } */ pub struct TlsStream(ssl::SslStream); impl fmt::Debug for TlsStream { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, fmt) } } impl TlsStream { pub fn get_ref(&self) -> &S { self.0.get_ref() } pub fn get_mut(&mut self) -> &mut S { self.0.get_mut() } } /* impl TlsStream { pub fn buffered_read_size(&self) -> Result { Ok(self.0.ssl().pending()) } pub fn peer_certificate(&self) -> Result, Error> { Ok(self.0.ssl().peer_certificate().map(Certificate)) } pub fn tls_server_end_point(&self) -> Result>, Error> { let cert = if self.0.ssl().is_server() { self.0.ssl().certificate().map(|x| x.to_owned()) } else { self.0.ssl().peer_certificate() }; let cert = match cert { Some(cert) => cert, None => return Ok(None), }; let algo_nid = cert.signature_algorithm().object().nid(); let signature_algorithms = match algo_nid.signature_algorithms() { Some(algs) => algs, None => return Ok(None), }; let md = match signature_algorithms.digest { Nid::MD5 | Nid::SHA1 => MessageDigest::sha256(), nid => match MessageDigest::from_nid(nid) { Some(md) => md, None => return Ok(None), }, }; let digest = cert.digest(md)?; Ok(Some(digest.to_vec())) } pub fn shutdown(&mut self) -> io::Result<()> { match self.0.shutdown() { Ok(_) => Ok(()), Err(ref e) if e.code() == ssl::ErrorCode::ZERO_RETURN => Ok(()), Err(e) => Err(e .into_io_error() .unwrap_or_else(|e| io::Error::new(io::ErrorKind::Other, e))), } } } */ impl io::Read for TlsStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl io::Write for TlsStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } fn flush(&mut self) -> io::Result<()> { self.0.flush() } } minreq-2.13.4/src/proxy.rs000064400000000000000000000121341046102023000135250ustar 00000000000000use crate::error::Error; use crate::ParsedRequest; use base64::engine::general_purpose::STANDARD; use base64::engine::Engine; /// Kind of proxy connection (Basic, Digest, etc) #[derive(Clone, PartialEq, Eq, Debug)] pub(crate) enum ProxyKind { Basic, } /// Proxy configuration. Only HTTP CONNECT proxies are supported (no SOCKS or /// HTTPS). /// /// When credentials are provided, the Basic authentication type is used for /// Proxy-Authorization. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Proxy { pub(crate) server: String, pub(crate) port: u32, pub(crate) user: Option, pub(crate) password: Option, pub(crate) kind: ProxyKind, } impl Proxy { fn parse_creds(creds: &str) -> (Option, Option) { if let Some((user, pass)) = split_once(creds, ":") { (Some(user.to_string()), Some(pass.to_string())) } else { (Some(creds.to_string()), None) } } fn parse_address(host: &str) -> Result<(String, Option), Error> { if let Some((host, port)) = split_once(host, ":") { let port = port.parse::().map_err(|_| Error::BadProxy)?; Ok((host.to_string(), Some(port))) } else { Ok((host.to_string(), None)) } } /// Creates a new Proxy configuration. /// /// Supported proxy format is: /// /// ```plaintext /// [http://][user[:password]@]host[:port] /// ``` /// /// The default port is 8080, to be changed to 1080 in minreq 3.0. /// /// # Example /// /// ``` /// let proxy = minreq::Proxy::new("user:password@localhost:1080").unwrap(); /// let request = minreq::post("http://example.com").with_proxy(proxy); /// ``` /// pub fn new>(proxy: S) -> Result { let proxy = proxy.as_ref(); let authority = if let Some((proto, auth)) = split_once(proxy, "://") { if proto != "http" { return Err(Error::BadProxy); } auth } else { proxy }; let ((user, password), host) = if let Some((userinfo, host)) = rsplit_once(authority, "@") { (Proxy::parse_creds(userinfo), host) } else { ((None, None), authority) }; let (host, port) = Proxy::parse_address(host)?; Ok(Self { server: host, user, password, port: port.unwrap_or(8080), kind: ProxyKind::Basic, }) } pub(crate) fn connect(&self, proxied_req: &ParsedRequest) -> String { let authorization = if let Some(user) = &self.user { match self.kind { ProxyKind::Basic => { let creds = if let Some(password) = &self.password { STANDARD.encode(format!("{}:{}", user, password)) } else { STANDARD.encode(user) }; format!("Proxy-Authorization: Basic {}\r\n", creds) } } } else { String::new() }; let host = &proxied_req.url.host; let port = proxied_req.url.port.port(); format!( "CONNECT {}:{} HTTP/1.1\r\n{}\r\n", host, port, authorization ) } pub(crate) fn verify_response(response: &[u8]) -> Result<(), Error> { let response_string = String::from_utf8_lossy(response); let top_line = response_string.lines().next().ok_or(Error::ProxyConnect)?; let status_code = top_line.split_whitespace().nth(1).ok_or(Error::BadProxy)?; match status_code { "200" => Ok(()), "401" | "407" => Err(Error::InvalidProxyCreds), _ => Err(Error::BadProxy), } } } #[allow(clippy::manual_split_once)] /// Replacement for str::split_once until MSRV is at least 1.52.0. fn split_once<'a>(string: &'a str, pattern: &str) -> Option<(&'a str, &'a str)> { let mut parts = string.splitn(2, pattern); let first = parts.next()?; let second = parts.next()?; Some((first, second)) } #[allow(clippy::manual_split_once)] /// Replacement for str::rsplit_once until MSRV is at least 1.52.0. fn rsplit_once<'a>(string: &'a str, pattern: &str) -> Option<(&'a str, &'a str)> { let mut parts = string.rsplitn(2, pattern); let second = parts.next()?; let first = parts.next()?; Some((first, second)) } #[cfg(test)] mod tests { use super::Proxy; #[test] fn parse_proxy() { let proxy = Proxy::new("user:p@ssw0rd@localhost:9999").unwrap(); assert_eq!(proxy.user, Some(String::from("user"))); assert_eq!(proxy.password, Some(String::from("p@ssw0rd"))); assert_eq!(proxy.server, String::from("localhost")); assert_eq!(proxy.port, 9999); } #[test] fn parse_regular_proxy_with_protocol() { let proxy = Proxy::new("http://localhost:1080").unwrap(); assert_eq!(proxy.user, None); assert_eq!(proxy.password, None); assert_eq!(proxy.server, String::from("localhost")); assert_eq!(proxy.port, 1080); } } minreq-2.13.4/src/request.rs000064400000000000000000000512651046102023000140440ustar 00000000000000use crate::connection::Connection; use crate::http_url::{HttpUrl, Port}; #[cfg(feature = "proxy")] use crate::proxy::Proxy; use crate::{Error, Response, ResponseLazy}; use std::collections::HashMap; use std::fmt; use std::fmt::Write; /// A URL type for requests. pub type URL = String; /// An HTTP request method. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Method { /// The GET method Get, /// The HEAD method Head, /// The POST method Post, /// The PUT method Put, /// The DELETE method Delete, /// The CONNECT method Connect, /// The OPTIONS method Options, /// The TRACE method Trace, /// The PATCH method Patch, /// A custom method, use with care: the string will be embedded in /// your request as-is. Custom(String), } impl fmt::Display for Method { /// Formats the Method to the form in the HTTP request, /// ie. Method::Get -> "GET", Method::Post -> "POST", etc. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Method::Get => write!(f, "GET"), Method::Head => write!(f, "HEAD"), Method::Post => write!(f, "POST"), Method::Put => write!(f, "PUT"), Method::Delete => write!(f, "DELETE"), Method::Connect => write!(f, "CONNECT"), Method::Options => write!(f, "OPTIONS"), Method::Trace => write!(f, "TRACE"), Method::Patch => write!(f, "PATCH"), Method::Custom(ref s) => write!(f, "{}", s), } } } /// An HTTP request. /// /// Generally created by the [`minreq::get`](fn.get.html)-style /// functions, corresponding to the HTTP method we want to use. /// /// # Example /// /// ``` /// let request = minreq::post("http://example.com"); /// ``` /// /// After creating the request, you would generally call /// [`send`](struct.Request.html#method.send) or /// [`send_lazy`](struct.Request.html#method.send_lazy) on it, as it /// doesn't do much on its own. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Request { pub(crate) method: Method, url: URL, params: String, headers: HashMap, body: Option>, pub(crate) timeout: Option, pub(crate) max_headers_size: Option, pub(crate) max_status_line_len: Option, max_redirects: usize, #[cfg(feature = "proxy")] pub(crate) proxy: Option, } impl Request { /// Creates a new HTTP `Request`. /// /// This is only the request's data, it is not sent yet. For /// sending the request, see [`send`](struct.Request.html#method.send). /// /// If `urlencoding` is not enabled, it is the responsibility of the /// user to ensure there are no illegal characters in the URL. /// /// If `urlencoding` is enabled, the resource part of the URL will be /// encoded. Any URL special characters (e.g. &, #, =) are not encoded /// as they are assumed to be meaningful parameters etc. pub fn new>(method: Method, url: T) -> Request { Request { method, url: url.into(), params: String::new(), headers: HashMap::new(), body: None, timeout: None, max_headers_size: None, max_status_line_len: None, max_redirects: 100, #[cfg(feature = "proxy")] proxy: None, } } /// Add headers to the request this is called on. Use this /// function to add headers to your requests. pub fn with_headers(mut self, headers: T) -> Request where T: IntoIterator, K: Into, V: Into, { let headers = headers.into_iter().map(|(k, v)| (k.into(), v.into())); self.headers.extend(headers); self } /// Adds a header to the request this is called on. Use this /// function to add headers to your requests. pub fn with_header, U: Into>(mut self, key: T, value: U) -> Request { self.headers.insert(key.into(), value.into()); self } /// Sets the request body. pub fn with_body>>(mut self, body: T) -> Request { let body = body.into(); let body_length = body.len(); self.body = Some(body); self.with_header("Content-Length", format!("{}", body_length)) } /// Adds given key and value as query parameter to request url /// (resource). /// /// If `urlencoding` is not enabled, it is the responsibility /// of the user to ensure there are no illegal characters in the /// key or value. /// /// If `urlencoding` is enabled, the key and value are both encoded. pub fn with_param, U: Into>(mut self, key: T, value: U) -> Request { let key = key.into(); #[cfg(feature = "urlencoding")] let key = urlencoding::encode(&key); let value = value.into(); #[cfg(feature = "urlencoding")] let value = urlencoding::encode(&value); if !self.params.is_empty() { self.params.push('&'); } self.params.push_str(&key); self.params.push('='); self.params.push_str(&value); self } /// Converts given argument to JSON and sets it as body. /// /// # Errors /// /// Returns /// [`SerdeJsonError`](enum.Error.html#variant.SerdeJsonError) if /// Serde runs into a problem when converting `body` into a /// string. #[cfg(feature = "json-using-serde")] pub fn with_json(mut self, body: &T) -> Result { self.headers.insert( "Content-Type".to_string(), "application/json; charset=UTF-8".to_string(), ); match serde_json::to_string(&body) { Ok(json) => Ok(self.with_body(json)), Err(err) => Err(Error::SerdeJsonError(err)), } } /// Sets the request timeout in seconds. pub fn with_timeout(mut self, timeout: u64) -> Request { self.timeout = Some(timeout); self } /// Sets the max redirects we follow until giving up. 100 by /// default. /// /// Warning: setting this to a very high number, such as 1000, may /// cause a stack overflow if that many redirects are followed. If /// you have a use for so many redirects that the stack overflow /// becomes a problem, please open an issue. pub fn with_max_redirects(mut self, max_redirects: usize) -> Request { self.max_redirects = max_redirects; self } /// Sets the maximum size of all the headers this request will /// accept. /// /// If this limit is passed, the request will close the connection /// and return an [Error::HeadersOverflow] error. /// /// The maximum length is counted in bytes, including line-endings /// and other whitespace. Both normal and trailing headers count /// towards this cap. /// /// `None` disables the cap, and may cause the program to use any /// amount of memory if the server responds with a lot of headers /// (or an infinite amount). In minreq versions 2.x.x, the default /// is None, so setting this manually is recommended when talking /// to untrusted servers. pub fn with_max_headers_size>>(mut self, max_headers_size: S) -> Request { self.max_headers_size = max_headers_size.into(); self } /// Sets the maximum length of the status line this request will /// accept. /// /// If this limit is passed, the request will close the connection /// and return an [Error::StatusLineOverflow] error. /// /// The maximum length is counted in bytes, including the /// line-ending `\r\n`. /// /// `None` disables the cap, and may cause the program to use any /// amount of memory if the server responds with a long (or /// infinite) status line. In minreq versions 2.x.x, the default /// is None, so setting this manually is recommended when talking /// to untrusted servers. pub fn with_max_status_line_length>>( mut self, max_status_line_len: S, ) -> Request { self.max_status_line_len = max_status_line_len.into(); self } /// Sets the proxy to use. #[cfg(feature = "proxy")] pub fn with_proxy(mut self, proxy: Proxy) -> Request { self.proxy = Some(proxy); self } /// Sends this request to the host. /// /// # Errors /// /// Returns `Err` if we run into an error while sending the /// request, or receiving/parsing the response. The specific error /// is described in the `Err`, and it can be any /// [`minreq::Error`](enum.Error.html) except /// [`SerdeJsonError`](enum.Error.html#variant.SerdeJsonError) and /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody). pub fn send(self) -> Result { let parsed_request = ParsedRequest::new(self)?; if parsed_request.url.https { #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] { let is_head = parsed_request.config.method == Method::Head; let response = Connection::new(parsed_request).send_https()?; Response::create(response, is_head) } #[cfg(not(any(feature = "rustls", feature = "openssl", feature = "native-tls")))] { Err(Error::HttpsFeatureNotEnabled) } } else { let is_head = parsed_request.config.method == Method::Head; let response = Connection::new(parsed_request).send()?; Response::create(response, is_head) } } /// Sends this request to the host, loaded lazily. /// /// # Errors /// /// See [`send`](struct.Request.html#method.send). pub fn send_lazy(self) -> Result { let parsed_request = ParsedRequest::new(self)?; if parsed_request.url.https { #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] { Connection::new(parsed_request).send_https() } #[cfg(not(any(feature = "rustls", feature = "openssl", feature = "native-tls")))] { Err(Error::HttpsFeatureNotEnabled) } } else { Connection::new(parsed_request).send() } } } pub(crate) struct ParsedRequest { pub(crate) url: HttpUrl, pub(crate) redirects: Vec, pub(crate) config: Request, } impl ParsedRequest { #[allow(unused_mut)] fn new(mut config: Request) -> Result { let mut url = HttpUrl::parse(&config.url, None)?; if !config.params.is_empty() { if url.path_and_query.contains('?') { url.path_and_query.push('&'); } else { url.path_and_query.push('?'); } url.path_and_query.push_str(&config.params); } #[cfg(feature = "proxy")] // Set default proxy from environment variables // // Curl documentation: https://everything.curl.dev/usingcurl/proxies/env // // Accepted variables are `http_proxy`, `https_proxy`, `HTTPS_PROXY`, `ALL_PROXY` // // Note: https://everything.curl.dev/usingcurl/proxies/env#http_proxy-in-lower-case-only if config.proxy.is_none() { // Set HTTP proxies if request's protocol is HTTPS and they're given if url.https { if let Ok(proxy) = std::env::var("https_proxy").map_err(|_| std::env::var("HTTPS_PROXY")) { if let Ok(proxy) = Proxy::new(proxy) { config.proxy = Some(proxy); } } } // Set HTTP proxies if request's protocol is HTTP and they're given else if let Ok(proxy) = std::env::var("http_proxy") { if let Ok(proxy) = Proxy::new(proxy) { config.proxy = Some(proxy); } } // Set any given proxies if neither of HTTP/HTTPS were given else if let Ok(proxy) = std::env::var("all_proxy").map_err(|_| std::env::var("ALL_PROXY")) { if let Ok(proxy) = Proxy::new(proxy) { config.proxy = Some(proxy); } } } Ok(ParsedRequest { url, redirects: Vec::new(), config, }) } fn get_http_head(&self) -> String { let mut http = String::with_capacity(32); // NOTE: As of 2.10.0, the fragment is intentionally left out of the request, based on: // - [RFC 3986 section 3.5](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5): // "...the fragment identifier is not used in the scheme-specific // processing of a URI; instead, the fragment identifier is separated // from the rest of the URI prior to a dereference..." // - [RFC 7231 section 9.5](https://datatracker.ietf.org/doc/html/rfc7231#section-9.5): // "Although fragment identifiers used within URI references are not // sent in requests..." // Add the request line and the "Host" header write!( http, "{} {} HTTP/1.1\r\nHost: {}", self.config.method, self.url.path_and_query, self.url.host ) .unwrap(); if let Port::Explicit(port) = self.url.port { write!(http, ":{}", port).unwrap(); } http += "\r\n"; // Add other headers for (k, v) in &self.config.headers { write!(http, "{}: {}\r\n", k, v).unwrap(); } if self.config.method == Method::Post || self.config.method == Method::Put || self.config.method == Method::Patch { let not_length = |key: &String| { let key = key.to_lowercase(); key != "content-length" && key != "transfer-encoding" }; if self.config.headers.keys().all(not_length) { // A user agent SHOULD send a Content-Length in a request message when no Transfer-Encoding // is sent and the request method defines a meaning for an enclosed payload body. // refer: https://tools.ietf.org/html/rfc7230#section-3.3.2 // A client MUST NOT send a message body in a TRACE request. // refer: https://tools.ietf.org/html/rfc7231#section-4.3.8 // similar line found for GET, HEAD, CONNECT and DELETE. http += "Content-Length: 0\r\n"; } } http += "\r\n"; http } /// Returns the HTTP request as bytes, ready to be sent to /// the server. pub(crate) fn as_bytes(&self) -> Vec { let mut head = self.get_http_head().into_bytes(); if let Some(body) = &self.config.body { head.extend(body); } head } /// Returns the redirected version of this Request, unless an /// infinite redirection loop was detected, or the redirection /// limit was reached. pub(crate) fn redirect_to(&mut self, url: &str) -> Result<(), Error> { if url.contains("://") { let mut url = HttpUrl::parse(url, Some(&self.url)).map_err(|_| { // TODO: Uncomment this for 3.0 // Error::InvalidProtocolInRedirect Error::IoError(std::io::Error::new( std::io::ErrorKind::Other, "was redirected to an absolute url with an invalid protocol", )) })?; std::mem::swap(&mut url, &mut self.url); self.redirects.push(url); } else { // The url does not have the protocol part, assuming it's // a relative resource. let mut absolute_url = String::new(); self.url.write_base_url_to(&mut absolute_url).unwrap(); absolute_url.push_str(url); let mut url = HttpUrl::parse(&absolute_url, Some(&self.url))?; std::mem::swap(&mut url, &mut self.url); self.redirects.push(url); } if self.redirects.len() > self.config.max_redirects { Err(Error::TooManyRedirections) } else if self .redirects .iter() .any(|redirect_url| redirect_url == &self.url) { Err(Error::InfiniteRedirectionLoop) } else { Ok(()) } } } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Get](enum.Method.html). pub fn get>(url: T) -> Request { Request::new(Method::Get, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Head](enum.Method.html). pub fn head>(url: T) -> Request { Request::new(Method::Head, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Post](enum.Method.html). pub fn post>(url: T) -> Request { Request::new(Method::Post, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Put](enum.Method.html). pub fn put>(url: T) -> Request { Request::new(Method::Put, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Delete](enum.Method.html). pub fn delete>(url: T) -> Request { Request::new(Method::Delete, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Connect](enum.Method.html). pub fn connect>(url: T) -> Request { Request::new(Method::Connect, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Options](enum.Method.html). pub fn options>(url: T) -> Request { Request::new(Method::Options, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Trace](enum.Method.html). pub fn trace>(url: T) -> Request { Request::new(Method::Trace, url) } /// Alias for [Request::new](struct.Request.html#method.new) with `method` set to /// [Method::Patch](enum.Method.html). pub fn patch>(url: T) -> Request { Request::new(Method::Patch, url) } #[cfg(test)] mod parsing_tests { use std::collections::HashMap; use super::{get, ParsedRequest}; #[test] fn test_headers() { let mut headers = HashMap::new(); headers.insert("foo".to_string(), "bar".to_string()); headers.insert("foo".to_string(), "baz".to_string()); let req = get("http://www.example.org/test/res").with_headers(headers.clone()); assert_eq!(req.headers, headers); } #[test] fn test_multiple_params() { let req = get("http://www.example.org/test/res") .with_param("foo", "bar") .with_param("asd", "qwe"); let req = ParsedRequest::new(req).unwrap(); assert_eq!(&req.url.path_and_query, "/test/res?foo=bar&asd=qwe"); } #[test] fn test_domain() { let req = get("http://www.example.org/test/res").with_param("foo", "bar"); let req = ParsedRequest::new(req).unwrap(); assert_eq!(&req.url.host, "www.example.org"); } #[test] fn test_protocol() { let req = ParsedRequest::new(get("http://www.example.org/").with_param("foo", "bar")).unwrap(); assert!(!req.url.https); let req = ParsedRequest::new(get("https://www.example.org/").with_param("foo", "bar")).unwrap(); assert!(req.url.https); } } #[cfg(all(test, feature = "urlencoding"))] mod encoding_tests { use super::{get, ParsedRequest}; #[test] fn test_with_param() { let req = get("http://www.example.org").with_param("foo", "bar"); let req = ParsedRequest::new(req).unwrap(); assert_eq!(&req.url.path_and_query, "/?foo=bar"); let req = get("http://www.example.org").with_param("ówò", "what's this? 👀"); let req = ParsedRequest::new(req).unwrap(); assert_eq!( &req.url.path_and_query, "/?%C3%B3w%C3%B2=what%27s%20this%3F%20%F0%9F%91%80" ); } #[test] fn test_on_creation() { let req = ParsedRequest::new(get("http://www.example.org/?foo=bar#baz")).unwrap(); assert_eq!(&req.url.path_and_query, "/?foo=bar"); let req = ParsedRequest::new(get("http://www.example.org/?ówò=what's this? 👀")).unwrap(); assert_eq!( &req.url.path_and_query, "/?%C3%B3w%C3%B2=what%27s%20this?%20%F0%9F%91%80" ); } } minreq-2.13.4/src/response.rs000064400000000000000000000456161046102023000142150ustar 00000000000000use crate::{connection::HttpStream, Error}; use std::collections::HashMap; use std::io::{self, BufReader, Bytes, Read}; use std::str; const BACKING_READ_BUFFER_LENGTH: usize = 16 * 1024; const MAX_CONTENT_LENGTH: usize = 16 * 1024; /// An HTTP response. /// /// Returned by [`Request::send`](struct.Request.html#method.send). /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), minreq::Error> { /// let response = minreq::get("http://example.com").send()?; /// println!("{}", response.as_str()?); /// # Ok(()) } /// ``` #[derive(Clone, PartialEq, Eq, Debug)] pub struct Response { /// The status code of the response, eg. 404. pub status_code: i32, /// The reason phrase of the response, eg. "Not Found". pub reason_phrase: String, /// The headers of the response. The header field names (the /// keys) are all lowercase. pub headers: HashMap, /// The URL of the resource returned in this response. May differ from the /// request URL if it was redirected or typo corrections were applied (e.g. /// would be corrected to /// ). pub url: String, body: Vec, } impl Response { pub(crate) fn create(mut parent: ResponseLazy, is_head: bool) -> Result { let mut body = Vec::new(); if !is_head && parent.status_code != 204 && parent.status_code != 304 { for byte in &mut parent { let (byte, length) = byte?; body.reserve(length); body.push(byte); } } let ResponseLazy { status_code, reason_phrase, headers, url, .. } = parent; Ok(Response { status_code, reason_phrase, headers, url, body, }) } /// Returns the body as an `&str`. /// /// # Errors /// /// Returns /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody) /// if the body is not UTF-8, with a description as to why the /// provided slice is not UTF-8. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// # let url = "http://example.org/"; /// let response = minreq::get(url).send()?; /// println!("{}", response.as_str()?); /// # Ok(()) /// # } /// ``` pub fn as_str(&self) -> Result<&str, Error> { match str::from_utf8(&self.body) { Ok(s) => Ok(s), Err(err) => Err(Error::InvalidUtf8InBody(err)), } } /// Returns a reference to the contained bytes of the body. If you /// want the `Vec` itself, use /// [`into_bytes()`](#method.into_bytes) instead. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// # let url = "http://example.org/"; /// let response = minreq::get(url).send()?; /// println!("{:?}", response.as_bytes()); /// # Ok(()) /// # } /// ``` pub fn as_bytes(&self) -> &[u8] { &self.body } /// Turns the `Response` into the inner `Vec`, the bytes that /// make up the response's body. If you just need a `&[u8]`, use /// [`as_bytes()`](#method.as_bytes) instead. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), Box> { /// # let url = "http://example.org/"; /// let response = minreq::get(url).send()?; /// println!("{:?}", response.into_bytes()); /// // This would error, as into_bytes consumes the Response: /// // let x = response.status_code; /// # Ok(()) /// # } /// ``` pub fn into_bytes(self) -> Vec { self.body } /// Converts JSON body to a `struct` using Serde. /// /// # Errors /// /// Returns /// [`SerdeJsonError`](enum.Error.html#variant.SerdeJsonError) if /// Serde runs into a problem, or /// [`InvalidUtf8InBody`](enum.Error.html#variant.InvalidUtf8InBody) /// if the body is not UTF-8. /// /// # Example /// In case compiler cannot figure out return type you might need to declare it explicitly: /// /// ```no_run /// use serde_json::Value; /// /// # fn main() -> Result<(), minreq::Error> { /// # let url_to_json_resource = "http://example.org/resource.json"; /// // Value could be any type that implements Deserialize! /// let user = minreq::get(url_to_json_resource).send()?.json::()?; /// println!("User name is '{}'", user["name"]); /// # Ok(()) /// # } /// ``` #[cfg(feature = "json-using-serde")] pub fn json<'a, T>(&'a self) -> Result where T: serde::de::Deserialize<'a>, { let str = match self.as_str() { Ok(str) => str, Err(_) => return Err(Error::InvalidUtf8InResponse), }; match serde_json::from_str(str) { Ok(json) => Ok(json), Err(err) => Err(Error::SerdeJsonError(err)), } } } /// An HTTP response, which is loaded lazily. /// /// In comparison to [`Response`](struct.Response.html), this is /// returned from /// [`send_lazy()`](struct.Request.html#method.send_lazy), where as /// [`Response`](struct.Response.html) is returned from /// [`send()`](struct.Request.html#method.send). /// /// In practice, "lazy loading" means that the bytes are only loaded /// as you iterate through them. The bytes are provided in the form of /// a `Result<(u8, usize), minreq::Error>`, as the reading operation /// can fail in various ways. The `u8` is the actual byte that was /// read, and `usize` is how many bytes we are expecting to read in /// the future (including this byte). Note, however, that the `usize` /// can change, particularly when the `Transfer-Encoding` is /// `chunked`: then it will reflect how many bytes are left of the /// current chunk. The expected size is capped at 16 KiB to avoid /// server-side DoS attacks targeted at clients accidentally reserving /// too much memory. /// /// # Example /// ```no_run /// // This is how the normal Response works behind the scenes, and /// // how you might use ResponseLazy. /// # fn main() -> Result<(), minreq::Error> { /// let response = minreq::get("http://example.com").send_lazy()?; /// let mut vec = Vec::new(); /// for result in response { /// let (byte, length) = result?; /// vec.reserve(length); /// vec.push(byte); /// } /// # Ok(()) /// # } /// /// ``` pub struct ResponseLazy { /// The status code of the response, eg. 404. pub status_code: i32, /// The reason phrase of the response, eg. "Not Found". pub reason_phrase: String, /// The headers of the response. The header field names (the /// keys) are all lowercase. pub headers: HashMap, /// The URL of the resource returned in this response. May differ from the /// request URL if it was redirected or typo corrections were applied (e.g. /// would be corrected to /// ). pub url: String, stream: HttpStreamBytes, state: HttpStreamState, max_trailing_headers_size: Option, } type HttpStreamBytes = Bytes>; impl ResponseLazy { pub(crate) fn from_stream( stream: HttpStream, max_headers_size: Option, max_status_line_len: Option, ) -> Result { let mut stream = BufReader::with_capacity(BACKING_READ_BUFFER_LENGTH, stream).bytes(); let ResponseMetadata { status_code, reason_phrase, headers, state, max_trailing_headers_size, } = read_metadata(&mut stream, max_headers_size, max_status_line_len)?; Ok(ResponseLazy { status_code, reason_phrase, headers, url: String::new(), stream, state, max_trailing_headers_size, }) } } impl Iterator for ResponseLazy { type Item = Result<(u8, usize), Error>; fn next(&mut self) -> Option { use HttpStreamState::*; match self.state { EndOnClose => read_until_closed(&mut self.stream), ContentLength(ref mut length) => read_with_content_length(&mut self.stream, length), Chunked(ref mut expecting_chunks, ref mut length, ref mut content_length) => { read_chunked( &mut self.stream, &mut self.headers, expecting_chunks, length, content_length, self.max_trailing_headers_size, ) } } } } impl Read for ResponseLazy { fn read(&mut self, buf: &mut [u8]) -> io::Result { let mut index = 0; for res in self { // there is no use for the estimated length in the read implementation // so it is ignored. let (byte, _) = res.map_err(|e| match e { Error::IoError(e) => e, _ => io::Error::new(io::ErrorKind::Other, e), })?; buf[index] = byte; index += 1; // if the buffer is full, it should stop reading if index >= buf.len() { break; } } // index of the next byte is the number of bytes thats have been read Ok(index) } } fn read_until_closed(bytes: &mut HttpStreamBytes) -> Option<::Item> { if let Some(byte) = bytes.next() { match byte { Ok(byte) => Some(Ok((byte, 1))), Err(err) => Some(Err(Error::IoError(err))), } } else { None } } fn read_with_content_length( bytes: &mut HttpStreamBytes, content_length: &mut usize, ) -> Option<::Item> { if *content_length > 0 { *content_length -= 1; if let Some(byte) = bytes.next() { match byte { // Cap Content-Length to 16KiB, to avoid out-of-memory issues. Ok(byte) => return Some(Ok((byte, (*content_length).min(MAX_CONTENT_LENGTH) + 1))), Err(err) => return Some(Err(Error::IoError(err))), } } } None } fn read_trailers( bytes: &mut HttpStreamBytes, headers: &mut HashMap, mut max_headers_size: Option, ) -> Result<(), Error> { loop { let trailer_line = read_line(bytes, max_headers_size, Error::HeadersOverflow)?; if let Some(ref mut max_headers_size) = max_headers_size { *max_headers_size -= trailer_line.len() + 2; } if let Some((header, value)) = parse_header(trailer_line) { headers.insert(header, value); } else { break; } } Ok(()) } fn read_chunked( bytes: &mut HttpStreamBytes, headers: &mut HashMap, expecting_more_chunks: &mut bool, chunk_length: &mut usize, content_length: &mut usize, max_trailing_headers_size: Option, ) -> Option<::Item> { if !*expecting_more_chunks && *chunk_length == 0 { return None; } if *chunk_length == 0 { // Max length of the chunk length line is 1KB: not too long to // take up much memory, long enough to tolerate some chunk // extensions (which are ignored). // Get the size of the next chunk let length_line = match read_line(bytes, Some(1024), Error::MalformedChunkLength) { Ok(line) => line, Err(err) => return Some(Err(err)), }; // Note: the trim() and check for empty lines shouldn't be // needed according to the RFC, but we might as well, it's a // small change and it fixes a few servers. let incoming_length = if length_line.is_empty() { 0 } else { let length = if let Some(i) = length_line.find(';') { length_line[..i].trim() } else { length_line.trim() }; match usize::from_str_radix(length, 16) { Ok(length) => length, Err(_) => return Some(Err(Error::MalformedChunkLength)), } }; if incoming_length == 0 { if let Err(err) = read_trailers(bytes, headers, max_trailing_headers_size) { return Some(Err(err)); } *expecting_more_chunks = false; headers.insert("content-length".to_string(), (*content_length).to_string()); headers.remove("transfer-encoding"); return None; } *chunk_length = incoming_length; *content_length += incoming_length; } if *chunk_length > 0 { *chunk_length -= 1; if let Some(byte) = bytes.next() { match byte { Ok(byte) => { // If we're at the end of the chunk... if *chunk_length == 0 { //...read the trailing \r\n of the chunk, and // possibly return an error instead. // TODO: Maybe this could be written in a way // that doesn't discard the last ok byte if // the \r\n reading fails? if let Err(err) = read_line(bytes, Some(2), Error::MalformedChunkEnd) { return Some(Err(err)); } } return Some(Ok((byte, (*chunk_length).min(MAX_CONTENT_LENGTH) + 1))); } Err(err) => return Some(Err(Error::IoError(err))), } } } None } enum HttpStreamState { // No Content-Length, and Transfer-Encoding != chunked, so we just // read unti lthe server closes the connection (this should be the // fallback, if I read the rfc right). EndOnClose, // Content-Length was specified, read that amount of bytes ContentLength(usize), // Transfer-Encoding == chunked, so we need to save two pieces of // information: are we expecting more chunks, how much is there // left of the current chunk, and how much have we read? The last // number is needed in order to provide an accurate Content-Length // header after loading all the bytes. Chunked(bool, usize, usize), } // This struct is just used in the Response and ResponseLazy // constructors, but not in their structs, for api-cleanliness // reasons. (Eg. response.status_code is much cleaner than // response.meta.status_code or similar.) struct ResponseMetadata { status_code: i32, reason_phrase: String, headers: HashMap, state: HttpStreamState, max_trailing_headers_size: Option, } fn read_metadata( stream: &mut HttpStreamBytes, mut max_headers_size: Option, max_status_line_len: Option, ) -> Result { let line = read_line(stream, max_status_line_len, Error::StatusLineOverflow)?; let (status_code, reason_phrase) = parse_status_line(&line); let mut headers = HashMap::new(); loop { let line = read_line(stream, max_headers_size, Error::HeadersOverflow)?; if line.is_empty() { // Body starts here break; } if let Some(ref mut max_headers_size) = max_headers_size { *max_headers_size -= line.len() + 2; } if let Some(header) = parse_header(line) { headers.insert(header.0, header.1); } } let mut chunked = false; let mut content_length = None; for (header, value) in &headers { // Handle the Transfer-Encoding header if header.to_lowercase().trim() == "transfer-encoding" && value.to_lowercase().trim() == "chunked" { chunked = true; } // Handle the Content-Length header if header.to_lowercase().trim() == "content-length" { match str::parse::(value.trim()) { Ok(length) => content_length = Some(length), Err(_) => return Err(Error::MalformedContentLength), } } } let state = if chunked { HttpStreamState::Chunked(true, 0, 0) } else if let Some(length) = content_length { HttpStreamState::ContentLength(length) } else { HttpStreamState::EndOnClose }; Ok(ResponseMetadata { status_code, reason_phrase, headers, state, max_trailing_headers_size: max_headers_size, }) } fn read_line( stream: &mut HttpStreamBytes, max_len: Option, overflow_error: Error, ) -> Result { let mut bytes = Vec::with_capacity(32); for byte in stream { match byte { Ok(byte) => { if let Some(max_len) = max_len { if bytes.len() >= max_len { return Err(overflow_error); } } if byte == b'\n' { if let Some(b'\r') = bytes.last() { bytes.pop(); } break; } else { bytes.push(byte); } } Err(err) => return Err(Error::IoError(err)), } } String::from_utf8(bytes).map_err(|_error| Error::InvalidUtf8InResponse) } fn parse_status_line(line: &str) -> (i32, String) { // sample status line format // HTTP/1.1 200 OK let mut status_code = String::with_capacity(3); let mut reason_phrase = String::with_capacity(2); let mut spaces = 0; for c in line.chars() { if spaces >= 2 { reason_phrase.push(c); } if c == ' ' { spaces += 1; } else if spaces == 1 { status_code.push(c); } } if let Ok(status_code) = status_code.parse::() { return (status_code, reason_phrase); } (503, "Server did not provide a status line".to_string()) } fn parse_header(mut line: String) -> Option<(String, String)> { if let Some(location) = line.find(':') { // Trim the first character of the header if it is a space, // otherwise return everything after the ':'. This should // preserve the behavior in versions <=2.0.1 in most cases // (namely, ones where it was valid), where the first // character after ':' was always cut off. let value = if let Some(sp) = line.get(location + 1..location + 2) { if sp == " " { line[location + 2..].to_string() } else { line[location + 1..].to_string() } } else { line[location + 1..].to_string() }; line.truncate(location); // Headers should be ascii, I'm pretty sure. If not, please open an issue. line.make_ascii_lowercase(); return Some((line, value)); } None } minreq-2.13.4/tests/main.rs000064400000000000000000000140111046102023000136370ustar 00000000000000extern crate minreq; mod setup; use self::setup::*; use std::io; #[test] #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))] fn test_https() { // TODO: Implement this locally. assert_eq!( get_status_code(minreq::get("https://example.com").send()), 200, ); } #[test] #[cfg(feature = "json-using-serde")] fn test_json_using_serde() { const JSON_SRC: &str = r#"{ "str": "Json test", "num": 42 }"#; let original_json: serde_json::Value = serde_json::from_str(JSON_SRC).unwrap(); let response = minreq::post(url("/echo")) .with_json(&original_json) .unwrap() .send() .unwrap(); let actual_json: serde_json::Value = response.json().unwrap(); assert_eq!(&actual_json, &original_json); } #[test] fn test_timeout_too_low() { setup(); let result = minreq::get(url("/slow_a")) .with_body("Q".to_string()) .with_timeout(1) .send(); assert!(result.is_err()); } #[test] fn test_timeout_high_enough() { setup(); let body = get_body( minreq::get(url("/slow_a")) .with_body("Q".to_string()) .with_timeout(3) .send(), ); assert_eq!(body, "j: Q"); } #[test] fn test_headers() { setup(); let body = get_body( minreq::get(url("/header_pong")) .with_header("Ping", "Qwerty") .send(), ); assert_eq!("Qwerty", body); } #[test] fn test_custom_method() { use minreq::Method; setup(); let body = get_body( minreq::Request::new(Method::Custom("GET".to_string()), url("/a")) .with_body("Q") .send(), ); assert_eq!("j: Q", body); } #[test] fn test_get() { setup(); let body = get_body(minreq::get(url("/a")).with_body("Q").send()); assert_eq!(body, "j: Q"); } #[test] fn test_redirect_get() { setup(); let body = get_body(minreq::get(url("/redirect")).with_body("Q").send()); assert_eq!(body, "j: Q"); } #[test] fn test_redirect_post() { setup(); // POSTing to /redirect should return a 303, which means we should // make a GET request to the given location. This test relies on // the fact that the test server only responds to GET requests on // the /a path. let body = get_body(minreq::post(url("/redirect")).with_body("Q").send()); assert_eq!(body, "j: Q"); } #[test] fn test_redirect_with_fragment() { setup(); let original_url = url("/redirect#foo"); let res = minreq::get(original_url).send().unwrap(); // Fragment should stay the same, otherwise redirected assert_eq!(res.url.as_str(), url("/a#foo")); } #[test] fn test_redirect_with_overridden_fragment() { setup(); let original_url = url("/redirect-baz#foo"); let res = minreq::get(original_url).send().unwrap(); // This redirect should provide its own fragment, overriding the initial one assert_eq!(res.url.as_str(), url("/a#baz")); } #[test] fn test_infinite_redirect() { setup(); let body = minreq::get(url("/infiniteredirect")).send(); assert!(body.is_err()); } #[test] fn test_relative_redirect_get() { setup(); let body = get_body(minreq::get(url("/relativeredirect")).with_body("Q").send()); assert_eq!(body, "j: Q"); } #[test] fn test_head() { setup(); assert_eq!(get_status_code(minreq::head(url("/b")).send()), 418); } #[test] fn test_post() { setup(); let body = get_body(minreq::post(url("/c")).with_body("E").send()); assert_eq!(body, "l: E"); } #[test] fn test_put() { setup(); let body = get_body(minreq::put(url("/d")).with_body("R").send()); assert_eq!(body, "m: R"); } #[test] fn test_delete() { setup(); assert_eq!(get_body(minreq::delete(url("/e")).send()), "n: "); } #[test] fn test_trace() { setup(); assert_eq!(get_body(minreq::trace(url("/f")).send()), "o: "); } #[test] fn test_options() { setup(); let body = get_body(minreq::options(url("/g")).with_body("U").send()); assert_eq!(body, "p: U"); } #[test] fn test_connect() { setup(); let body = get_body(minreq::connect(url("/h")).with_body("I").send()); assert_eq!(body, "q: I"); } #[test] fn test_patch() { setup(); let body = get_body(minreq::patch(url("/i")).with_body("O").send()); assert_eq!(body, "r: O"); } #[test] fn tcp_connect_timeout() { let _listener = std::net::TcpListener::bind("127.0.0.1:32162").unwrap(); let resp = minreq::Request::new(minreq::Method::Get, "http://127.0.0.1:32162") .with_timeout(1) .send(); assert!(resp.is_err()); if let Some(minreq::Error::IoError(err)) = resp.err() { assert_eq!(err.kind(), io::ErrorKind::TimedOut); } else { panic!("timeout test request did not return an error"); } } #[test] fn test_header_cap() { setup(); let body = minreq::get(url("/long_header")) .with_max_headers_size(999) .send(); assert!(body.is_err()); assert!(matches!(body.err(), Some(minreq::Error::HeadersOverflow))); let body = minreq::get(url("/long_header")) .with_max_headers_size(1500) .send(); assert!(body.is_ok()); } #[test] fn test_status_line_cap() { setup(); let expected_status_line = "HTTP/1.1 203 Non-Authoritative Information"; let body = minreq::get(url("/long_status_line")) .with_max_status_line_length(expected_status_line.len() + 1) .send(); assert!(body.is_err()); assert!(matches!( body.err(), Some(minreq::Error::StatusLineOverflow) )); let body = minreq::get(url("/long_status_line")) .with_max_status_line_length(expected_status_line.len() + 2) .send(); assert!(body.is_ok()); } #[test] fn test_massive_content_length() { setup(); std::thread::spawn(|| { // If minreq trusts Content-Length, this should crash pretty much straight away. let _ = minreq::get(url("/massive_content_length")).send(); }); std::thread::sleep(std::time::Duration::from_millis(500)); // If it were to crash, it would have at this point. Pass! } minreq-2.13.4/tests/setup.rs000064400000000000000000000211211046102023000140530ustar 00000000000000extern crate minreq; extern crate tiny_http; use self::tiny_http::{Header, Method, Response, Server, StatusCode}; use std::str::FromStr; use std::sync::{Arc, Once}; use std::thread; use std::time::Duration; static INIT: Once = Once::new(); pub fn setup() { INIT.call_once(|| { let server = Arc::new(Server::http("localhost:35562").unwrap()); for _ in 0..4 { let server = server.clone(); thread::spawn(move || loop { let mut request = { if let Ok(request) = server.recv() { request } else { continue; // If .recv() fails, just try again. } }; let mut content = String::new(); request.as_reader().read_to_string(&mut content).ok(); let headers = Vec::from(request.headers()); let url = String::from(request.url().split('#').next().unwrap()); match request.method() { Method::Get if url == "/header_pong" => { for header in headers { if header.field.as_str() == "Ping" { let response = Response::from_string(format!("{}", header.value)); request.respond(response).ok(); return; } } request.respond(Response::from_string("No header!")).ok(); } Method::Get if url == "/slow_a" => { thread::sleep(Duration::from_secs(2)); let response = Response::from_string(format!("j: {}", content)); request.respond(response).ok(); } Method::Get if url == "/a" => { let response = Response::from_string(format!("j: {}", content)); request.respond(response).ok(); } Method::Post if url == "/a" => { let response = Response::from_string("POST to /a is not valid."); request.respond(response).ok(); } Method::Get if url == "/long_header" => { let mut long_header = String::with_capacity(1000); long_header += "Very-Long-Header: "; for _ in 0..1000 - long_header.len() { long_header += "."; } let long_header = Header::from_str(&long_header).unwrap(); let response = Response::empty(200).with_header(long_header); request.respond(response).ok(); } Method::Get if url == "/massive_content_length" => { let status = StatusCode(200); let body = std::io::empty(); let length = 1_000_000_000_000_000; let response = Response::new(status, vec![], body, Some(length), None) .with_chunked_threshold(2 * length); request.respond(response).ok(); } Method::Get if url == "/long_status_line" => { request.respond(Response::empty(203)).ok(); } Method::Get if url == "/redirect-baz" => { let response = Response::empty(301).with_header( Header::from_str("Location: http://localhost:35562/a#baz").unwrap(), ); request.respond(response).ok(); } Method::Get if url == "/redirect" => { let response = Response::empty(301).with_header( Header::from_bytes(&b"Location"[..], &b"http://localhost:35562/a"[..]) .unwrap(), ); request.respond(response).ok(); } Method::Post if url == "/redirect" => { let response = Response::empty(303).with_header( Header::from_bytes(&b"Location"[..], &b"http://localhost:35562/a"[..]) .unwrap(), ); request.respond(response).ok(); } Method::Get if url == "/infiniteredirect" => { let response = Response::empty(301).with_header( Header::from_bytes( &b"Location"[..], &b"http://localhost:35562/redirectpong"[..], ) .unwrap(), ); request.respond(response).ok(); } Method::Get if url == "/redirectpong" => { let response = Response::empty(301).with_header( Header::from_bytes( &b"Location"[..], &b"http://localhost:35562/infiniteredirect"[..], ) .unwrap(), ); request.respond(response).ok(); } Method::Get if url == "/relativeredirect" => { let response = Response::empty(303) .with_header(Header::from_bytes(&b"Location"[..], &b"/a"[..]).unwrap()); request.respond(response).ok(); } Method::Post if url == "/echo" => { request.respond(Response::from_string(content)).ok(); } Method::Head if url == "/b" => { request.respond(Response::empty(418)).ok(); } Method::Post if url == "/c" => { let response = Response::from_string(format!("l: {}", content)); request.respond(response).ok(); } Method::Put if url == "/d" => { let response = Response::from_string(format!("m: {}", content)); request.respond(response).ok(); } Method::Delete if url == "/e" => { let response = Response::from_string(format!("n: {}", content)); request.respond(response).ok(); } Method::Trace if url == "/f" => { let response = Response::from_string(format!("o: {}", content)); request.respond(response).ok(); } Method::Options if url == "/g" => { let response = Response::from_string(format!("p: {}", content)); request.respond(response).ok(); } Method::Connect if url == "/h" => { let response = Response::from_string(format!("q: {}", content)); request.respond(response).ok(); } Method::Patch if url == "/i" => { let response = Response::from_string(format!("r: {}", content)); request.respond(response).ok(); } _ => { request .respond(Response::from_string("Not Found").with_status_code(404)) .ok(); } } }); } }); } pub fn url(req: &str) -> String { format!("http://localhost:35562{}", req) } pub fn get_body(request: Result) -> String { match request { Ok(response) => match response.as_str() { Ok(str) => String::from(str), Err(err) => { println!("\n[ERROR]: {}\n", err); String::new() } }, Err(err) => { println!("\n[ERROR]: {}\n", err); String::new() } } } pub fn get_status_code(request: Result) -> i32 { match request { Ok(response) => response.status_code, Err(err) => { println!("\n[ERROR]: {}\n", err); -1 } } }