chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

1
vendor/reqwest/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"74291d442dbcefcec4747a78fc59d6f048d1c567960f5788af7e36c50ff5d898","Cargo.lock":"1f04a3a0090e97a9eaf83d2d923feafe37c521e0e344df2f89891f2aa068aa48","Cargo.toml":"23c8a0827aaac74059965a182c6873990e75aa550e8079d3b51bb95092871205","Cargo.toml.orig":"a0f5d08c3e29e19701dd75b182e1577eaf2c70f6f1ae46b2df91338888f0b454","LICENSE-APACHE":"751963a8b88c0e3a9f27e98933079fbcf09b9998b2c13ac49c2e4d58444520d6","LICENSE-MIT":"47d4e1803702728e03d30f8a848ae2249ba274bbd9f8443e803f7924d83cd371","README.md":"d34eccc4a6fe2f52486defcc6709985f8edd0e6642cea2269173772aea93f91e","src/async_impl/body.rs":"60948ec5a44c995beb2dd22f6d4ca1f48a56cf01f8fc3174ed809a1e3dd9ce59","src/async_impl/client.rs":"8c30b009838be2126b90344ef97fb6141ac8bdf695f4e7bb00a3052a707d9b5c","src/async_impl/h3_client/connect.rs":"4375585275c9efafbdab94bd2f341fe160555316a3a3948c83fee7c0a1d0eddd","src/async_impl/h3_client/dns.rs":"0b0876288eeba6c39331b3bd6b8b01cd4ab01e0362a1e859d961fcc5d520b43a","src/async_impl/h3_client/mod.rs":"3d7a499aec889845749098bf620dbd8c7e5338b5104a28dd12d3c8162532cab5","src/async_impl/h3_client/pool.rs":"01e118f38288b92cfec9d5e19884e1e8dadc1de94bf0b9bc10336e38092d593a","src/async_impl/mod.rs":"d207798170e6263f62e652a5cca27bf868e7e17c910ba3ee8907a303cb34608d","src/async_impl/multipart.rs":"fa28f3689c3975cad971f902c368b89485317be28aaca0ea4de67ca71413f093","src/async_impl/request.rs":"22bb3a3810cfe505a1eb34e4bf6f16db9f9dbcfb7ec79d1652996e72a6738fd3","src/async_impl/response.rs":"e7ad89c6473ad3adcb96a0adec857cc2c74bbabeffc56346efa270ce7082ea9f","src/async_impl/upgrade.rs":"b3944107063f2b5886b754ff80c4d3af34c9a97c4e9206d3bac3f4f3d6ae5d8a","src/blocking/body.rs":"578d58f3c661a4c43b771e982bac1dd08a6fedaf98f8baacd096fbe0cad63f7a","src/blocking/client.rs":"28cd16f9133a84484598ab00a3d39c24413067747b3b619d357bfe95c84171bc","src/blocking/mod.rs":"0f6e73f94fc6088261fc93b3b95fa99b6293fdd482103dcc08f2bf8c5ca44f4f","src/blocking/multipart.rs":"9b023b57b7f075d9d2ccd48c530cb29344f45f0ce4868f7dcc8ce9d093f6b312","src/blocking/request.rs":"045ffb9697312fc848f4e2f333f8bed6dae3268652ab298f1d4d4ce517342761","src/blocking/response.rs":"0ef0d5bfed59109238316f0f6dddf44dcfe68843f4491c59d24fab166cd766f4","src/blocking/wait.rs":"295a7be8cb959c1c414b832f58f9268c14aae2a170d4f8a09a133521a8387dfa","src/config.rs":"21d3c917c317e2c56ddc227897f88ee0506b11fdc83d1efdad22d4a9c4796eed","src/connect.rs":"cbe71704db6ea7ca1c83e7753c90aa9ff60a80f8d8c8ec2e795db054534caa42","src/cookie.rs":"1925b79a225bc9c0398c59e2fac11379df5db2d27493a1902ae79388ba1817d5","src/dns/gai.rs":"d078d9982d7e5e9e977e90ded62080fc3ac0b124753de508147f15ff8818aff8","src/dns/hickory.rs":"7275c4eb9e3ac588e4a291148a007f135db814380f8f27ad6bb2542b380df1da","src/dns/mod.rs":"cf9875316437ff762962a867bb3650dfe93f5cb6ec0a8e3397bfa20e36fb9c44","src/dns/resolve.rs":"7ca80ad7e849a2ac7b98bafe3a287ecd3580c8fa3dce873d0daabcc41b78ae7f","src/error.rs":"4f2ffac588056f8d98f8619fa4046e66efee97c0faef51ddce9c07ea0a46601b","src/into_url.rs":"761e9d0e04f652b201317d746cbe2a538de0604240914913fbe968114d88893d","src/lib.rs":"e31f340ec37d77c27ddad9ef7889f4924efa12450b63bf0c5af98c337876c9f3","src/proxy.rs":"7d5d3ed0fe95b0c82f1081e0907f4691d53c929dbe0f2c6134dceaca304839ba","src/redirect.rs":"b8a618396c45884e5557314a0fb99ec01c1868b77132e2cc81aa0a4ac4831c06","src/response.rs":"12cf82de2499a426686ed4f3719cce73e269b7b9ddc9c88458e7378beb2c58a8","src/retry.rs":"38e827605e2a1905cc9f9ae1194f4799902da43765b2e73a0d4dd9be130f8c7e","src/tls.rs":"73afedbf9e144a8c66dad7706bc37c6a73ff185eff1ee8654d158e57a0a8cbd6","src/util.rs":"38de462df299faa9294594796a2f90020d4fa6f001724c285850b268c716a7aa","src/wasm/body.rs":"81eea1079d4bee8e23e9be0b7488a07373a23c8eddf5c05aa9ec2b83810f9061","src/wasm/client.rs":"ca4afe51ea1c8d893fb695eb1867eeed3ad6f0ea80ed6040eda946d50aea0d97","src/wasm/mod.rs":"79c7aeac9b9d271e4cfe931b39314aef3a7ad78184a84424306e772652535e0b","src/wasm/multipart.rs":"4a347e2beedc2686d5d3818e205715cc6abe7ca678e26bf89de89a2ecb548f40","src/wasm/request.rs":"27f7f9790debdce2281c5a310008b5137253d0be106f9f2b9caf8696818e39dc","src/wasm/response.rs":"c7931612eb1c370345e938c8ce8a1f600f42b479ae81441f20d8832948cdd7c6"},"package":"eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"}

7
vendor/reqwest/.cargo_vcs_info.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"git": {
"sha1": "ef2768a823b28bf00e23e218e034be035b08d770",
"dirty": true
},
"path_in_vcs": ""
}

2713
vendor/reqwest/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

509
vendor/reqwest/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,509 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.64.0"
name = "reqwest"
version = "0.12.28"
authors = ["Sean McArthur <sean@seanmonstar.com>"]
build = false
include = [
"README.md",
"Cargo.toml",
"LICENSE-APACHE",
"LICENSE-MIT",
"src/**/*.rs",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "higher level HTTP client library"
documentation = "https://docs.rs/reqwest"
readme = "README.md"
keywords = [
"http",
"request",
"client",
]
categories = [
"web-programming::http-client",
"wasm",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/seanmonstar/reqwest"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
"--cfg",
"reqwest_unstable",
]
targets = [
"x86_64-unknown-linux-gnu",
"wasm32-unknown-unknown",
]
[package.metadata.playground]
features = [
"blocking",
"cookies",
"json",
"multipart",
]
[features]
__rustls = [
"dep:hyper-rustls",
"dep:tokio-rustls",
"dep:rustls",
"__tls",
]
__rustls-ring = [
"hyper-rustls?/ring",
"tokio-rustls?/ring",
"rustls?/ring",
"quinn?/ring",
]
__tls = [
"dep:rustls-pki-types",
"tokio/io-util",
]
blocking = [
"dep:futures-channel",
"futures-channel?/sink",
"dep:futures-util",
"futures-util?/io",
"futures-util?/sink",
"tokio/sync",
]
brotli = ["tower-http/decompression-br"]
charset = [
"dep:encoding_rs",
"dep:mime",
]
cookies = [
"dep:cookie_crate",
"dep:cookie_store",
]
default = [
"default-tls",
"charset",
"http2",
"system-proxy",
]
default-tls = [
"dep:hyper-tls",
"dep:native-tls-crate",
"__tls",
"dep:tokio-native-tls",
]
deflate = ["tower-http/decompression-deflate"]
gzip = ["tower-http/decompression-gzip"]
hickory-dns = [
"dep:hickory-resolver",
"dep:once_cell",
]
http2 = [
"h2",
"hyper/http2",
"hyper-util/http2",
"hyper-rustls?/http2",
]
http3 = [
"rustls-tls-manual-roots",
"dep:h3",
"dep:h3-quinn",
"dep:quinn",
"tokio/macros",
]
json = ["dep:serde_json"]
macos-system-configuration = ["system-proxy"]
multipart = [
"dep:mime_guess",
"dep:futures-util",
]
native-tls = ["default-tls"]
native-tls-alpn = [
"native-tls",
"native-tls-crate?/alpn",
"hyper-tls?/alpn",
]
native-tls-vendored = [
"native-tls",
"native-tls-crate?/vendored",
]
rustls-tls = ["rustls-tls-webpki-roots"]
rustls-tls-manual-roots = [
"rustls-tls-manual-roots-no-provider",
"__rustls-ring",
]
rustls-tls-manual-roots-no-provider = ["__rustls"]
rustls-tls-native-roots = [
"rustls-tls-native-roots-no-provider",
"__rustls-ring",
]
rustls-tls-native-roots-no-provider = [
"dep:rustls-native-certs",
"hyper-rustls?/native-tokio",
"__rustls",
]
rustls-tls-no-provider = ["rustls-tls-manual-roots-no-provider"]
rustls-tls-webpki-roots = [
"rustls-tls-webpki-roots-no-provider",
"__rustls-ring",
]
rustls-tls-webpki-roots-no-provider = [
"dep:webpki-roots",
"hyper-rustls?/webpki-tokio",
"__rustls",
]
socks = []
stream = [
"tokio/fs",
"dep:futures-util",
"dep:tokio-util",
"dep:wasm-streams",
]
system-proxy = ["hyper-util/client-proxy-system"]
trust-dns = []
zstd = ["tower-http/decompression-zstd"]
[lib]
name = "reqwest"
path = "src/lib.rs"
[dependencies.base64]
version = "0.22"
[dependencies.bytes]
version = "1.2"
[dependencies.futures-core]
version = "0.3.28"
default-features = false
[dependencies.futures-util]
version = "0.3.28"
optional = true
default-features = false
[dependencies.http]
version = "1.1"
[dependencies.mime_guess]
version = "2.0"
optional = true
default-features = false
[dependencies.serde]
version = "1.0"
[dependencies.serde_json]
version = "1.0"
optional = true
[dependencies.serde_urlencoded]
version = "0.7.1"
[dependencies.sync_wrapper]
version = "1.0"
features = ["futures"]
[dependencies.url]
version = "2.4"
[dev-dependencies.libc]
version = "0"
[dev-dependencies.num_cpus]
version = "1.0"
[dev-dependencies.tower]
version = "0.5.2"
features = ["limit"]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.cookie_crate]
version = "0.18.0"
optional = true
package = "cookie"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.cookie_store]
version = "0.22.0"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.encoding_rs]
version = "0.8"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.futures-channel]
version = "0.3"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.h2]
version = "0.4"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.h3]
version = "0.0.8"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.h3-quinn]
version = "0.0.10"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.hickory-resolver]
version = "0.25"
features = ["tokio"]
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.http-body]
version = "1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.http-body-util]
version = "0.1.2"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.hyper]
version = "1.1"
features = [
"http1",
"client",
]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.hyper-rustls]
version = "0.27.0"
features = [
"http1",
"tls12",
]
optional = true
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.hyper-tls]
version = "0.6"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.hyper-util]
version = "0.1.12"
features = [
"http1",
"client",
"client-legacy",
"client-proxy",
"tokio",
]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.log]
version = "0.4.17"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.mime]
version = "0.3.16"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.native-tls-crate]
version = "0.2.10"
optional = true
package = "native-tls"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.once_cell]
version = "1.18"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.percent-encoding]
version = "2.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.pin-project-lite]
version = "0.2.11"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.quinn]
version = "0.11.1"
features = [
"rustls",
"runtime-tokio",
]
optional = true
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rustls]
version = "0.23.4"
features = [
"std",
"tls12",
]
optional = true
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rustls-native-certs]
version = "0.8.0"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rustls-pki-types]
version = "1.9.0"
features = ["std"]
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "1.0"
features = [
"net",
"time",
]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio-native-tls]
version = "0.3.0"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio-rustls]
version = "0.26"
features = ["tls12"]
optional = true
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio-util]
version = "0.7.9"
features = ["io"]
optional = true
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tower]
version = "0.5.2"
features = [
"retry",
"timeout",
"util",
]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tower-http]
version = "0.6.8"
features = ["follow-redirect"]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tower-service]
version = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.webpki-roots]
version = "1"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.brotli_crate]
version = "8"
package = "brotli"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.doc-comment]
version = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.env_logger]
version = "0.10"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.flate2]
version = "1.0.13"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.futures-util]
version = "0.3.28"
features = [
"std",
"alloc",
]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.hyper]
version = "1.1.0"
features = [
"http1",
"http2",
"client",
"server",
]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.hyper-util]
version = "0.1.12"
features = [
"http1",
"http2",
"client",
"client-legacy",
"server-auto",
"server-graceful",
"tokio",
]
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.serde]
version = "1.0"
features = ["derive"]
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.tokio]
version = "1.0"
features = [
"macros",
"rt-multi-thread",
]
default-features = false
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.zstd_crate]
version = "0.13"
package = "zstd"
[target.'cfg(target_arch = "wasm32")'.dependencies.js-sys]
version = "0.3.77"
[target.'cfg(target_arch = "wasm32")'.dependencies.serde_json]
version = "1.0"
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2.89"
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen-futures]
version = "0.4.18"
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-streams]
version = "0.4"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3.28"
features = [
"AbortController",
"AbortSignal",
"Headers",
"Request",
"RequestInit",
"RequestMode",
"Response",
"Window",
"FormData",
"Blob",
"BlobPropertyBag",
"ServiceWorkerGlobalScope",
"RequestCredentials",
"File",
"ReadableStream",
"RequestCache",
]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.wasm-bindgen]
version = "0.2.89"
features = ["serde-serialize"]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.wasm-bindgen-test]
version = "0.3"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(reqwest_unstable)"]

201
vendor/reqwest/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Sean McArthur
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

20
vendor/reqwest/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2016-2025 Sean McArthur
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

86
vendor/reqwest/README.md vendored Normal file
View File

@@ -0,0 +1,86 @@
# reqwest
[![crates.io](https://img.shields.io/crates/v/reqwest.svg)](https://crates.io/crates/reqwest)
[![Documentation](https://docs.rs/reqwest/badge.svg)](https://docs.rs/reqwest)
[![MIT/Apache-2 licensed](https://img.shields.io/crates/l/reqwest.svg)](./LICENSE-APACHE)
[![CI](https://github.com/seanmonstar/reqwest/actions/workflows/ci.yml/badge.svg)](https://github.com/seanmonstar/reqwest/actions/workflows/ci.yml)
An ergonomic, batteries-included HTTP Client for Rust.
- Async and blocking `Client`s
- Plain bodies, JSON, urlencoded, multipart
- Customizable redirect policy
- HTTP Proxies
- HTTPS via system-native TLS (or optionally, rustls)
- Cookie Store
- WASM
## Example
This asynchronous example uses [Tokio](https://tokio.rs) and enables some
optional features, so your `Cargo.toml` could look like this:
```toml
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
```
And then the code:
```rust,no_run
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::get("https://httpbin.org/ip")
.await?
.json::<HashMap<String, String>>()
.await?;
println!("{resp:#?}");
Ok(())
}
```
## Commercial Support
For private advice, support, reviews, access to the maintainer, and the like, reach out for [commercial support][sponsor].
## Requirements
On Linux:
- OpenSSL with headers. See https://docs.rs/openssl for supported versions
and more details. Alternatively you can enable the `native-tls-vendored`
feature to compile a copy of OpenSSL. Or, you can use [rustls](https://github.com/rustls/rustls)
via `rustls-tls` or other `rustls-tls-*` features.
On Windows and macOS:
- Nothing.
By default, Reqwest uses [rust-native-tls](https://github.com/sfackler/rust-native-tls),
which will use the operating system TLS framework if available, meaning Windows
and macOS. On Linux, it will use the available OpenSSL or fail to build if
not found.
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
## Sponsors
Support this project by becoming a [sponsor][].
[sponsor]: https://seanmonstar.com/sponsor

484
vendor/reqwest/src/async_impl/body.rs vendored Normal file
View File

@@ -0,0 +1,484 @@
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use std::time::Duration;
use bytes::Bytes;
use http_body::Body as HttpBody;
use http_body_util::combinators::BoxBody;
use pin_project_lite::pin_project;
#[cfg(feature = "stream")]
use tokio::fs::File;
use tokio::time::Sleep;
#[cfg(feature = "stream")]
use tokio_util::io::ReaderStream;
/// An asynchronous request body.
pub struct Body {
inner: Inner,
}
enum Inner {
Reusable(Bytes),
Streaming(BoxBody<Bytes, Box<dyn std::error::Error + Send + Sync>>),
}
pin_project! {
/// A body with a total timeout.
///
/// The timeout does not reset upon each chunk, but rather requires the whole
/// body be streamed before the deadline is reached.
pub(crate) struct TotalTimeoutBody<B> {
#[pin]
inner: B,
timeout: Pin<Box<Sleep>>,
}
}
pin_project! {
pub(crate) struct ReadTimeoutBody<B> {
#[pin]
inner: B,
#[pin]
sleep: Option<Sleep>,
timeout: Duration,
}
}
impl Body {
/// Returns a reference to the internal data of the `Body`.
///
/// `None` is returned, if the underlying data is a stream.
pub fn as_bytes(&self) -> Option<&[u8]> {
match &self.inner {
Inner::Reusable(bytes) => Some(bytes.as_ref()),
Inner::Streaming(..) => None,
}
}
/// Wrap a futures `Stream` in a box inside `Body`.
///
/// # Example
///
/// ```
/// # use reqwest::Body;
/// # use futures_util;
/// # fn main() {
/// let chunks: Vec<Result<_, ::std::io::Error>> = vec![
/// Ok("hello"),
/// Ok(" "),
/// Ok("world"),
/// ];
///
/// let stream = futures_util::stream::iter(chunks);
///
/// let body = Body::wrap_stream(stream);
/// # }
/// ```
///
/// # Optional
///
/// This requires the `stream` feature to be enabled.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub fn wrap_stream<S>(stream: S) -> Body
where
S: futures_core::stream::TryStream + Send + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
Bytes: From<S::Ok>,
{
Body::stream(stream)
}
#[cfg(any(feature = "stream", feature = "multipart", feature = "blocking"))]
pub(crate) fn stream<S>(stream: S) -> Body
where
S: futures_core::stream::TryStream + Send + 'static,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
Bytes: From<S::Ok>,
{
use futures_util::TryStreamExt;
use http_body::Frame;
use http_body_util::StreamBody;
let body = http_body_util::BodyExt::boxed(StreamBody::new(sync_wrapper::SyncStream::new(
stream
.map_ok(|d| Frame::data(Bytes::from(d)))
.map_err(Into::into),
)));
Body {
inner: Inner::Streaming(body),
}
}
pub(crate) fn empty() -> Body {
Body::reusable(Bytes::new())
}
pub(crate) fn reusable(chunk: Bytes) -> Body {
Body {
inner: Inner::Reusable(chunk),
}
}
/// Wrap a [`HttpBody`] in a box inside `Body`.
///
/// # Example
///
/// ```
/// # use reqwest::Body;
/// # use futures_util;
/// # fn main() {
/// let content = "hello,world!".to_string();
///
/// let body = Body::wrap(content);
/// # }
/// ```
pub fn wrap<B>(inner: B) -> Body
where
B: HttpBody + Send + Sync + 'static,
B::Data: Into<Bytes>,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use http_body_util::BodyExt;
let boxed = IntoBytesBody { inner }.map_err(Into::into).boxed();
Body {
inner: Inner::Streaming(boxed),
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
match self.inner {
Inner::Reusable(ref chunk) => Some(Body::reusable(chunk.clone())),
Inner::Streaming { .. } => None,
}
}
#[cfg(feature = "multipart")]
pub(crate) fn content_length(&self) -> Option<u64> {
match self.inner {
Inner::Reusable(ref bytes) => Some(bytes.len() as u64),
Inner::Streaming(ref body) => body.size_hint().exact(),
}
}
}
impl Default for Body {
#[inline]
fn default() -> Body {
Body::empty()
}
}
/*
impl From<hyper::Body> for Body {
#[inline]
fn from(body: hyper::Body) -> Body {
Self {
inner: Inner::Streaming {
body: Box::pin(WrapHyper(body)),
},
}
}
}
*/
impl From<Bytes> for Body {
#[inline]
fn from(bytes: Bytes) -> Body {
Body::reusable(bytes)
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(vec: Vec<u8>) -> Body {
Body::reusable(vec.into())
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body::reusable(Bytes::from_static(s))
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
Body::reusable(s.into())
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
s.as_bytes().into()
}
}
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
impl From<File> for Body {
#[inline]
fn from(file: File) -> Body {
Body::wrap_stream(ReaderStream::new(file))
}
}
impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Body").finish()
}
}
impl HttpBody for Body {
type Data = Bytes;
type Error = crate::Error;
fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match self.inner {
Inner::Reusable(ref mut bytes) => {
let out = bytes.split_off(0);
if out.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(hyper::body::Frame::data(out))))
}
}
Inner::Streaming(ref mut body) => Poll::Ready(
ready!(Pin::new(body).poll_frame(cx))
.map(|opt_chunk| opt_chunk.map_err(crate::error::body)),
),
}
}
fn size_hint(&self) -> http_body::SizeHint {
match self.inner {
Inner::Reusable(ref bytes) => http_body::SizeHint::with_exact(bytes.len() as u64),
Inner::Streaming(ref body) => body.size_hint(),
}
}
fn is_end_stream(&self) -> bool {
match self.inner {
Inner::Reusable(ref bytes) => bytes.is_empty(),
Inner::Streaming(ref body) => body.is_end_stream(),
}
}
}
// ===== impl TotalTimeoutBody =====
pub(crate) fn total_timeout<B>(body: B, timeout: Pin<Box<Sleep>>) -> TotalTimeoutBody<B> {
TotalTimeoutBody {
inner: body,
timeout,
}
}
pub(crate) fn with_read_timeout<B>(body: B, timeout: Duration) -> ReadTimeoutBody<B> {
ReadTimeoutBody {
inner: body,
sleep: None,
timeout,
}
}
impl<B> hyper::body::Body for TotalTimeoutBody<B>
where
B: hyper::body::Body,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Data = B::Data;
type Error = crate::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
let this = self.project();
if let Poll::Ready(()) = this.timeout.as_mut().poll(cx) {
return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
}
Poll::Ready(
ready!(this.inner.poll_frame(cx))
.map(|opt_chunk| opt_chunk.map_err(crate::error::body)),
)
}
#[inline]
fn size_hint(&self) -> http_body::SizeHint {
self.inner.size_hint()
}
#[inline]
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
impl<B> hyper::body::Body for ReadTimeoutBody<B>
where
B: hyper::body::Body,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Data = B::Data;
type Error = crate::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
let mut this = self.project();
// Start the `Sleep` if not active.
let sleep_pinned = if let Some(some) = this.sleep.as_mut().as_pin_mut() {
some
} else {
this.sleep.set(Some(tokio::time::sleep(*this.timeout)));
this.sleep.as_mut().as_pin_mut().unwrap()
};
// Error if the timeout has expired.
if let Poll::Ready(()) = sleep_pinned.poll(cx) {
return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut))));
}
let item = ready!(this.inner.poll_frame(cx))
.map(|opt_chunk| opt_chunk.map_err(crate::error::body));
// a ready frame means timeout is reset
this.sleep.set(None);
Poll::Ready(item)
}
#[inline]
fn size_hint(&self) -> http_body::SizeHint {
self.inner.size_hint()
}
#[inline]
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
pub(crate) type ResponseBody =
http_body_util::combinators::BoxBody<Bytes, Box<dyn std::error::Error + Send + Sync>>;
pub(crate) fn boxed<B>(body: B) -> ResponseBody
where
B: hyper::body::Body<Data = Bytes> + Send + Sync + 'static,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use http_body_util::BodyExt;
body.map_err(box_err).boxed()
}
pub(crate) fn response<B>(
body: B,
deadline: Option<Pin<Box<Sleep>>>,
read_timeout: Option<Duration>,
) -> ResponseBody
where
B: hyper::body::Body<Data = Bytes> + Send + Sync + 'static,
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
use http_body_util::BodyExt;
match (deadline, read_timeout) {
(Some(total), Some(read)) => {
let body = with_read_timeout(body, read).map_err(box_err);
total_timeout(body, total).map_err(box_err).boxed()
}
(Some(total), None) => total_timeout(body, total).map_err(box_err).boxed(),
(None, Some(read)) => with_read_timeout(body, read).map_err(box_err).boxed(),
(None, None) => body.map_err(box_err).boxed(),
}
}
fn box_err<E>(err: E) -> Box<dyn std::error::Error + Send + Sync>
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
err.into()
}
// ===== impl IntoBytesBody =====
pin_project! {
struct IntoBytesBody<B> {
#[pin]
inner: B,
}
}
// We can't use `map_frame()` because that loses the hint data (for good reason).
// But we aren't transforming the data.
impl<B> hyper::body::Body for IntoBytesBody<B>
where
B: hyper::body::Body,
B::Data: Into<Bytes>,
{
type Data = Bytes;
type Error = B::Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match ready!(self.project().inner.poll_frame(cx)) {
Some(Ok(f)) => Poll::Ready(Some(Ok(f.map_data(Into::into)))),
Some(Err(e)) => Poll::Ready(Some(Err(e))),
None => Poll::Ready(None),
}
}
#[inline]
fn size_hint(&self) -> http_body::SizeHint {
self.inner.size_hint()
}
#[inline]
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
#[cfg(test)]
mod tests {
use http_body::Body as _;
use super::Body;
#[test]
fn test_as_bytes() {
let test_data = b"Test body";
let body = Body::from(&test_data[..]);
assert_eq!(body.as_bytes(), Some(&test_data[..]));
}
#[test]
fn body_exact_length() {
let empty_body = Body::empty();
assert!(empty_body.is_end_stream());
assert_eq!(empty_body.size_hint().exact(), Some(0));
let bytes_body = Body::reusable("abc".into());
assert!(!bytes_body.is_end_stream());
assert_eq!(bytes_body.size_hint().exact(), Some(3));
// can delegate even when wrapped
let stream_body = Body::wrap(empty_body);
assert!(stream_body.is_end_stream());
assert_eq!(stream_body.size_hint().exact(), Some(0));
}
}

3144
vendor/reqwest/src/async_impl/client.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,144 @@
use crate::async_impl::h3_client::dns::resolve;
use crate::dns::DynResolver;
use crate::error::BoxError;
use bytes::Bytes;
use h3::client::SendRequest;
use h3_quinn::{Connection, OpenStreams};
use http::Uri;
use hyper_util::client::legacy::connect::dns::Name;
use quinn::crypto::rustls::QuicClientConfig;
use quinn::{ClientConfig, Endpoint, TransportConfig};
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use std::sync::Arc;
type H3Connection = (
h3::client::Connection<Connection, Bytes>,
SendRequest<OpenStreams, Bytes>,
);
/// H3 Client Config
#[derive(Clone)]
pub(crate) struct H3ClientConfig {
/// Set the maximum HTTP/3 header size this client is willing to accept.
///
/// See [header size constraints] section of the specification for details.
///
/// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints
///
/// Please see docs in [`Builder`] in [`h3`].
///
/// [`Builder`]: https://docs.rs/h3/latest/h3/client/struct.Builder.html#method.max_field_section_size
pub(crate) max_field_section_size: Option<u64>,
/// Enable whether to send HTTP/3 protocol grease on the connections.
///
/// Just like in HTTP/2, HTTP/3 also uses the concept of "grease"
///
/// to prevent potential interoperability issues in the future.
/// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve
/// and accommodate future changes without breaking existing implementations.
///
/// Please see docs in [`Builder`] in [`h3`].
///
/// [`Builder`]: https://docs.rs/h3/latest/h3/client/struct.Builder.html#method.send_grease
pub(crate) send_grease: Option<bool>,
}
impl Default for H3ClientConfig {
fn default() -> Self {
Self {
max_field_section_size: None,
send_grease: None,
}
}
}
#[derive(Clone)]
pub(crate) struct H3Connector {
resolver: DynResolver,
endpoint: Endpoint,
client_config: H3ClientConfig,
}
impl H3Connector {
pub fn new(
resolver: DynResolver,
tls: rustls::ClientConfig,
local_addr: Option<IpAddr>,
transport_config: TransportConfig,
client_config: H3ClientConfig,
) -> Result<H3Connector, BoxError> {
let quic_client_config = Arc::new(QuicClientConfig::try_from(tls)?);
let mut config = ClientConfig::new(quic_client_config);
// FIXME: Replace this when there is a setter.
config.transport_config(Arc::new(transport_config));
let socket_addr = match local_addr {
Some(ip) => SocketAddr::new(ip, 0),
None => "[::]:0".parse::<SocketAddr>().unwrap(),
};
let mut endpoint = Endpoint::client(socket_addr)?;
endpoint.set_default_client_config(config);
Ok(Self {
resolver,
endpoint,
client_config,
})
}
pub async fn connect(&mut self, dest: Uri) -> Result<H3Connection, BoxError> {
let host = dest
.host()
.ok_or("destination must have a host")?
.trim_start_matches('[')
.trim_end_matches(']');
let port = dest.port_u16().unwrap_or(443);
let addrs = if let Some(addr) = IpAddr::from_str(host).ok() {
// If the host is already an IP address, skip resolving.
vec![SocketAddr::new(addr, port)]
} else {
let addrs = resolve(&mut self.resolver, Name::from_str(host)?).await?;
let addrs = addrs.map(|mut addr| {
addr.set_port(port);
addr
});
addrs.collect()
};
self.remote_connect(addrs, host).await
}
async fn remote_connect(
&mut self,
addrs: Vec<SocketAddr>,
server_name: &str,
) -> Result<H3Connection, BoxError> {
let mut err = None;
for addr in addrs {
match self.endpoint.connect(addr, server_name)?.await {
Ok(new_conn) => {
let quinn_conn = Connection::new(new_conn);
let mut h3_client_builder = h3::client::builder();
if let Some(max_field_section_size) = self.client_config.max_field_section_size
{
h3_client_builder.max_field_section_size(max_field_section_size);
}
if let Some(send_grease) = self.client_config.send_grease {
h3_client_builder.send_grease(send_grease);
}
return Ok(h3_client_builder.build(quinn_conn).await?);
}
Err(e) => err = Some(e),
}
}
match err {
Some(e) => Err(Box::new(e) as BoxError),
None => Err("failed to establish connection for HTTP/3 request".into()),
}
}
}

View File

@@ -0,0 +1,43 @@
use core::task;
use hyper_util::client::legacy::connect::dns::Name;
use std::future::Future;
use std::net::SocketAddr;
use std::task::Poll;
use tower_service::Service;
// Trait from hyper to implement DNS resolution for HTTP/3 client.
pub trait Resolve {
type Addrs: Iterator<Item = SocketAddr>;
type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
fn resolve(&mut self, name: Name) -> Self::Future;
}
impl<S> Resolve for S
where
S: Service<Name>,
S::Response: Iterator<Item = SocketAddr>,
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
{
type Addrs = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
Service::poll_ready(self, cx)
}
fn resolve(&mut self, name: Name) -> Self::Future {
Service::call(self, name)
}
}
pub(super) async fn resolve<R>(resolver: &mut R, name: Name) -> Result<R::Addrs, R::Error>
where
R: Resolve,
{
std::future::poll_fn(|cx| resolver.poll_ready(cx)).await?;
resolver.resolve(name).await
}

View File

@@ -0,0 +1,116 @@
#![cfg(feature = "http3")]
pub(crate) mod connect;
pub(crate) mod dns;
mod pool;
use crate::async_impl::body::ResponseBody;
use crate::async_impl::h3_client::pool::{Key, Pool, PoolClient};
use crate::error::{BoxError, Error, Kind};
use crate::{error, Body};
use connect::H3Connector;
use http::{Request, Response};
use log::trace;
use std::future::{self, Future};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
use sync_wrapper::SyncWrapper;
use tower::Service;
#[derive(Clone)]
pub(crate) struct H3Client {
pool: Pool,
connector: H3Connector,
}
impl H3Client {
pub fn new(connector: H3Connector, pool_timeout: Option<Duration>) -> Self {
H3Client {
pool: Pool::new(pool_timeout),
connector,
}
}
async fn get_pooled_client(&mut self, key: Key) -> Result<PoolClient, BoxError> {
if let Some(client) = self.pool.try_pool(&key) {
trace!("getting client from pool with key {key:?}");
return Ok(client);
}
trace!("did not find connection {key:?} in pool so connecting...");
let dest = pool::domain_as_uri(key.clone());
let lock = match self.pool.connecting(&key) {
pool::Connecting::InProgress(waiter) => {
trace!("connecting to {key:?} is already in progress, subscribing...");
match waiter.receive().await {
Some(client) => return Ok(client),
None => return Err("failed to establish connection for HTTP/3 request".into()),
}
}
pool::Connecting::Acquired(lock) => lock,
};
trace!("connecting to {key:?}...");
let (driver, tx) = self.connector.connect(dest).await?;
trace!("saving new pooled connection to {key:?}");
Ok(self.pool.new_connection(lock, driver, tx))
}
async fn send_request(
mut self,
key: Key,
req: Request<Body>,
) -> Result<Response<ResponseBody>, Error> {
let mut pooled = match self.get_pooled_client(key).await {
Ok(client) => client,
Err(e) => return Err(error::request(e)),
};
pooled
.send_request(req)
.await
.map_err(|e| Error::new(Kind::Request, Some(e)))
}
pub fn request(&self, mut req: Request<Body>) -> H3ResponseFuture {
let pool_key = match pool::extract_domain(req.uri_mut()) {
Ok(s) => s,
Err(e) => {
return H3ResponseFuture {
inner: SyncWrapper::new(Box::pin(future::ready(Err(e)))),
}
}
};
H3ResponseFuture {
inner: SyncWrapper::new(Box::pin(self.clone().send_request(pool_key, req))),
}
}
}
impl Service<Request<Body>> for H3Client {
type Response = Response<ResponseBody>;
type Error = Error;
type Future = H3ResponseFuture;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
self.request(req)
}
}
pub(crate) struct H3ResponseFuture {
inner: SyncWrapper<Pin<Box<dyn Future<Output = Result<Response<ResponseBody>, Error>> + Send>>>,
}
impl Future for H3ResponseFuture {
type Output = Result<Response<ResponseBody>, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.get_mut().as_mut().poll(cx)
}
}

View File

@@ -0,0 +1,372 @@
use bytes::Bytes;
use std::collections::HashMap;
use std::future;
use std::pin::Pin;
use std::sync::mpsc::{Receiver, TryRecvError};
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;
use tokio::sync::{oneshot, watch};
use tokio::time::Instant;
use crate::async_impl::body::ResponseBody;
use crate::error::{BoxError, Error, Kind};
use crate::Body;
use bytes::Buf;
use h3::client::SendRequest;
use h3_quinn::{Connection, OpenStreams};
use http::uri::{Authority, Scheme};
use http::{Request, Response, Uri};
use log::{error, trace};
pub(super) type Key = (Scheme, Authority);
#[derive(Clone)]
pub struct Pool {
inner: Arc<Mutex<PoolInner>>,
}
struct ConnectingLockInner {
key: Key,
pool: Arc<Mutex<PoolInner>>,
}
/// A lock that ensures only one HTTP/3 connection is established per host at a
/// time. The lock is automatically released when dropped.
pub struct ConnectingLock(Option<ConnectingLockInner>);
/// A waiter that allows subscribers to receive updates when a new connection is
/// established or when the connection attempt fails. For example, when
/// connection lock is dropped due to an error.
pub struct ConnectingWaiter {
receiver: watch::Receiver<Option<PoolClient>>,
}
pub enum Connecting {
/// A connection attempt is already in progress.
/// You must subscribe to updates instead of initiating a new connection.
InProgress(ConnectingWaiter),
/// The connection lock has been acquired, allowing you to initiate a
/// new connection.
Acquired(ConnectingLock),
}
impl ConnectingLock {
fn new(key: Key, pool: Arc<Mutex<PoolInner>>) -> Self {
Self(Some(ConnectingLockInner { key, pool }))
}
/// Forget the lock and return corresponding Key
fn forget(mut self) -> Key {
// Unwrap is safe because the Option can be None only after dropping the
// lock
self.0.take().unwrap().key
}
}
impl Drop for ConnectingLock {
fn drop(&mut self) {
if let Some(ConnectingLockInner { key, pool }) = self.0.take() {
let mut pool = pool.lock().unwrap();
pool.connecting.remove(&key);
trace!("HTTP/3 connecting lock for {:?} is dropped", key);
}
}
}
impl ConnectingWaiter {
pub async fn receive(mut self) -> Option<PoolClient> {
match self.receiver.wait_for(Option::is_some).await {
// unwrap because we already checked that option is Some
Ok(ok) => Some(ok.as_ref().unwrap().to_owned()),
Err(_) => None,
}
}
}
impl Pool {
pub fn new(timeout: Option<Duration>) -> Self {
Self {
inner: Arc::new(Mutex::new(PoolInner {
connecting: HashMap::new(),
idle_conns: HashMap::new(),
timeout,
})),
}
}
/// Acquire a connecting lock. This is to ensure that we have only one HTTP3
/// connection per host.
pub fn connecting(&self, key: &Key) -> Connecting {
let mut inner = self.inner.lock().unwrap();
if let Some(sender) = inner.connecting.get(key) {
Connecting::InProgress(ConnectingWaiter {
receiver: sender.subscribe(),
})
} else {
let (tx, _) = watch::channel(None);
inner.connecting.insert(key.clone(), tx);
Connecting::Acquired(ConnectingLock::new(key.clone(), Arc::clone(&self.inner)))
}
}
pub fn try_pool(&self, key: &Key) -> Option<PoolClient> {
let mut inner = self.inner.lock().unwrap();
let timeout = inner.timeout;
if let Some(conn) = inner.idle_conns.get(&key) {
// We check first if the connection still valid
// and if not, we remove it from the pool.
if conn.is_invalid() {
trace!("pooled HTTP/3 connection is invalid so removing it...");
inner.idle_conns.remove(&key);
return None;
}
if let Some(duration) = timeout {
if Instant::now().saturating_duration_since(conn.idle_timeout) > duration {
trace!("pooled connection expired");
return None;
}
}
}
inner
.idle_conns
.get_mut(&key)
.and_then(|conn| Some(conn.pool()))
}
pub fn new_connection(
&mut self,
lock: ConnectingLock,
mut driver: h3::client::Connection<Connection, Bytes>,
tx: SendRequest<OpenStreams, Bytes>,
) -> PoolClient {
let (close_tx, close_rx) = std::sync::mpsc::channel();
tokio::spawn(async move {
let e = future::poll_fn(|cx| driver.poll_close(cx)).await;
trace!("poll_close returned error {e:?}");
close_tx.send(e).ok();
});
let mut inner = self.inner.lock().unwrap();
// We clean up "connecting" here so we don't have to acquire the lock again.
let key = lock.forget();
let Some(notifier) = inner.connecting.remove(&key) else {
unreachable!("there should be one connecting lock at a time");
};
let client = PoolClient::new(tx);
// Send the client to all our awaiters
let pool_client = if let Err(watch::error::SendError(Some(unsent_client))) =
notifier.send(Some(client.clone()))
{
// If there are no awaiters, the client is returned to us. As a
// micro optimisation, let's reuse it and avoid cloning.
unsent_client
} else {
client.clone()
};
let conn = PoolConnection::new(pool_client, close_rx);
inner.insert(key, conn);
client
}
}
struct PoolInner {
connecting: HashMap<Key, watch::Sender<Option<PoolClient>>>,
idle_conns: HashMap<Key, PoolConnection>,
timeout: Option<Duration>,
}
impl PoolInner {
fn insert(&mut self, key: Key, conn: PoolConnection) {
if self.idle_conns.contains_key(&key) {
trace!("connection already exists for key {key:?}");
}
self.idle_conns.insert(key, conn);
}
}
#[derive(Clone)]
pub struct PoolClient {
inner: SendRequest<OpenStreams, Bytes>,
}
impl PoolClient {
pub fn new(tx: SendRequest<OpenStreams, Bytes>) -> Self {
Self { inner: tx }
}
pub async fn send_request(
&mut self,
req: Request<Body>,
) -> Result<Response<ResponseBody>, BoxError> {
use hyper::body::Body as _;
let (head, mut req_body) = req.into_parts();
let mut req = Request::from_parts(head, ());
if let Some(n) = req_body.size_hint().exact() {
if n > 0 {
req.headers_mut()
.insert(http::header::CONTENT_LENGTH, n.into());
}
}
let (mut send, mut recv) = self.inner.send_request(req).await?.split();
let (tx, mut rx) = oneshot::channel::<Result<(), BoxError>>();
tokio::spawn(async move {
let mut req_body = Pin::new(&mut req_body);
loop {
match std::future::poll_fn(|cx| req_body.as_mut().poll_frame(cx)).await {
Some(Ok(frame)) => {
if let Ok(b) = frame.into_data() {
if let Err(e) = send.send_data(Bytes::copy_from_slice(&b)).await {
if let Err(e) = tx.send(Err(e.into())) {
error!("Failed to communicate send.send_data() error: {e:?}");
}
return;
}
}
}
Some(Err(e)) => {
if let Err(e) = tx.send(Err(e.into())) {
error!("Failed to communicate req_body read error: {e:?}");
}
return;
}
None => break,
}
}
if let Err(e) = send.finish().await {
if let Err(e) = tx.send(Err(e.into())) {
error!("Failed to communicate send.finish read error: {e:?}");
}
return;
}
let _ = tx.send(Ok(()));
});
tokio::select! {
Ok(Err(e)) = &mut rx => Err(e),
resp = recv.recv_response() => {
let resp = resp?;
let resp_body = crate::async_impl::body::boxed(Incoming::new(recv, resp.headers(), rx));
Ok(resp.map(|_| resp_body))
}
}
}
}
pub struct PoolConnection {
// This receives errors from polling h3 driver.
close_rx: Receiver<h3::error::ConnectionError>,
client: PoolClient,
idle_timeout: Instant,
}
impl PoolConnection {
pub fn new(client: PoolClient, close_rx: Receiver<h3::error::ConnectionError>) -> Self {
Self {
close_rx,
client,
idle_timeout: Instant::now(),
}
}
pub fn pool(&mut self) -> PoolClient {
self.idle_timeout = Instant::now();
self.client.clone()
}
pub fn is_invalid(&self) -> bool {
match self.close_rx.try_recv() {
Err(TryRecvError::Empty) => false,
Err(TryRecvError::Disconnected) => true,
Ok(_) => true,
}
}
}
struct Incoming<S, B> {
inner: h3::client::RequestStream<S, B>,
content_length: Option<u64>,
send_rx: oneshot::Receiver<Result<(), BoxError>>,
}
impl<S, B> Incoming<S, B> {
fn new(
stream: h3::client::RequestStream<S, B>,
headers: &http::header::HeaderMap,
send_rx: oneshot::Receiver<Result<(), BoxError>>,
) -> Self {
Self {
inner: stream,
content_length: headers
.get(http::header::CONTENT_LENGTH)
.and_then(|h| h.to_str().ok())
.and_then(|v| v.parse().ok()),
send_rx,
}
}
}
impl<S, B> http_body::Body for Incoming<S, B>
where
S: h3::quic::RecvStream,
{
type Data = Bytes;
type Error = crate::error::Error;
fn poll_frame(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
if let Ok(Err(e)) = self.send_rx.try_recv() {
return Poll::Ready(Some(Err(crate::error::body(e))));
}
match futures_core::ready!(self.inner.poll_recv_data(cx)) {
Ok(Some(mut b)) => Poll::Ready(Some(Ok(hyper::body::Frame::data(
b.copy_to_bytes(b.remaining()),
)))),
Ok(None) => Poll::Ready(None),
Err(e) => Poll::Ready(Some(Err(crate::error::body(e)))),
}
}
fn size_hint(&self) -> hyper::body::SizeHint {
if let Some(content_length) = self.content_length {
hyper::body::SizeHint::with_exact(content_length)
} else {
hyper::body::SizeHint::default()
}
}
}
pub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {
let uri_clone = uri.clone();
match (uri_clone.scheme(), uri_clone.authority()) {
(Some(scheme), Some(auth)) => Ok((scheme.clone(), auth.clone())),
_ => Err(Error::new(Kind::Request, None::<Error>)),
}
}
pub(crate) fn domain_as_uri((scheme, auth): Key) -> Uri {
http::uri::Builder::new()
.scheme(scheme)
.authority(auth)
.path_and_query("/")
.build()
.expect("domain is valid Uri")
}

14
vendor/reqwest/src/async_impl/mod.rs vendored Normal file
View File

@@ -0,0 +1,14 @@
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
pub use self::upgrade::Upgraded;
pub mod body;
pub mod client;
pub mod h3_client;
#[cfg(feature = "multipart")]
pub mod multipart;
pub(crate) mod request;
mod response;
mod upgrade;

View File

@@ -0,0 +1,754 @@
//! multipart/form-data
use std::borrow::Cow;
use std::fmt;
use std::pin::Pin;
#[cfg(feature = "stream")]
use std::io;
#[cfg(feature = "stream")]
use std::path::Path;
use bytes::Bytes;
use mime_guess::Mime;
use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
#[cfg(feature = "stream")]
use tokio::fs::File;
use futures_core::Stream;
use futures_util::{future, stream, StreamExt};
use http_body_util::BodyExt;
use super::Body;
use crate::header::HeaderMap;
/// An async multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
/// A field in a multipart form.
pub struct Part {
meta: PartMetadata,
value: Body,
body_length: Option<u64>,
}
pub(crate) struct FormParts<P> {
pub(crate) boundary: String,
pub(crate) computed_headers: Vec<Vec<u8>>,
pub(crate) fields: Vec<(Cow<'static, str>, P)>,
pub(crate) percent_encoding: PercentEncoding,
}
pub(crate) struct PartMetadata {
mime: Option<Mime>,
file_name: Option<Cow<'static, str>>,
pub(crate) headers: HeaderMap,
}
pub(crate) trait PartProps {
fn value_len(&self) -> Option<u64>;
fn metadata(&self) -> &PartMetadata;
}
// ===== impl Form =====
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
/// Creates a new async Form without any content.
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
/// Get the boundary that this form will use.
#[inline]
pub fn boundary(&self) -> &str {
self.inner.boundary()
}
/// Add a data field with supplied name and value.
///
/// # Examples
///
/// ```
/// let form = reqwest::multipart::Form::new()
/// .text("username", "seanmonstar")
/// .text("password", "secret");
/// ```
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a file field.
///
/// The path will be used to try to guess the filename and mime.
///
/// # Examples
///
/// ```no_run
/// # async fn run() -> std::io::Result<()> {
/// let form = reqwest::multipart::Form::new()
/// .file("key", "/path/to/file").await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Errors when the file cannot be opened.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub async fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
where
T: Into<Cow<'static, str>>,
U: AsRef<Path>,
{
Ok(self.part(name, Part::file(path).await?))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub fn percent_encode_path_segment(self) -> Form {
self.with_inner(|inner| inner.percent_encode_path_segment())
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub fn percent_encode_attr_chars(self) -> Form {
self.with_inner(|inner| inner.percent_encode_attr_chars())
}
/// Configure this `Form` to skip percent-encoding
pub fn percent_encode_noop(self) -> Form {
self.with_inner(|inner| inner.percent_encode_noop())
}
/// Consume this instance and transform into an instance of Body for use in a request.
pub(crate) fn stream(self) -> Body {
if self.inner.fields.is_empty() {
return Body::empty();
}
Body::stream(self.into_stream())
}
/// Produce a stream of the bytes in this `Form`, consuming it.
pub fn into_stream(mut self) -> impl Stream<Item = Result<Bytes, crate::Error>> + Send + Sync {
if self.inner.fields.is_empty() {
let empty_stream: Pin<
Box<dyn Stream<Item = Result<Bytes, crate::Error>> + Send + Sync>,
> = Box::pin(futures_util::stream::empty());
return empty_stream;
}
// create initial part to init reduce chain
let (name, part) = self.inner.fields.remove(0);
let start = Box::pin(self.part_stream(name, part))
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>;
let fields = self.inner.take_fields();
// for each field, chain an additional stream
let stream = fields.into_iter().fold(start, |memo, (name, part)| {
let part_stream = self.part_stream(name, part);
Box::pin(memo.chain(part_stream))
as Pin<Box<dyn Stream<Item = crate::Result<Bytes>> + Send + Sync>>
});
// append special ending boundary
let last = stream::once(future::ready(Ok(
format!("--{}--\r\n", self.boundary()).into()
)));
Box::pin(stream.chain(last))
}
/// Generate a hyper::Body stream for a single Part instance of a Form request.
pub(crate) fn part_stream<T>(
&mut self,
name: T,
part: Part,
) -> impl Stream<Item = Result<Bytes, crate::Error>>
where
T: Into<Cow<'static, str>>,
{
// start with boundary
let boundary = stream::once(future::ready(Ok(
format!("--{}\r\n", self.boundary()).into()
)));
// append headers
let header = stream::once(future::ready(Ok({
let mut h = self
.inner
.percent_encoding
.encode_headers(&name.into(), &part.meta);
h.extend_from_slice(b"\r\n\r\n");
h.into()
})));
// then append form data followed by terminating CRLF
boundary
.chain(header)
.chain(part.value.into_data_stream())
.chain(stream::once(future::ready(Ok("\r\n".into()))))
}
pub(crate) fn compute_length(&mut self) -> Option<u64> {
self.inner.compute_length()
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
// ===== impl Part =====
impl Part {
/// Makes a text parameter.
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body, None)
}
/// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body, None)
}
/// Makes a new parameter from an arbitrary stream.
pub fn stream<T: Into<Body>>(value: T) -> Part {
Part::new(value.into(), None)
}
/// Makes a new parameter from an arbitrary stream with a known length. This is particularly
/// useful when adding something like file contents as a stream, where you can know the content
/// length beforehand.
pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part {
Part::new(value.into(), Some(length))
}
/// Makes a file parameter.
///
/// # Errors
///
/// Errors when the file cannot be opened.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub async fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
let path = path.as_ref();
let file_name = path
.file_name()
.map(|filename| filename.to_string_lossy().into_owned());
let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
let mime = mime_guess::from_ext(ext).first_or_octet_stream();
let file = File::open(path).await?;
let len = file.metadata().await.map(|m| m.len()).ok();
let field = match len {
Some(len) => Part::stream_with_length(file, len),
None => Part::stream(file),
}
.mime(mime);
Ok(if let Some(file_name) = file_name {
field.file_name(file_name)
} else {
field
})
}
fn new(value: Body, body_length: Option<u64>) -> Part {
Part {
meta: PartMetadata::new(),
value,
body_length,
}
}
/// Tries to set the mime of this part.
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
// Re-export when mime 0.4 is available, with split MediaType/MediaRange.
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
/// Sets the filename, builder style.
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
/// Sets custom headers for the part.
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
..self
}
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn value_len(&self) -> Option<u64> {
if self.body_length.is_some() {
self.body_length
} else {
self.value.content_length()
}
}
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
// ===== impl FormParts =====
impl<P: PartProps> FormParts<P> {
pub(crate) fn new() -> Self {
FormParts {
boundary: gen_boundary(),
computed_headers: Vec::new(),
fields: Vec::new(),
percent_encoding: PercentEncoding::PathSegment,
}
}
pub(crate) fn boundary(&self) -> &str {
&self.boundary
}
/// Adds a customized Part.
pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
where
T: Into<Cow<'static, str>>,
{
self.fields.push((name.into(), part));
self
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub(crate) fn percent_encode_path_segment(mut self) -> Self {
self.percent_encoding = PercentEncoding::PathSegment;
self
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub(crate) fn percent_encode_attr_chars(mut self) -> Self {
self.percent_encoding = PercentEncoding::AttrChar;
self
}
/// Configure this `Form` to skip percent-encoding
pub(crate) fn percent_encode_noop(mut self) -> Self {
self.percent_encoding = PercentEncoding::NoOp;
self
}
// If predictable, computes the length the request will have
// The length should be predictable if only String and file fields have been added,
// but not if a generic reader has been added;
pub(crate) fn compute_length(&mut self) -> Option<u64> {
let mut length = 0u64;
for &(ref name, ref field) in self.fields.iter() {
match field.value_len() {
Some(value_length) => {
// We are constructing the header just to get its length. To not have to
// construct it again when the request is sent we cache these headers.
let header = self.percent_encoding.encode_headers(name, field.metadata());
let header_length = header.len();
self.computed_headers.push(header);
// The additions mimic the format string out of which the field is constructed
// in Reader. Not the cleanest solution because if that format string is
// ever changed then this formula needs to be changed too which is not an
// obvious dependency in the code.
length += 2
+ self.boundary().len() as u64
+ 2
+ header_length as u64
+ 4
+ value_length
+ 2
}
_ => return None,
}
}
// If there is at least one field there is a special boundary for the very last field.
if !self.fields.is_empty() {
length += 2 + self.boundary().len() as u64 + 4
}
Some(length)
}
/// Take the fields vector of this instance, replacing with an empty vector.
fn take_fields(&mut self) -> Vec<(Cow<'static, str>, P)> {
std::mem::replace(&mut self.fields, Vec::new())
}
}
impl<P: fmt::Debug> FormParts<P> {
pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct(ty_name)
.field("boundary", &self.boundary)
.field("parts", &self.fields)
.finish()
}
}
// ===== impl PartMetadata =====
impl PartMetadata {
pub(crate) fn new() -> Self {
PartMetadata {
mime: None,
file_name: None,
headers: HeaderMap::default(),
}
}
pub(crate) fn mime(mut self, mime: Mime) -> Self {
self.mime = Some(mime);
self
}
pub(crate) fn file_name<T>(mut self, filename: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.file_name = Some(filename.into());
self
}
pub(crate) fn headers<T>(mut self, headers: T) -> Self
where
T: Into<HeaderMap>,
{
self.headers = headers.into();
self
}
}
impl PartMetadata {
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
&self,
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
debug_struct
.field("mime", &self.mime)
.field("file_name", &self.file_name)
.field("headers", &self.headers)
}
}
// https://url.spec.whatwg.org/#fragment-percent-encode-set
const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
.add(b' ')
.add(b'"')
.add(b'<')
.add(b'>')
.add(b'`');
// https://url.spec.whatwg.org/#path-percent-encode-set
const PATH_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'#').add(b'?').add(b'{').add(b'}');
const PATH_SEGMENT_ENCODE_SET: &AsciiSet = &PATH_ENCODE_SET.add(b'/').add(b'%');
// https://tools.ietf.org/html/rfc8187#section-3.2.1
const ATTR_CHAR_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
.remove(b'!')
.remove(b'#')
.remove(b'$')
.remove(b'&')
.remove(b'+')
.remove(b'-')
.remove(b'.')
.remove(b'^')
.remove(b'_')
.remove(b'`')
.remove(b'|')
.remove(b'~');
pub(crate) enum PercentEncoding {
PathSegment,
AttrChar,
NoOp,
}
impl PercentEncoding {
pub(crate) fn encode_headers(&self, name: &str, field: &PartMetadata) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"Content-Disposition: form-data; ");
match self.percent_encode(name) {
Cow::Borrowed(value) => {
// nothing has been percent encoded
buf.extend_from_slice(b"name=\"");
buf.extend_from_slice(value.as_bytes());
buf.extend_from_slice(b"\"");
}
Cow::Owned(value) => {
// something has been percent encoded
buf.extend_from_slice(b"name*=utf-8''");
buf.extend_from_slice(value.as_bytes());
}
}
// According to RFC7578 Section 4.2, `filename*=` syntax is invalid.
// See https://github.com/seanmonstar/reqwest/issues/419.
if let Some(filename) = &field.file_name {
buf.extend_from_slice(b"; filename=\"");
let legal_filename = filename
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\r', "\\\r")
.replace('\n', "\\\n");
buf.extend_from_slice(legal_filename.as_bytes());
buf.extend_from_slice(b"\"");
}
if let Some(mime) = &field.mime {
buf.extend_from_slice(b"\r\nContent-Type: ");
buf.extend_from_slice(mime.as_ref().as_bytes());
}
for (k, v) in field.headers.iter() {
buf.extend_from_slice(b"\r\n");
buf.extend_from_slice(k.as_str().as_bytes());
buf.extend_from_slice(b": ");
buf.extend_from_slice(v.as_bytes());
}
buf
}
fn percent_encode<'a>(&self, value: &'a str) -> Cow<'a, str> {
use percent_encoding::utf8_percent_encode as percent_encode;
match self {
Self::PathSegment => percent_encode(value, PATH_SEGMENT_ENCODE_SET).into(),
Self::AttrChar => percent_encode(value, ATTR_CHAR_ENCODE_SET).into(),
Self::NoOp => value.into(),
}
}
}
fn gen_boundary() -> String {
use crate::util::fast_random as random;
let a = random();
let b = random();
let c = random();
let d = random();
format!("{a:016x}-{b:016x}-{c:016x}-{d:016x}")
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::stream;
use futures_util::TryStreamExt;
use std::future;
use tokio::{self, runtime};
#[test]
fn form_empty() {
let form = Form::new();
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("new rt");
let body = form.stream().into_data_stream();
let s = body.map_ok(|try_c| try_c.to_vec()).try_concat();
let out = rt.block_on(s);
assert!(out.unwrap().is_empty());
}
#[test]
fn stream_to_end() {
let mut form = Form::new()
.part(
"reader1",
Part::stream(Body::stream(stream::once(future::ready::<
Result<String, crate::Error>,
>(Ok(
"part1".to_owned()
))))),
)
.part("key1", Part::text("value1"))
.part(
"key2",
Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
)
.part(
"reader2",
Part::stream(Body::stream(stream::once(future::ready::<
Result<String, crate::Error>,
>(Ok(
"part2".to_owned()
))))),
)
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
part1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
part2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("new rt");
let body = form.stream().into_data_stream();
let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
let out = rt.block_on(s).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&out).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
}
#[test]
fn stream_to_end_with_header() {
let mut part = Part::text("value2").mime(mime_guess::mime::IMAGE_BMP);
let mut headers = HeaderMap::new();
headers.insert("Hdr3", "/a/b/c".parse().unwrap());
part = part.headers(headers);
let mut form = Form::new().part("key2", part);
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\
hdr3: /a/b/c\r\n\
\r\n\
value2\r\n\
--boundary--\r\n";
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("new rt");
let body = form.stream().into_data_stream();
let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
let out = rt.block_on(s).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&out).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&out).unwrap(), expected);
}
#[test]
fn correct_content_length() {
// Setup an arbitrary data stream
let stream_data = b"just some stream data";
let stream_len = stream_data.len();
let stream_data = stream_data
.chunks(3)
.map(|c| Ok::<_, std::io::Error>(Bytes::from(c)));
let the_stream = futures_util::stream::iter(stream_data);
let bytes_data = b"some bytes data".to_vec();
let bytes_len = bytes_data.len();
let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64);
let body_part = Part::bytes(bytes_data);
// A simple check to make sure we get the configured body length
assert_eq!(stream_part.value_len().unwrap(), stream_len as u64);
// Make sure it delegates to the underlying body if length is not specified
assert_eq!(body_part.value_len().unwrap(), bytes_len as u64);
}
#[test]
fn header_percent_encoding() {
let name = "start%'\"\r\nßend";
let field = Part::text("");
assert_eq!(
PercentEncoding::PathSegment.encode_headers(name, &field.meta),
&b"Content-Disposition: form-data; name*=utf-8''start%25'%22%0D%0A%C3%9Fend"[..]
);
assert_eq!(
PercentEncoding::AttrChar.encode_headers(name, &field.meta),
&b"Content-Disposition: form-data; name*=utf-8''start%25%27%22%0D%0A%C3%9Fend"[..]
);
}
}

1143
vendor/reqwest/src/async_impl/request.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,505 @@
use std::fmt;
use std::net::SocketAddr;
use std::pin::Pin;
use std::time::Duration;
use bytes::Bytes;
use http_body_util::BodyExt;
use hyper::{HeaderMap, StatusCode, Version};
use hyper_util::client::legacy::connect::HttpInfo;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "json")]
use serde_json;
use tokio::time::Sleep;
use url::Url;
use super::body::Body;
use crate::async_impl::body::ResponseBody;
#[cfg(feature = "cookies")]
use crate::cookie;
#[cfg(feature = "charset")]
use encoding_rs::{Encoding, UTF_8};
#[cfg(feature = "charset")]
use mime::Mime;
/// A Response to a submitted `Request`.
pub struct Response {
pub(super) res: hyper::Response<ResponseBody>,
// Boxed to save space (11 words to 1 word), and it's not accessed
// frequently internally.
url: Box<Url>,
}
impl Response {
pub(super) fn new(
res: hyper::Response<ResponseBody>,
url: Url,
total_timeout: Option<Pin<Box<Sleep>>>,
read_timeout: Option<Duration>,
) -> Response {
let (parts, body) = res.into_parts();
let res = hyper::Response::from_parts(
parts,
super::body::response(body, total_timeout, read_timeout),
);
Response {
res,
url: Box::new(url),
}
}
/// Get the `StatusCode` of this `Response`.
#[inline]
pub fn status(&self) -> StatusCode {
self.res.status()
}
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.res.version()
}
/// Get the `Headers` of this `Response`.
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.res.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.res.headers_mut()
}
/// Get the content length of the response, if it is known.
///
/// This value does not directly represents the value of the `Content-Length`
/// header, but rather the size of the response's body. To read the header's
/// value, please use the [`Response::headers`] method instead.
///
/// Reasons it may not be known:
///
/// - The response does not include a body (e.g. it responds to a `HEAD`
/// request).
/// - The response is gzipped and automatically decoded (thus changing the
/// actual decoded length).
pub fn content_length(&self) -> Option<u64> {
use hyper::body::Body;
Body::size_hint(self.res.body()).exact()
}
/// Retrieve the cookies contained in the response.
///
/// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(self.res.headers()).filter_map(Result::ok)
}
/// Get the final `Url` of this `Response`.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/// Get the remote address used to get this `Response`.
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.res
.extensions()
.get::<HttpInfo>()
.map(|info| info.remote_addr())
}
/// Returns a reference to the associated extensions.
pub fn extensions(&self) -> &http::Extensions {
self.res.extensions()
}
/// Returns a mutable reference to the associated extensions.
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
self.res.extensions_mut()
}
// body methods
/// Get the full response text.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the
/// [`char::REPLACEMENT_CHARACTER`].
/// Encoding is determined from the `charset` parameter of `Content-Type` header,
/// and defaults to `utf-8` if not presented.
///
/// Note that the BOM is stripped from the returned String.
///
/// # Note
///
/// If the `charset` feature is disabled the method will only attempt to decode the
/// response as UTF-8, regardless of the given `Content-Type`
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::get("http://httpbin.org/range/26")
/// .await?
/// .text()
/// .await?;
///
/// println!("text: {content:?}");
/// # Ok(())
/// # }
/// ```
pub async fn text(self) -> crate::Result<String> {
#[cfg(feature = "charset")]
{
self.text_with_charset("utf-8").await
}
#[cfg(not(feature = "charset"))]
{
let full = self.bytes().await?;
let text = String::from_utf8_lossy(&full);
Ok(text.into_owned())
}
}
/// Get the full response text given a specific encoding.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
/// You can provide a default encoding for decoding the raw message, while the
/// `charset` parameter of `Content-Type` header is still prioritized. For more information
/// about the possible encoding name, please go to [`encoding_rs`] docs.
///
/// Note that the BOM is stripped from the returned String.
///
/// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
///
/// # Optional
///
/// This requires the optional `encoding_rs` feature enabled.
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::get("http://httpbin.org/range/26")
/// .await?
/// .text_with_charset("utf-8")
/// .await?;
///
/// println!("text: {content:?}");
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "charset")]
#[cfg_attr(docsrs, doc(cfg(feature = "charset")))]
pub async fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
let content_type = self
.headers()
.get(crate::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let encoding_name = content_type
.as_ref()
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
.unwrap_or(default_encoding);
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
let full = self.bytes().await?;
let (text, _, _) = encoding.decode(&full);
Ok(text.into_owned())
}
/// Try to deserialize the response body as JSON.
///
/// # Optional
///
/// This requires the optional `json` feature enabled.
///
/// # Examples
///
/// ```
/// # extern crate reqwest;
/// # extern crate serde;
/// #
/// # use reqwest::Error;
/// # use serde::Deserialize;
/// #
/// // This `derive` requires the `serde` dependency.
/// #[derive(Deserialize)]
/// struct Ip {
/// origin: String,
/// }
///
/// # async fn run() -> Result<(), Error> {
/// let ip = reqwest::get("http://httpbin.org/ip")
/// .await?
/// .json::<Ip>()
/// .await?;
///
/// println!("ip: {}", ip.origin);
/// # Ok(())
/// # }
/// #
/// # fn main() { }
/// ```
///
/// # Errors
///
/// This method fails whenever the response body is not in JSON format,
/// or it cannot be properly deserialized to target type `T`. For more
/// details please see [`serde_json::from_reader`].
///
/// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
let full = self.bytes().await?;
serde_json::from_slice(&full).map_err(crate::error::decode)
}
/// Get the full response body as `Bytes`.
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bytes = reqwest::get("http://httpbin.org/ip")
/// .await?
/// .bytes()
/// .await?;
///
/// println!("bytes: {bytes:?}");
/// # Ok(())
/// # }
/// ```
pub async fn bytes(self) -> crate::Result<Bytes> {
use http_body_util::BodyExt;
BodyExt::collect(self.res.into_body())
.await
.map(|buf| buf.to_bytes())
.map_err(crate::error::decode)
}
/// Stream a chunk of the response body.
///
/// When the response body has been exhausted, this will return `None`.
///
/// # Example
///
/// ```
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut res = reqwest::get("https://hyper.rs").await?;
///
/// while let Some(chunk) = res.chunk().await? {
/// println!("Chunk: {chunk:?}");
/// }
/// # Ok(())
/// # }
/// ```
pub async fn chunk(&mut self) -> crate::Result<Option<Bytes>> {
use http_body_util::BodyExt;
// loop to ignore unrecognized frames
loop {
if let Some(res) = self.res.body_mut().frame().await {
let frame = res.map_err(crate::error::decode)?;
if let Ok(buf) = frame.into_data() {
return Ok(Some(buf));
}
// else continue
} else {
return Ok(None);
}
}
}
/// Convert the response into a `Stream` of `Bytes` from the body.
///
/// # Example
///
/// ```
/// use futures_util::StreamExt;
///
/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut stream = reqwest::get("http://httpbin.org/ip")
/// .await?
/// .bytes_stream();
///
/// while let Some(item) = stream.next().await {
/// println!("Chunk: {:?}", item?);
/// }
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the optional `stream` feature to be enabled.
#[cfg(feature = "stream")]
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
http_body_util::BodyDataStream::new(self.res.into_body().map_err(crate::error::decode))
}
// util methods
/// Turn a response into an error if the server returned an error.
///
/// # Example
///
/// ```
/// # use reqwest::Response;
/// fn on_response(res: Response) {
/// match res.error_for_status() {
/// Ok(_res) => (),
/// Err(err) => {
/// // asserting a 400 as an example
/// // it could be any status between 400...599
/// assert_eq!(
/// err.status(),
/// Some(reqwest::StatusCode::BAD_REQUEST)
/// );
/// }
/// }
/// }
/// # fn main() {}
/// ```
pub fn error_for_status(self) -> crate::Result<Self> {
let status = self.status();
let reason = self.extensions().get::<hyper::ext::ReasonPhrase>().cloned();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url, status, reason))
} else {
Ok(self)
}
}
/// Turn a reference to a response into an error if the server returned an error.
///
/// # Example
///
/// ```
/// # use reqwest::Response;
/// fn on_response(res: &Response) {
/// match res.error_for_status_ref() {
/// Ok(_res) => (),
/// Err(err) => {
/// // asserting a 400 as an example
/// // it could be any status between 400...599
/// assert_eq!(
/// err.status(),
/// Some(reqwest::StatusCode::BAD_REQUEST)
/// );
/// }
/// }
/// }
/// # fn main() {}
/// ```
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
let status = self.status();
let reason = self.extensions().get::<hyper::ext::ReasonPhrase>().cloned();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url.clone(), status, reason))
} else {
Ok(self)
}
}
// private
// The Response's body is an implementation detail.
// You no longer need to get a reference to it, there are async methods
// on the `Response` itself.
//
// This method is just used by the blocking API.
#[cfg(feature = "blocking")]
pub(crate) fn body_mut(&mut self) -> &mut ResponseBody {
self.res.body_mut()
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
.field("url", &self.url().as_str())
.field("status", &self.status())
.field("headers", self.headers())
.finish()
}
}
/// A `Response` can be piped as the `Body` of another request.
impl From<Response> for Body {
fn from(r: Response) -> Body {
Body::wrap(r.res.into_body())
}
}
// I'm not sure this conversion is that useful... People should be encouraged
// to use `http::Response`, not `reqwest::Response`.
impl<T: Into<Body>> From<http::Response<T>> for Response {
fn from(r: http::Response<T>) -> Response {
use crate::response::ResponseUrl;
let (mut parts, body) = r.into_parts();
let body: crate::async_impl::body::Body = body.into();
let url = parts
.extensions
.remove::<ResponseUrl>()
.unwrap_or_else(|| ResponseUrl(Url::parse("http://no.url.provided.local").unwrap()));
let url = url.0;
let res = hyper::Response::from_parts(parts, ResponseBody::new(body.map_err(Into::into)));
Response {
res,
url: Box::new(url),
}
}
}
/// A `Response` can be converted into a `http::Response`.
// It's supposed to be the inverse of the conversion above.
impl From<Response> for http::Response<Body> {
fn from(r: Response) -> http::Response<Body> {
let (parts, body) = r.res.into_parts();
let body = Body::wrap(body);
http::Response::from_parts(parts, body)
}
}
#[cfg(test)]
mod tests {
use super::Response;
use crate::ResponseBuilderExt;
use http::response::Builder;
use url::Url;
#[test]
fn test_from_http_response() {
let url = Url::parse("http://example.com").unwrap();
let response = Builder::new()
.status(200)
.url(url.clone())
.body("foo")
.unwrap();
let response = Response::from(response);
assert_eq!(response.status(), 200);
assert_eq!(*response.url(), url);
}
}

View File

@@ -0,0 +1,75 @@
use std::pin::Pin;
use std::task::{self, Poll};
use std::{fmt, io};
use hyper_util::rt::TokioIo;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
/// An upgraded HTTP connection.
pub struct Upgraded {
inner: TokioIo<hyper::upgrade::Upgraded>,
}
impl AsyncRead for Upgraded {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_read(cx, buf)
}
}
impl AsyncWrite for Upgraded {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write(cx, buf)
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_flush(cx)
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
Pin::new(&mut self.inner).poll_shutdown(cx)
}
fn is_write_vectored(&self) -> bool {
self.inner.is_write_vectored()
}
}
impl fmt::Debug for Upgraded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Upgraded").finish()
}
}
impl From<hyper::upgrade::Upgraded> for Upgraded {
fn from(inner: hyper::upgrade::Upgraded) -> Self {
Upgraded {
inner: TokioIo::new(inner),
}
}
}
impl super::response::Response {
/// Consumes the response and returns a future for a possible HTTP upgrade.
pub async fn upgrade(self) -> crate::Result<Upgraded> {
hyper::upgrade::on(self.res)
.await
.map(Upgraded::from)
.map_err(crate::error::upgrade)
}
}

372
vendor/reqwest/src/blocking/body.rs vendored Normal file
View File

@@ -0,0 +1,372 @@
use std::fmt;
use std::fs::File;
use std::future::Future;
#[cfg(feature = "multipart")]
use std::io::Cursor;
use std::io::{self, Read};
use std::mem::{self, MaybeUninit};
use std::ptr;
use bytes::Bytes;
use futures_channel::mpsc;
use crate::async_impl;
/// The body of a `Request`.
///
/// In most cases, this is not needed directly, as the
/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
/// passing many things (like a string or vector of bytes).
///
/// [builder]: ./struct.RequestBuilder.html#method.body
#[derive(Debug)]
pub struct Body {
kind: Kind,
}
impl Body {
/// Instantiate a `Body` from a reader.
///
/// # Note
///
/// While allowing for many types to be used, these bodies do not have
/// a way to reset to the beginning and be reused. This means that when
/// encountering a 307 or 308 status code, instead of repeating the
/// request at the new location, the `Response` will be returned with
/// the redirect status code set.
///
/// ```rust
/// # use std::fs::File;
/// # use reqwest::blocking::Body;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let file = File::open("national_secrets.txt")?;
/// let body = Body::new(file);
/// # Ok(())
/// # }
/// ```
///
/// If you have a set of bytes, like `String` or `Vec<u8>`, using the
/// `From` implementations for `Body` will store the data in a manner
/// it can be reused.
///
/// ```rust
/// # use reqwest::blocking::Body;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let s = "A stringy body";
/// let body = Body::from(s);
/// # Ok(())
/// # }
/// ```
pub fn new<R: Read + Send + 'static>(reader: R) -> Body {
Body {
kind: Kind::Reader(Box::from(reader), None),
}
}
/// Create a `Body` from a `Read` where the size is known in advance
/// but the data should not be fully loaded into memory. This will
/// set the `Content-Length` header and stream from the `Read`.
///
/// ```rust
/// # use std::fs::File;
/// # use reqwest::blocking::Body;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let file = File::open("a_large_file.txt")?;
/// let file_size = file.metadata()?.len();
/// let body = Body::sized(file, file_size);
/// # Ok(())
/// # }
/// ```
pub fn sized<R: Read + Send + 'static>(reader: R, len: u64) -> Body {
Body {
kind: Kind::Reader(Box::from(reader), Some(len)),
}
}
/// Returns the body as a byte slice if the body is already buffered in
/// memory. For streamed requests this method returns `None`.
pub fn as_bytes(&self) -> Option<&[u8]> {
match self.kind {
Kind::Reader(_, _) => None,
Kind::Bytes(ref bytes) => Some(bytes.as_ref()),
}
}
/// Converts streamed requests to their buffered equivalent and
/// returns a reference to the buffer. If the request is already
/// buffered, this has no effect.
///
/// Be aware that for large requests this method is expensive
/// and may cause your program to run out of memory.
pub fn buffer(&mut self) -> Result<&[u8], crate::Error> {
match self.kind {
Kind::Reader(ref mut reader, maybe_len) => {
let mut bytes = if let Some(len) = maybe_len {
Vec::with_capacity(len as usize)
} else {
Vec::new()
};
io::copy(reader, &mut bytes).map_err(crate::error::builder)?;
self.kind = Kind::Bytes(bytes.into());
self.buffer()
}
Kind::Bytes(ref bytes) => Ok(bytes.as_ref()),
}
}
#[cfg(feature = "multipart")]
pub(crate) fn len(&self) -> Option<u64> {
match self.kind {
Kind::Reader(_, len) => len,
Kind::Bytes(ref bytes) => Some(bytes.len() as u64),
}
}
#[cfg(feature = "multipart")]
pub(crate) fn into_reader(self) -> Reader {
match self.kind {
Kind::Reader(r, _) => Reader::Reader(r),
Kind::Bytes(b) => Reader::Bytes(Cursor::new(b)),
}
}
pub(crate) fn into_async(self) -> (Option<Sender>, async_impl::Body, Option<u64>) {
match self.kind {
Kind::Reader(read, len) => {
let (tx, rx) = mpsc::channel(0);
let tx = Sender {
body: (read, len),
tx,
};
(Some(tx), async_impl::Body::stream(rx), len)
}
Kind::Bytes(chunk) => {
let len = chunk.len() as u64;
(None, async_impl::Body::reusable(chunk), Some(len))
}
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
self.kind.try_clone().map(|kind| Body { kind })
}
}
enum Kind {
Reader(Box<dyn Read + Send>, Option<u64>),
Bytes(Bytes),
}
impl Kind {
fn try_clone(&self) -> Option<Kind> {
match self {
Kind::Reader(..) => None,
Kind::Bytes(v) => Some(Kind::Bytes(v.clone())),
}
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(v: Vec<u8>) -> Body {
Body {
kind: Kind::Bytes(v.into()),
}
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
s.into_bytes().into()
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body {
kind: Kind::Bytes(Bytes::from_static(s)),
}
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
s.as_bytes().into()
}
}
impl From<File> for Body {
#[inline]
fn from(f: File) -> Body {
let len = f.metadata().map(|m| m.len()).ok();
Body {
kind: Kind::Reader(Box::new(f), len),
}
}
}
impl From<Bytes> for Body {
#[inline]
fn from(b: Bytes) -> Body {
Body {
kind: Kind::Bytes(b),
}
}
}
impl fmt::Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Kind::Reader(_, ref v) => f
.debug_struct("Reader")
.field("length", &DebugLength(v))
.finish(),
Kind::Bytes(ref v) => fmt::Debug::fmt(v, f),
}
}
}
struct DebugLength<'a>(&'a Option<u64>);
impl<'a> fmt::Debug for DebugLength<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self.0 {
Some(ref len) => fmt::Debug::fmt(len, f),
None => f.write_str("Unknown"),
}
}
}
#[cfg(feature = "multipart")]
pub(crate) enum Reader {
Reader(Box<dyn Read + Send>),
Bytes(Cursor<Bytes>),
}
#[cfg(feature = "multipart")]
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Reader::Reader(ref mut rdr) => rdr.read(buf),
Reader::Bytes(ref mut rdr) => rdr.read(buf),
}
}
}
pub(crate) struct Sender {
body: (Box<dyn Read + Send>, Option<u64>),
tx: mpsc::Sender<Result<Bytes, Abort>>,
}
#[derive(Debug)]
struct Abort;
impl fmt::Display for Abort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("abort request body")
}
}
impl std::error::Error for Abort {}
async fn send_future(sender: Sender) -> Result<(), crate::Error> {
use bytes::{BufMut, BytesMut};
use futures_util::SinkExt;
use std::cmp;
let con_len = sender.body.1;
let cap = cmp::min(sender.body.1.unwrap_or(8192), 8192);
let mut written = 0;
let mut buf = BytesMut::zeroed(cap as usize);
buf.clear();
let mut body = sender.body.0;
// Put in an option so that it can be consumed on error to call abort()
let mut tx = Some(sender.tx);
loop {
if Some(written) == con_len {
// Written up to content-length, so stop.
return Ok(());
}
// The input stream is read only if the buffer is empty so
// that there is only one read in the buffer at any time.
//
// We need to know whether there is any data to send before
// we check the transmission channel (with poll_ready below)
// because sometimes the receiver disappears as soon as it
// considers the data is completely transmitted, which may
// be true.
//
// The use case is a web server that closes its
// input stream as soon as the data received is valid JSON.
// This behaviour is questionable, but it exists and the
// fact is that there is actually no remaining data to read.
if buf.is_empty() {
if buf.capacity() == buf.len() {
buf.reserve(8192);
// zero out the reserved memory
let uninit = buf.spare_capacity_mut();
let uninit_len = uninit.len();
unsafe {
ptr::write_bytes(uninit.as_mut_ptr().cast::<u8>(), 0, uninit_len);
}
}
let bytes = unsafe {
mem::transmute::<&mut [MaybeUninit<u8>], &mut [u8]>(buf.spare_capacity_mut())
};
match body.read(bytes) {
Ok(0) => {
// The buffer was empty and nothing's left to
// read. Return.
return Ok(());
}
Ok(n) => unsafe {
buf.advance_mut(n);
},
Err(e) => {
let _ = tx
.take()
.expect("tx only taken on error")
.clone()
.try_send(Err(Abort));
return Err(crate::error::body(e));
}
}
}
// The only way to get here is when the buffer is not empty.
// We can check the transmission channel
let buf_len = buf.len() as u64;
tx.as_mut()
.expect("tx only taken on error")
.send(Ok(buf.split().freeze()))
.await
.map_err(crate::error::body)?;
written += buf_len;
}
}
impl Sender {
// A `Future` that may do blocking read calls.
// As a `Future`, this integrates easily with `wait::timeout`.
pub(crate) fn send(self) -> impl Future<Output = Result<(), crate::Error>> {
send_future(self)
}
}
// useful for tests, but not publicly exposed
#[cfg(test)]
pub(crate) fn read_to_string(mut body: Body) -> io::Result<String> {
let mut s = String::new();
match body.kind {
Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s),
Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s),
}
.map(|_| s)
}

1524
vendor/reqwest/src/blocking/client.rs vendored Normal file

File diff suppressed because it is too large Load Diff

108
vendor/reqwest/src/blocking/mod.rs vendored Normal file
View File

@@ -0,0 +1,108 @@
//! A blocking Client API.
//!
//! The blocking `Client` will block the current thread to execute, instead
//! of returning futures that need to be executed on a runtime.
//!
//! Conversely, the functionality in `reqwest::blocking` must *not* be executed
//! within an async runtime, or it will panic when attempting to block. If
//! calling directly from an async function, consider using an async
//! [`reqwest::Client`][crate::Client] instead. If the immediate context is only
//! synchronous, but a transitive caller is async, consider changing that caller
//! to use [`tokio::task::spawn_blocking`] around the calls that need to block.
//!
//! # Optional
//!
//! This requires the optional `blocking` feature to be enabled.
//!
//! # Making a GET request
//!
//! For a single request, you can use the [`get`] shortcut method.
//!
//! ```rust
//! # use reqwest::{Error, Response};
//!
//! # fn run() -> Result<(), Error> {
//! let body = reqwest::blocking::get("https://www.rust-lang.org")?
//! .text()?;
//!
//! println!("body = {body:?}");
//! # Ok(())
//! # }
//! ```
//!
//! Additionally, the blocking [`Response`] struct implements Rust's
//! `Read` trait, so many useful standard library and third party crates will
//! have convenience methods that take a `Response` anywhere `T: Read` is
//! acceptable.
//!
//! **NOTE**: If you plan to perform multiple requests, it is best to create a
//! [`Client`] and reuse it, taking advantage of keep-alive connection pooling.
//!
//! # Making POST requests (or setting request bodies)
//!
//! There are several ways you can set the body of a request. The basic one is
//! by using the `body()` method of a [`RequestBuilder`]. This lets you set the
//! exact raw bytes of what the body should be. It accepts various types,
//! including `String`, `Vec<u8>`, and `File`. If you wish to pass a custom
//! Reader, you can use the `reqwest::blocking::Body::new()` constructor.
//!
//! ```rust
//! # use reqwest::Error;
//! #
//! # fn run() -> Result<(), Error> {
//! let client = reqwest::blocking::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .body("the exact body that is sent")
//! .send()?;
//! # Ok(())
//! # }
//! ```
//!
//! ## And More
//!
//! Most features available to the asynchronous `Client` are also available,
//! on the blocking `Client`, see those docs for more.
mod body;
mod client;
#[cfg(feature = "multipart")]
pub mod multipart;
mod request;
mod response;
mod wait;
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
/// Shortcut method to quickly make a *blocking* `GET` request.
///
/// **NOTE**: This function creates a new internal `Client` on each call,
/// and so should not be used if making many requests. Create a
/// [`Client`](./struct.Client.html) instead.
///
/// # Examples
///
/// ```rust
/// # fn run() -> Result<(), reqwest::Error> {
/// let body = reqwest::blocking::get("https://www.rust-lang.org")?
/// .text()?;
/// # Ok(())
/// # }
/// # fn main() { }
/// ```
///
/// # Errors
///
/// This function fails if:
///
/// - the native TLS backend cannot be initialized,
/// - the supplied `Url` cannot be parsed,
/// - there was an error while sending request,
/// - a redirect loop was detected,
/// - the redirect limit was exhausted, or
/// - the total download time exceeds 30 seconds.
pub fn get<T: crate::IntoUrl>(url: T) -> crate::Result<Response> {
Client::builder().build()?.get(url).send()
}

494
vendor/reqwest/src/blocking/multipart.rs vendored Normal file
View File

@@ -0,0 +1,494 @@
//! multipart/form-data
//!
//! To send a `multipart/form-data` body, a [`Form`] is built up, adding
//! fields or customized [`Part`]s, and then calling the
//! [`multipart`][builder] method on the `RequestBuilder`.
//!
//! # Example
//!
//! ```
//! use reqwest::blocking::multipart;
//!
//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
//! let form = multipart::Form::new()
//! // Adding just a simple text field...
//! .text("username", "seanmonstar")
//! // And a file...
//! .file("photo", "/path/to/photo.png")?;
//!
//! // Customize all the details of a Part if needed...
//! let bio = multipart::Part::text("hallo peeps")
//! .file_name("bio.txt")
//! .mime_str("text/plain")?;
//!
//! // Add the custom part to our form...
//! let form = form.part("biography", bio);
//!
//! // And finally, send the form
//! let client = reqwest::blocking::Client::new();
//! let resp = client
//! .post("http://localhost:8080/user")
//! .multipart(form)
//! .send()?;
//! # Ok(())
//! # }
//! # fn main() {}
//! ```
//!
//! [builder]: ../struct.RequestBuilder.html#method.multipart
use std::borrow::Cow;
use std::fmt;
use std::fs::File;
use std::io::{self, Cursor, Read};
use std::path::Path;
use mime_guess::{self, Mime};
use super::Body;
use crate::async_impl::multipart::{FormParts, PartMetadata, PartProps};
use crate::header::HeaderMap;
/// A multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
/// A field in a multipart form.
pub struct Part {
meta: PartMetadata,
value: Body,
}
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
/// Creates a new Form without any content.
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
/// Get the boundary that this form will use.
#[inline]
pub fn boundary(&self) -> &str {
self.inner.boundary()
}
/// Add a data field with supplied name and value.
///
/// # Examples
///
/// ```
/// let form = reqwest::blocking::multipart::Form::new()
/// .text("username", "seanmonstar")
/// .text("password", "secret");
/// ```
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a file field.
///
/// The path will be used to try to guess the filename and mime.
///
/// # Examples
///
/// ```no_run
/// # fn run() -> std::io::Result<()> {
/// let form = reqwest::blocking::multipart::Form::new()
/// .file("key", "/path/to/file")?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Errors when the file cannot be opened.
pub fn file<T, U>(self, name: T, path: U) -> io::Result<Form>
where
T: Into<Cow<'static, str>>,
U: AsRef<Path>,
{
Ok(self.part(name, Part::file(path)?))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
/// Configure this `Form` to percent-encode using the `path-segment` rules.
pub fn percent_encode_path_segment(self) -> Form {
self.with_inner(|inner| inner.percent_encode_path_segment())
}
/// Configure this `Form` to percent-encode using the `attr-char` rules.
pub fn percent_encode_attr_chars(self) -> Form {
self.with_inner(|inner| inner.percent_encode_attr_chars())
}
/// Configure this `Form` to skip percent-encoding
pub fn percent_encode_noop(self) -> Form {
self.with_inner(|inner| inner.percent_encode_noop())
}
pub(crate) fn reader(self) -> Reader {
Reader::new(self)
}
/// Produce a reader over the multipart form data.
pub fn into_reader(self) -> impl Read {
self.reader()
}
// If predictable, computes the length the request will have
// The length should be predictable if only String and file fields have been added,
// but not if a generic reader has been added;
pub(crate) fn compute_length(&mut self) -> Option<u64> {
self.inner.compute_length()
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
impl Part {
/// Makes a text parameter.
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body)
}
/// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body)
}
/// Adds a generic reader.
///
/// Does not set filename or mime.
pub fn reader<T: Read + Send + 'static>(value: T) -> Part {
Part::new(Body::new(value))
}
/// Adds a generic reader with known length.
///
/// Does not set filename or mime.
pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part {
Part::new(Body::sized(value, length))
}
/// Makes a file parameter.
///
/// # Errors
///
/// Errors when the file cannot be opened.
pub fn file<T: AsRef<Path>>(path: T) -> io::Result<Part> {
let path = path.as_ref();
let file_name = path
.file_name()
.map(|filename| filename.to_string_lossy().into_owned());
let ext = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
let mime = mime_guess::from_ext(ext).first_or_octet_stream();
let file = File::open(path)?;
let field = Part::new(Body::from(file)).mime(mime);
Ok(if let Some(file_name) = file_name {
field.file_name(file_name)
} else {
field
})
}
fn new(value: Body) -> Part {
Part {
meta: PartMetadata::new(),
value,
}
}
/// Tries to set the mime of this part.
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
// Re-export when mime 0.4 is available, with split MediaType/MediaRange.
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
/// Sets the filename, builder style.
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
/// Sets custom headers for the part.
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
value: self.value,
}
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn value_len(&self) -> Option<u64> {
self.value.len()
}
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
pub(crate) struct Reader {
form: Form,
active_reader: Option<Box<dyn Read + Send>>,
}
impl fmt::Debug for Reader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Reader").field("form", &self.form).finish()
}
}
impl Reader {
fn new(form: Form) -> Reader {
let mut reader = Reader {
form,
active_reader: None,
};
reader.next_reader();
reader
}
fn next_reader(&mut self) {
self.active_reader = if !self.form.inner.fields.is_empty() {
// We need to move out of the vector here because we are consuming the field's reader
let (name, field) = self.form.inner.fields.remove(0);
let boundary = Cursor::new(format!("--{}\r\n", self.form.boundary()));
let header = Cursor::new({
// Try to use cached headers created by compute_length
let mut h = if !self.form.inner.computed_headers.is_empty() {
self.form.inner.computed_headers.remove(0)
} else {
self.form
.inner
.percent_encoding
.encode_headers(&name, field.metadata())
};
h.extend_from_slice(b"\r\n\r\n");
h
});
let reader = boundary
.chain(header)
.chain(field.value.into_reader())
.chain(Cursor::new("\r\n"));
// According to https://tools.ietf.org/html/rfc2046#section-5.1.1
// the very last field has a special boundary
if !self.form.inner.fields.is_empty() {
Some(Box::new(reader))
} else {
Some(Box::new(reader.chain(Cursor::new(format!(
"--{}--\r\n",
self.form.boundary()
)))))
}
} else {
None
}
}
}
impl Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut total_bytes_read = 0usize;
let mut last_read_bytes;
loop {
match self.active_reader {
Some(ref mut reader) => {
last_read_bytes = reader.read(&mut buf[total_bytes_read..])?;
total_bytes_read += last_read_bytes;
if total_bytes_read == buf.len() {
return Ok(total_bytes_read);
}
}
None => return Ok(total_bytes_read),
};
if last_read_bytes == 0 && !buf.is_empty() {
self.next_reader();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn form_empty() {
let mut output = Vec::new();
let mut form = Form::new();
let length = form.compute_length();
form.reader().read_to_end(&mut output).unwrap();
assert_eq!(output, b"");
assert_eq!(length.unwrap(), 0);
}
#[test]
fn read_to_end() {
let mut output = Vec::new();
let mut form = Form::new()
.part("reader1", Part::reader(std::io::empty()))
.part("key1", Part::text("value1"))
.part(
"key2",
Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
)
.part("reader2", Part::reader(std::io::empty()))
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let length = form.compute_length();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"reader2\"\r\n\r\n\
\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
assert!(length.is_none());
}
#[test]
fn read_to_end_with_length() {
let mut output = Vec::new();
let mut form = Form::new()
.text("key1", "value1")
.part(
"key2",
Part::text("value2").mime(mime_guess::mime::IMAGE_BMP),
)
.part("key3", Part::text("value3").file_name("filename"));
form.inner.boundary = "boundary".to_string();
let length = form.compute_length();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key1\"\r\n\r\n\
value1\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\r\n\
value2\r\n\
--boundary\r\n\
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\
value3\r\n--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
assert_eq!(length.unwrap(), expected.len() as u64);
}
#[test]
fn read_to_end_with_header() {
let mut output = Vec::new();
let mut part = Part::text("value2").mime(mime_guess::mime::IMAGE_BMP);
let mut headers = HeaderMap::new();
headers.insert("Hdr3", "/a/b/c".parse().unwrap());
part = part.headers(headers);
let mut form = Form::new().part("key2", part);
form.inner.boundary = "boundary".to_string();
let expected = "--boundary\r\n\
Content-Disposition: form-data; name=\"key2\"\r\n\
Content-Type: image/bmp\r\n\
hdr3: /a/b/c\r\n\
\r\n\
value2\r\n\
--boundary--\r\n";
form.reader().read_to_end(&mut output).unwrap();
// These prints are for debug purposes in case the test fails
println!(
"START REAL\n{}\nEND REAL",
std::str::from_utf8(&output).unwrap()
);
println!("START EXPECTED\n{expected}\nEND EXPECTED");
assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
}
}

1099
vendor/reqwest/src/blocking/request.rs vendored Normal file

File diff suppressed because it is too large Load Diff

452
vendor/reqwest/src/blocking/response.rs vendored Normal file
View File

@@ -0,0 +1,452 @@
use std::fmt;
use std::io::{self, Read};
use std::mem;
use std::net::SocketAddr;
use std::pin::Pin;
use std::time::Duration;
use bytes::Bytes;
use futures_util::TryStreamExt;
use http;
use http_body_util::BodyExt;
use hyper::header::HeaderMap;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
use super::client::KeepCoreThreadAlive;
use super::wait;
#[cfg(feature = "cookies")]
use crate::cookie;
use crate::{async_impl, StatusCode, Url, Version};
/// A Response to a submitted `Request`.
pub struct Response {
inner: async_impl::Response,
body: Option<Pin<Box<dyn futures_util::io::AsyncRead + Send + Sync>>>,
timeout: Option<Duration>,
_thread_handle: KeepCoreThreadAlive,
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
impl Response {
pub(crate) fn new(
res: async_impl::Response,
timeout: Option<Duration>,
thread: KeepCoreThreadAlive,
) -> Response {
Response {
inner: res,
body: None,
timeout,
_thread_handle: thread,
}
}
/// Get the `StatusCode` of this `Response`.
///
/// # Examples
///
/// Checking for general status class:
///
/// ```rust
/// # #[cfg(feature = "json")]
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let resp = reqwest::blocking::get("http://httpbin.org/get")?;
/// if resp.status().is_success() {
/// println!("success!");
/// } else if resp.status().is_server_error() {
/// println!("server error!");
/// } else {
/// println!("Something else happened. Status: {:?}", resp.status());
/// }
/// # Ok(())
/// # }
/// ```
///
/// Checking for specific status codes:
///
/// ```rust
/// use reqwest::blocking::Client;
/// use reqwest::StatusCode;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::new();
///
/// let resp = client.post("http://httpbin.org/post")
/// .body("possibly too large")
/// .send()?;
///
/// match resp.status() {
/// StatusCode::OK => println!("success!"),
/// StatusCode::PAYLOAD_TOO_LARGE => {
/// println!("Request payload is too large!");
/// }
/// s => println!("Received response status: {s:?}"),
/// };
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn status(&self) -> StatusCode {
self.inner.status()
}
/// Get the `Headers` of this `Response`.
///
/// # Example
///
/// Saving an etag when caching a file:
///
/// ```
/// use reqwest::blocking::Client;
/// use reqwest::header::ETAG;
///
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::new();
///
/// let mut resp = client.get("http://httpbin.org/cache").send()?;
/// if resp.status().is_success() {
/// if let Some(etag) = resp.headers().get(ETAG) {
/// std::fs::write("etag", etag.as_bytes());
/// }
/// let mut file = std::fs::File::create("file")?;
/// resp.copy_to(&mut file)?;
/// }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.inner.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.inner.headers_mut()
}
/// Retrieve the cookies contained in the response.
///
/// Note that invalid 'Set-Cookie' headers will be ignored.
///
/// # Optional
///
/// This requires the optional `cookies` feature to be enabled.
#[cfg(feature = "cookies")]
#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))]
pub fn cookies<'a>(&'a self) -> impl Iterator<Item = cookie::Cookie<'a>> + 'a {
cookie::extract_response_cookies(self.headers()).filter_map(Result::ok)
}
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.inner.version()
}
/// Get the final `Url` of this `Response`.
///
/// # Example
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
/// assert_eq!(resp.url().as_str(), "http://httpbin.org/get");
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn url(&self) -> &Url {
self.inner.url()
}
/// Get the remote address used to get this `Response`.
///
/// # Example
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let resp = reqwest::blocking::get("http://httpbin.org/redirect/1")?;
/// println!("httpbin.org address: {:?}", resp.remote_addr());
/// # Ok(())
/// # }
/// ```
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.inner.remote_addr()
}
/// Returns a reference to the associated extensions.
pub fn extensions(&self) -> &http::Extensions {
self.inner.extensions()
}
/// Returns a mutable reference to the associated extensions.
pub fn extensions_mut(&mut self) -> &mut http::Extensions {
self.inner.extensions_mut()
}
/// Get the content length of the response, if it is known.
///
///
/// This value does not directly represents the value of the `Content-Length`
/// header, but rather the size of the response's body. To read the header's
/// value, please use the [`Response::headers`] method instead.
///
/// Reasons it may not be known:
///
/// - The response does not include a body (e.g. it responds to a `HEAD`
/// request).
/// - The response is gzipped and automatically decoded (thus changing the
/// actual decoded length).
pub fn content_length(&self) -> Option<u64> {
self.inner.content_length()
}
/// Try and deserialize the response body as JSON using `serde`.
///
/// # Optional
///
/// This requires the optional `json` feature enabled.
///
/// # Examples
///
/// ```rust
/// # extern crate reqwest;
/// # extern crate serde;
/// #
/// # use reqwest::Error;
/// # use serde::Deserialize;
/// #
/// // This `derive` requires the `serde` dependency.
/// #[derive(Deserialize)]
/// struct Ip {
/// origin: String,
/// }
///
/// # fn run() -> Result<(), Error> {
/// let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;
/// # Ok(())
/// # }
/// #
/// # fn main() { }
/// ```
///
/// # Errors
///
/// This method fails whenever the response body is not in JSON format,
/// or it cannot be properly deserialized to target type `T`. For more
/// details please see [`serde_json::from_reader`].
///
/// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
})
}
/// Get the full response body as `Bytes`.
///
/// # Example
///
/// ```
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let bytes = reqwest::blocking::get("http://httpbin.org/ip")?.bytes()?;
///
/// println!("bytes: {bytes:?}");
/// # Ok(())
/// # }
/// ```
pub fn bytes(self) -> crate::Result<Bytes> {
wait::timeout(self.inner.bytes(), self.timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
})
}
/// Get the response text.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
/// Encoding is determined from the `charset` parameter of `Content-Type` header,
/// and defaults to `utf-8` if not presented.
///
/// # Note
///
/// If the `charset` feature is disabled the method will only attempt to decode the
/// response as UTF-8, regardless of the given `Content-Type`
///
/// # Example
///
/// ```rust
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::blocking::get("http://httpbin.org/range/26")?.text()?;
/// # Ok(())
/// # }
/// ```
pub fn text(self) -> crate::Result<String> {
wait::timeout(self.inner.text(), self.timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
})
}
/// Get the response text given a specific encoding.
///
/// This method decodes the response body with BOM sniffing
/// and with malformed sequences replaced with the [`char::REPLACEMENT_CHARACTER`].
/// You can provide a default encoding for decoding the raw message, while the
/// `charset` parameter of `Content-Type` header is still prioritized. For more information
/// about the possible encoding name, please go to [`encoding_rs`] docs.
///
/// [`encoding_rs`]: https://docs.rs/encoding_rs/0.8/encoding_rs/#relationship-with-windows-code-pages
///
/// # Optional
///
/// This requires the optional `charset` feature enabled.
///
/// # Example
///
/// ```rust
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let content = reqwest::blocking::get("http://httpbin.org/range/26")?
/// .text_with_charset("utf-8")?;
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "charset")]
#[cfg_attr(docsrs, doc(cfg(feature = "charset")))]
pub fn text_with_charset(self, default_encoding: &str) -> crate::Result<String> {
wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| {
match e {
wait::Waited::TimedOut(e) => crate::error::decode(e),
wait::Waited::Inner(e) => e,
}
})
}
/// Copy the response body into a writer.
///
/// This function internally uses [`std::io::copy`] and hence will continuously read data from
/// the body and then write it into writer in a streaming fashion until EOF is met.
///
/// On success, the total number of bytes that were copied to `writer` is returned.
///
/// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
///
/// # Example
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut resp = reqwest::blocking::get("http://httpbin.org/range/5")?;
/// let mut buf: Vec<u8> = vec![];
/// resp.copy_to(&mut buf)?;
/// assert_eq!(b"abcde", buf.as_slice());
/// # Ok(())
/// # }
/// ```
pub fn copy_to<W: ?Sized>(&mut self, w: &mut W) -> crate::Result<u64>
where
W: io::Write,
{
io::copy(self, w).map_err(crate::error::decode_io)
}
/// Turn a response into an error if the server returned an error.
///
/// # Example
///
/// ```rust,no_run
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let res = reqwest::blocking::get("http://httpbin.org/status/400")?
/// .error_for_status();
/// if let Err(err) = res {
/// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
/// }
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn error_for_status(self) -> crate::Result<Self> {
let Response {
body,
inner,
timeout,
_thread_handle,
} = self;
inner.error_for_status().map(move |inner| Response {
inner,
body,
timeout,
_thread_handle,
})
}
/// Turn a reference to a response into an error if the server returned an error.
///
/// # Example
///
/// ```rust,no_run
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let res = reqwest::blocking::get("http://httpbin.org/status/400")?;
/// let res = res.error_for_status_ref();
/// if let Err(err) = res {
/// assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST));
/// }
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
self.inner.error_for_status_ref().and_then(|_| Ok(self))
}
// private
fn body_mut(&mut self) -> Pin<&mut dyn futures_util::io::AsyncRead> {
if self.body.is_none() {
let body = mem::replace(
self.inner.body_mut(),
async_impl::body::boxed(http_body_util::Empty::new()),
);
self.body = Some(Box::pin(
async_impl::body::Body::wrap(body)
.into_data_stream()
.map_err(crate::error::Error::into_io)
.into_async_read(),
));
}
self.body.as_mut().expect("body was init").as_mut()
}
}
impl Read for Response {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
use futures_util::io::AsyncReadExt;
let timeout = self.timeout;
wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e {
wait::Waited::TimedOut(e) => crate::error::decode(e).into_io(),
wait::Waited::Inner(e) => e,
})
}
}
impl<T: Into<async_impl::body::Body>> From<http::Response<T>> for Response {
fn from(r: http::Response<T>) -> Response {
let response = async_impl::Response::from(r);
Response::new(response, None, KeepCoreThreadAlive::empty())
}
}

82
vendor/reqwest/src/blocking/wait.rs vendored Normal file
View File

@@ -0,0 +1,82 @@
use std::future::Future;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
use std::thread::{self, Thread};
use std::time::Duration;
use tokio::time::Instant;
pub(crate) fn timeout<F, I, E>(fut: F, timeout: Option<Duration>) -> Result<I, Waited<E>>
where
F: Future<Output = Result<I, E>>,
{
enter();
let deadline = timeout.map(|d| {
log::trace!("wait at most {d:?}");
Instant::now() + d
});
let thread = ThreadWaker(thread::current());
// Arc shouldn't be necessary, since `Thread` is reference counted internally,
// but let's just stay safe for now.
let waker = Waker::from(Arc::new(thread));
let mut cx = Context::from_waker(&waker);
futures_util::pin_mut!(fut);
loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(Ok(val)) => return Ok(val),
Poll::Ready(Err(err)) => return Err(Waited::Inner(err)),
Poll::Pending => (), // fallthrough
}
if let Some(deadline) = deadline {
let now = Instant::now();
if now >= deadline {
log::trace!("wait timeout exceeded");
return Err(Waited::TimedOut(crate::error::TimedOut));
}
log::trace!(
"({:?}) park timeout {:?}",
thread::current().id(),
deadline - now
);
thread::park_timeout(deadline - now);
} else {
log::trace!("({:?}) park without timeout", thread::current().id());
thread::park();
}
}
}
#[derive(Debug)]
pub(crate) enum Waited<E> {
TimedOut(crate::error::TimedOut),
Inner(E),
}
struct ThreadWaker(Thread);
impl Wake for ThreadWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.unpark();
}
}
fn enter() {
// Check we aren't already in a runtime
#[cfg(debug_assertions)]
{
let _enter = tokio::runtime::Builder::new_current_thread()
.build()
.expect("build shell runtime")
.enter();
}
}

110
vendor/reqwest/src/config.rs vendored Normal file
View File

@@ -0,0 +1,110 @@
//! The `config` module provides a generic mechanism for loading and managing
//! request-scoped configuration.
//!
//! # Design Overview
//!
//! This module is centered around two abstractions:
//!
//! - The [`RequestConfigValue`] trait, used to associate a config key type with its value type.
//! - The [`RequestConfig`] struct, which wraps an optional value of the type linked via [`RequestConfigValue`].
//!
//! Under the hood, the [`RequestConfig`] struct holds a single value for the associated config type.
//! This value can be conveniently accessed, inserted, or mutated using [`http::Extensions`],
//! enabling type-safe configuration storage and retrieval on a per-request basis.
//!
//! # Motivation
//!
//! The key design benefit is the ability to store multiple config types—potentially even with the same
//! value type (e.g., [`Duration`])—without code duplication or ambiguity. By leveraging trait association,
//! each config key is distinct at the type level, while code for storage and access remains totally generic.
//!
//! # Usage
//!
//! Implement [`RequestConfigValue`] for any marker type you wish to use as a config key,
//! specifying the associated value type. Then use [`RequestConfig<T>`] in [`Extensions`]
//! to set or retrieve config values for each key type in a uniform way.
use std::any::type_name;
use std::fmt::Debug;
use std::time::Duration;
use http::Extensions;
/// This trait is empty and is only used to associate a configuration key type with its
/// corresponding value type.
pub(crate) trait RequestConfigValue: Copy + Clone + 'static {
type Value: Clone + Debug + Send + Sync + 'static;
}
/// RequestConfig carries a request-scoped configuration value.
#[derive(Clone, Copy)]
pub(crate) struct RequestConfig<T: RequestConfigValue>(Option<T::Value>);
impl<T: RequestConfigValue> Default for RequestConfig<T> {
fn default() -> Self {
RequestConfig(None)
}
}
impl<T> RequestConfig<T>
where
T: RequestConfigValue,
{
pub(crate) fn new(v: Option<T::Value>) -> Self {
RequestConfig(v)
}
/// format request config value as struct field.
///
/// We provide this API directly to avoid leak internal value to callers.
pub(crate) fn fmt_as_field(&self, f: &mut std::fmt::DebugStruct<'_, '_>) {
if let Some(v) = &self.0 {
f.field(type_name::<T>(), v);
}
}
/// Retrieve the value from the request-scoped configuration.
///
/// If the request specifies a value, use that value; otherwise, attempt to retrieve it from the current instance (typically a client instance).
pub(crate) fn fetch<'client, 'request>(
&'client self,
ext: &'request Extensions,
) -> Option<&'request T::Value>
where
'client: 'request,
{
ext.get::<RequestConfig<T>>()
.and_then(|v| v.0.as_ref())
.or(self.0.as_ref())
}
/// Retrieve the value from the request's Extensions.
pub(crate) fn get(ext: &Extensions) -> Option<&T::Value> {
ext.get::<RequestConfig<T>>().and_then(|v| v.0.as_ref())
}
/// Retrieve the mutable value from the request's Extensions.
pub(crate) fn get_mut(ext: &mut Extensions) -> &mut Option<T::Value> {
let cfg = ext.get_or_insert_default::<RequestConfig<T>>();
&mut cfg.0
}
}
// ================================
//
// The following sections are all configuration types
// provided by reqwest.
//
// To add a new config:
//
// 1. create a new struct for the config key like `RequestTimeout`.
// 2. implement `RequestConfigValue` for the struct, the `Value` is the config value's type.
//
// ================================
#[derive(Clone, Copy)]
pub(crate) struct TotalTimeout;
impl RequestConfigValue for TotalTimeout {
type Value = Duration;
}

2091
vendor/reqwest/src/connect.rs vendored Normal file

File diff suppressed because it is too large Load Diff

293
vendor/reqwest/src/cookie.rs vendored Normal file
View File

@@ -0,0 +1,293 @@
//! HTTP Cookies
use crate::header::{HeaderValue, SET_COOKIE};
use bytes::Bytes;
use std::convert::TryInto;
use std::fmt;
use std::sync::RwLock;
use std::time::SystemTime;
/// Actions for a persistent cookie store providing session support.
pub trait CookieStore: Send + Sync {
/// Store a set of Set-Cookie header values received from `url`
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url);
/// Get any Cookie values in the store for `url`
fn cookies(&self, url: &url::Url) -> Option<HeaderValue>;
}
/// A single HTTP cookie.
pub struct Cookie<'a>(cookie_crate::Cookie<'a>);
/// A good default `CookieStore` implementation.
///
/// This is the implementation used when simply calling `cookie_store(true)`.
/// This type is exposed to allow creating one and filling it with some
/// existing cookies more easily, before creating a `Client`.
///
/// For more advanced scenarios, such as needing to serialize the store or
/// manipulate it between requests, you may refer to the
/// [reqwest_cookie_store crate](https://crates.io/crates/reqwest_cookie_store).
#[derive(Debug, Default)]
pub struct Jar(RwLock<cookie_store::CookieStore>);
// ===== impl Cookie =====
impl<'a> Cookie<'a> {
fn parse(value: &'a HeaderValue) -> Result<Cookie<'a>, CookieParseError> {
std::str::from_utf8(value.as_bytes())
.map_err(cookie_crate::ParseError::from)
.and_then(cookie_crate::Cookie::parse)
.map_err(CookieParseError)
.map(Cookie)
}
/// The name of the cookie.
pub fn name(&self) -> &str {
self.0.name()
}
/// The value of the cookie.
pub fn value(&self) -> &str {
self.0.value()
}
/// Returns true if the 'HttpOnly' directive is enabled.
pub fn http_only(&self) -> bool {
self.0.http_only().unwrap_or(false)
}
/// Returns true if the 'Secure' directive is enabled.
pub fn secure(&self) -> bool {
self.0.secure().unwrap_or(false)
}
/// Returns true if 'SameSite' directive is 'Lax'.
pub fn same_site_lax(&self) -> bool {
self.0.same_site() == Some(cookie_crate::SameSite::Lax)
}
/// Returns true if 'SameSite' directive is 'Strict'.
pub fn same_site_strict(&self) -> bool {
self.0.same_site() == Some(cookie_crate::SameSite::Strict)
}
/// Returns the path directive of the cookie, if set.
pub fn path(&self) -> Option<&str> {
self.0.path()
}
/// Returns the domain directive of the cookie, if set.
pub fn domain(&self) -> Option<&str> {
self.0.domain()
}
/// Get the Max-Age information.
pub fn max_age(&self) -> Option<std::time::Duration> {
self.0.max_age().map(|d| {
d.try_into()
.expect("time::Duration into std::time::Duration")
})
}
/// The cookie expiration time.
pub fn expires(&self) -> Option<SystemTime> {
match self.0.expires() {
Some(cookie_crate::Expiration::DateTime(offset)) => Some(SystemTime::from(offset)),
None | Some(cookie_crate::Expiration::Session) => None,
}
}
}
impl<'a> fmt::Debug for Cookie<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
pub(crate) fn extract_response_cookie_headers<'a>(
headers: &'a hyper::HeaderMap,
) -> impl Iterator<Item = &'a HeaderValue> + 'a {
headers.get_all(SET_COOKIE).iter()
}
pub(crate) fn extract_response_cookies<'a>(
headers: &'a hyper::HeaderMap,
) -> impl Iterator<Item = Result<Cookie<'a>, CookieParseError>> + 'a {
headers
.get_all(SET_COOKIE)
.iter()
.map(|value| Cookie::parse(value))
}
/// Error representing a parse failure of a 'Set-Cookie' header.
pub(crate) struct CookieParseError(cookie_crate::ParseError);
impl<'a> fmt::Debug for CookieParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> fmt::Display for CookieParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl std::error::Error for CookieParseError {}
// ===== impl Jar =====
impl Jar {
/// Add a cookie to this jar.
///
/// # Example
///
/// ```
/// use reqwest::{cookie::Jar, Url};
///
/// let cookie = "foo=bar; Domain=yolo.local";
/// let url = "https://yolo.local".parse::<Url>().unwrap();
///
/// let jar = Jar::default();
/// jar.add_cookie_str(cookie, &url);
///
/// // and now add to a `ClientBuilder`?
/// ```
pub fn add_cookie_str(&self, cookie: &str, url: &url::Url) {
let cookies = cookie_crate::Cookie::parse(cookie)
.ok()
.map(|c| c.into_owned())
.into_iter();
self.0.write().unwrap().store_response_cookies(cookies, url);
}
}
impl CookieStore for Jar {
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url) {
let iter =
cookie_headers.filter_map(|val| Cookie::parse(val).map(|c| c.0.into_owned()).ok());
self.0.write().unwrap().store_response_cookies(iter, url);
}
fn cookies(&self, url: &url::Url) -> Option<HeaderValue> {
let s = self
.0
.read()
.unwrap()
.get_request_values(url)
.map(|(name, value)| format!("{name}={value}"))
.collect::<Vec<_>>()
.join("; ");
if s.is_empty() {
return None;
}
HeaderValue::from_maybe_shared(Bytes::from(s)).ok()
}
}
pub(crate) mod service {
use crate::cookie;
use http::{Request, Response};
use http_body::Body;
use pin_project_lite::pin_project;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::ready;
use std::task::Context;
use std::task::Poll;
use tower::Service;
use url::Url;
/// A [`Service`] that adds cookie support to a lower-level [`Service`].
#[derive(Clone)]
pub struct CookieService<S> {
inner: S,
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
}
impl<S> CookieService<S> {
/// Create a new [`CookieService`].
pub fn new(inner: S, cookie_store: Option<Arc<dyn cookie::CookieStore>>) -> Self {
Self {
inner,
cookie_store,
}
}
}
impl<ReqBody, ResBody, S> Service<Request<ReqBody>> for CookieService<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ReqBody: Body + Default,
{
type Response = Response<ResBody>;
type Error = S::Error;
type Future = ResponseFuture<S, ReqBody>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
let clone = self.inner.clone();
let mut inner = std::mem::replace(&mut self.inner, clone);
let url = Url::parse(req.uri().to_string().as_str()).expect("invalid URL");
if let Some(cookie_store) = self.cookie_store.as_ref() {
if req.headers().get(crate::header::COOKIE).is_none() {
let headers = req.headers_mut();
crate::util::add_cookie_header(headers, &**cookie_store, &url);
}
}
let cookie_store = self.cookie_store.clone();
ResponseFuture {
future: inner.call(req),
cookie_store,
url,
}
}
}
pin_project! {
#[allow(missing_debug_implementations)]
#[derive(Clone)]
/// A [`Future`] that adds cookie support to a lower-level [`Future`].
pub struct ResponseFuture<S, B>
where
S: Service<Request<B>>,
{
#[pin]
future: S::Future,
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
url: Url,
}
}
impl<S, ReqBody, ResBody> Future for ResponseFuture<S, ReqBody>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ReqBody: Body + Default,
{
type Output = Result<Response<ResBody>, S::Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let cookie_store = self.cookie_store.clone();
let url = self.url.clone();
let res = ready!(self.project().future.as_mut().poll(cx)?);
if let Some(cookie_store) = cookie_store.as_ref() {
let mut cookies = cookie::extract_response_cookie_headers(res.headers()).peekable();
if cookies.peek().is_some() {
cookie_store.set_cookies(&mut cookies, &url);
}
}
Poll::Ready(Ok(res))
}
}
}

32
vendor/reqwest/src/dns/gai.rs vendored Normal file
View File

@@ -0,0 +1,32 @@
use hyper_util::client::legacy::connect::dns::GaiResolver as HyperGaiResolver;
use tower_service::Service;
use crate::dns::{Addrs, Name, Resolve, Resolving};
use crate::error::BoxError;
#[derive(Debug)]
pub struct GaiResolver(HyperGaiResolver);
impl GaiResolver {
pub fn new() -> Self {
Self(HyperGaiResolver::new())
}
}
impl Default for GaiResolver {
fn default() -> Self {
GaiResolver::new()
}
}
impl Resolve for GaiResolver {
fn resolve(&self, name: Name) -> Resolving {
let mut this = self.0.clone();
Box::pin(async move {
this.call(name.0)
.await
.map(|addrs| Box::new(addrs) as Addrs)
.map_err(|err| Box::new(err) as BoxError)
})
}
}

73
vendor/reqwest/src/dns/hickory.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
//! DNS resolution via the [hickory-resolver](https://github.com/hickory-dns/hickory-dns) crate
use hickory_resolver::{
config::LookupIpStrategy, lookup_ip::LookupIpIntoIter, ResolveError, TokioResolver,
};
use once_cell::sync::OnceCell;
use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
use super::{Addrs, Name, Resolve, Resolving};
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
#[derive(Debug, Default, Clone)]
pub(crate) struct HickoryDnsResolver {
/// Since we might not have been called in the context of a
/// Tokio Runtime in initialization, so we must delay the actual
/// construction of the resolver.
state: Arc<OnceCell<TokioResolver>>,
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
#[derive(Debug)]
struct HickoryDnsSystemConfError(ResolveError);
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.clone();
Box::pin(async move {
let resolver = resolver.state.get_or_try_init(new_resolver)?;
let lookup = resolver.lookup_ip(name.as_str()).await?;
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
}
}
impl Iterator for SocketAddrs {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
/// Create a new resolver with the default configuration,
/// which reads from `/etc/resolve.conf`. The options are
/// overridden to look up for both IPv4 and IPv6 addresses
/// to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioResolver, HickoryDnsSystemConfError> {
let mut builder = TokioResolver::builder_tokio().map_err(HickoryDnsSystemConfError)?;
builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(builder.build())
}
impl fmt::Display for HickoryDnsSystemConfError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("error reading DNS system conf for hickory-dns")
}
}
impl std::error::Error for HickoryDnsSystemConfError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}

12
vendor/reqwest/src/dns/mod.rs vendored Normal file
View File

@@ -0,0 +1,12 @@
//! DNS resolution
pub use resolve::{Addrs, Name, Resolve, Resolving};
pub(crate) use resolve::{DnsResolverWithOverrides, DynResolver};
#[cfg(docsrs)]
pub use resolve::IntoResolve;
pub(crate) mod gai;
#[cfg(feature = "hickory-dns")]
pub(crate) mod hickory;
pub(crate) mod resolve;

193
vendor/reqwest/src/dns/resolve.rs vendored Normal file
View File

@@ -0,0 +1,193 @@
use hyper_util::client::legacy::connect::dns::Name as HyperName;
use tower_service::Service;
use std::collections::HashMap;
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
use std::task::{Context, Poll};
use crate::error::BoxError;
/// Alias for an `Iterator` trait object over `SocketAddr`.
pub type Addrs = Box<dyn Iterator<Item = SocketAddr> + Send>;
/// Alias for the `Future` type returned by a DNS resolver.
pub type Resolving = Pin<Box<dyn Future<Output = Result<Addrs, BoxError>> + Send>>;
/// Trait for customizing DNS resolution in reqwest.
pub trait Resolve: Send + Sync {
/// Performs DNS resolution on a `Name`.
/// The return type is a future containing an iterator of `SocketAddr`.
///
/// It differs from `tower_service::Service<Name>` in several ways:
/// * It is assumed that `resolve` will always be ready to poll.
/// * It does not need a mutable reference to `self`.
/// * Since trait objects cannot make use of associated types, it requires
/// wrapping the returned `Future` and its contained `Iterator` with `Box`.
///
/// Explicitly specified port in the URL will override any port in the resolved `SocketAddr`s.
/// Otherwise, port `0` will be replaced by the conventional port for the given scheme (e.g. 80 for http).
fn resolve(&self, name: Name) -> Resolving;
}
/// A name that must be resolved to addresses.
#[derive(Debug)]
pub struct Name(pub(super) HyperName);
/// A more general trait implemented for types implementing `Resolve`.
///
/// Unnameable, only exported to aid seeing what implements this.
pub trait IntoResolve {
#[doc(hidden)]
fn into_resolve(self) -> Arc<dyn Resolve>;
}
impl Name {
/// View the name as a string.
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl FromStr for Name {
type Err = sealed::InvalidNameError;
fn from_str(host: &str) -> Result<Self, Self::Err> {
HyperName::from_str(host)
.map(Name)
.map_err(|_| sealed::InvalidNameError { _ext: () })
}
}
#[derive(Clone)]
pub(crate) struct DynResolver {
resolver: Arc<dyn Resolve>,
}
impl DynResolver {
pub(crate) fn new(resolver: Arc<dyn Resolve>) -> Self {
Self { resolver }
}
#[cfg(feature = "socks")]
pub(crate) fn gai() -> Self {
Self::new(Arc::new(super::gai::GaiResolver::new()))
}
/// Resolve an HTTP host and port, not just a domain name.
///
/// This does the same thing that hyper-util's HttpConnector does, before
/// calling out to its underlying DNS resolver.
#[cfg(feature = "socks")]
pub(crate) async fn http_resolve(
&self,
target: &http::Uri,
) -> Result<impl Iterator<Item = std::net::SocketAddr>, BoxError> {
let host = target.host().ok_or("missing host")?;
let port = target
.port_u16()
.unwrap_or_else(|| match target.scheme_str() {
Some("https") => 443,
Some("socks4") | Some("socks4a") | Some("socks5") | Some("socks5h") => 1080,
_ => 80,
});
let explicit_port = target.port().is_some();
let addrs = self.resolver.resolve(host.parse()?).await?;
Ok(addrs.map(move |mut addr| {
if explicit_port || addr.port() == 0 {
addr.set_port(port);
}
addr
}))
}
}
impl Service<HyperName> for DynResolver {
type Response = Addrs;
type Error = BoxError;
type Future = Resolving;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, name: HyperName) -> Self::Future {
self.resolver.resolve(Name(name))
}
}
pub(crate) struct DnsResolverWithOverrides {
dns_resolver: Arc<dyn Resolve>,
overrides: Arc<HashMap<String, Vec<SocketAddr>>>,
}
impl DnsResolverWithOverrides {
pub(crate) fn new(
dns_resolver: Arc<dyn Resolve>,
overrides: HashMap<String, Vec<SocketAddr>>,
) -> Self {
DnsResolverWithOverrides {
dns_resolver,
overrides: Arc::new(overrides),
}
}
}
impl Resolve for DnsResolverWithOverrides {
fn resolve(&self, name: Name) -> Resolving {
match self.overrides.get(name.as_str()) {
Some(dest) => {
let addrs: Addrs = Box::new(dest.clone().into_iter());
Box::pin(std::future::ready(Ok(addrs)))
}
None => self.dns_resolver.resolve(name),
}
}
}
impl IntoResolve for Arc<dyn Resolve> {
fn into_resolve(self) -> Arc<dyn Resolve> {
self
}
}
impl<R> IntoResolve for Arc<R>
where
R: Resolve + 'static,
{
fn into_resolve(self) -> Arc<dyn Resolve> {
self
}
}
impl<R> IntoResolve for R
where
R: Resolve + 'static,
{
fn into_resolve(self) -> Arc<dyn Resolve> {
Arc::new(self)
}
}
mod sealed {
use std::fmt;
#[derive(Debug)]
pub struct InvalidNameError {
pub(super) _ext: (),
}
impl fmt::Display for InvalidNameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid DNS name")
}
}
impl std::error::Error for InvalidNameError {}
}

462
vendor/reqwest/src/error.rs vendored Normal file
View File

@@ -0,0 +1,462 @@
#![cfg_attr(target_arch = "wasm32", allow(unused))]
use std::error::Error as StdError;
use std::fmt;
use std::io;
use crate::util::Escape;
use crate::{StatusCode, Url};
/// A `Result` alias where the `Err` case is `reqwest::Error`.
pub type Result<T> = std::result::Result<T, Error>;
/// The Errors that may occur when processing a `Request`.
///
/// Note: Errors may include the full URL used to make the `Request`. If the URL
/// contains sensitive information (e.g. an API key as a query parameter), be
/// sure to remove it ([`without_url`](Error::without_url))
pub struct Error {
inner: Box<Inner>,
}
pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
struct Inner {
kind: Kind,
source: Option<BoxError>,
url: Option<Url>,
}
impl Error {
pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
where
E: Into<BoxError>,
{
Error {
inner: Box::new(Inner {
kind,
source: source.map(Into::into),
url: None,
}),
}
}
/// Returns a possible URL related to this error.
///
/// # Examples
///
/// ```
/// # async fn run() {
/// // displays last stop of a redirect loop
/// let response = reqwest::get("http://site.with.redirect.loop").await;
/// if let Err(e) = response {
/// if e.is_redirect() {
/// if let Some(final_stop) = e.url() {
/// println!("redirect loop at {final_stop}");
/// }
/// }
/// }
/// # }
/// ```
pub fn url(&self) -> Option<&Url> {
self.inner.url.as_ref()
}
/// Returns a mutable reference to the URL related to this error
///
/// This is useful if you need to remove sensitive information from the URL
/// (e.g. an API key in the query), but do not want to remove the URL
/// entirely.
pub fn url_mut(&mut self) -> Option<&mut Url> {
self.inner.url.as_mut()
}
/// Add a url related to this error (overwriting any existing)
pub fn with_url(mut self, url: Url) -> Self {
self.inner.url = Some(url);
self
}
pub(crate) fn if_no_url(mut self, f: impl FnOnce() -> Url) -> Self {
if self.inner.url.is_none() {
self.inner.url = Some(f());
}
self
}
/// Strip the related url from this error (if, for example, it contains
/// sensitive information)
pub fn without_url(mut self) -> Self {
self.inner.url = None;
self
}
/// Returns true if the error is from a type Builder.
pub fn is_builder(&self) -> bool {
matches!(self.inner.kind, Kind::Builder)
}
/// Returns true if the error is from a `RedirectPolicy`.
pub fn is_redirect(&self) -> bool {
matches!(self.inner.kind, Kind::Redirect)
}
/// Returns true if the error is from `Response::error_for_status`.
pub fn is_status(&self) -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
matches!(self.inner.kind, Kind::Status(_, _))
}
#[cfg(target_arch = "wasm32")]
{
matches!(self.inner.kind, Kind::Status(_))
}
}
/// Returns true if the error is related to a timeout.
pub fn is_timeout(&self) -> bool {
let mut source = self.source();
while let Some(err) = source {
if err.is::<TimedOut>() {
return true;
}
#[cfg(not(target_arch = "wasm32"))]
if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
if hyper_err.is_timeout() {
return true;
}
}
if let Some(io) = err.downcast_ref::<io::Error>() {
if io.kind() == io::ErrorKind::TimedOut {
return true;
}
}
source = err.source();
}
false
}
/// Returns true if the error is related to the request
pub fn is_request(&self) -> bool {
matches!(self.inner.kind, Kind::Request)
}
#[cfg(not(target_arch = "wasm32"))]
/// Returns true if the error is related to connect
pub fn is_connect(&self) -> bool {
let mut source = self.source();
while let Some(err) = source {
if let Some(hyper_err) = err.downcast_ref::<hyper_util::client::legacy::Error>() {
if hyper_err.is_connect() {
return true;
}
}
source = err.source();
}
false
}
/// Returns true if the error is related to the request or response body
pub fn is_body(&self) -> bool {
matches!(self.inner.kind, Kind::Body)
}
/// Returns true if the error is related to decoding the response's body
pub fn is_decode(&self) -> bool {
matches!(self.inner.kind, Kind::Decode)
}
/// Returns the status code, if the error was generated from a response.
pub fn status(&self) -> Option<StatusCode> {
match self.inner.kind {
#[cfg(target_arch = "wasm32")]
Kind::Status(code) => Some(code),
#[cfg(not(target_arch = "wasm32"))]
Kind::Status(code, _) => Some(code),
_ => None,
}
}
/// Returns true if the error is related to a protocol upgrade request
pub fn is_upgrade(&self) -> bool {
matches!(self.inner.kind, Kind::Upgrade)
}
// private
#[allow(unused)]
pub(crate) fn into_io(self) -> io::Error {
io::Error::new(io::ErrorKind::Other, self)
}
}
/// Converts from external types to reqwest's
/// internal equivalents.
///
/// Currently only is used for `tower::timeout::error::Elapsed`.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn cast_to_internal_error(error: BoxError) -> BoxError {
if error.is::<tower::timeout::error::Elapsed>() {
Box::new(crate::error::TimedOut) as BoxError
} else {
error
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("reqwest::Error");
builder.field("kind", &self.inner.kind);
if let Some(ref url) = self.inner.url {
builder.field("url", &url.as_str());
}
if let Some(ref source) = self.inner.source {
builder.field("source", source);
}
builder.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.inner.kind {
Kind::Builder => f.write_str("builder error")?,
Kind::Request => f.write_str("error sending request")?,
Kind::Body => f.write_str("request or response body error")?,
Kind::Decode => f.write_str("error decoding response body")?,
Kind::Redirect => f.write_str("error following redirect")?,
Kind::Upgrade => f.write_str("error upgrading connection")?,
#[cfg(target_arch = "wasm32")]
Kind::Status(ref code) => {
let prefix = if code.is_client_error() {
"HTTP status client error"
} else {
debug_assert!(code.is_server_error());
"HTTP status server error"
};
write!(f, "{prefix} ({code})")?;
}
#[cfg(not(target_arch = "wasm32"))]
Kind::Status(ref code, ref reason) => {
let prefix = if code.is_client_error() {
"HTTP status client error"
} else {
debug_assert!(code.is_server_error());
"HTTP status server error"
};
if let Some(reason) = reason {
write!(
f,
"{prefix} ({} {})",
code.as_str(),
Escape::new(reason.as_bytes())
)?;
} else {
write!(f, "{prefix} ({code})")?;
}
}
};
if let Some(url) = &self.inner.url {
write!(f, " for url ({url})")?;
}
Ok(())
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner.source.as_ref().map(|e| &**e as _)
}
}
#[cfg(target_arch = "wasm32")]
impl From<crate::error::Error> for wasm_bindgen::JsValue {
fn from(err: Error) -> wasm_bindgen::JsValue {
js_sys::Error::from(err).into()
}
}
#[cfg(target_arch = "wasm32")]
impl From<crate::error::Error> for js_sys::Error {
fn from(err: Error) -> js_sys::Error {
js_sys::Error::new(&format!("{err}"))
}
}
#[derive(Debug)]
pub(crate) enum Kind {
Builder,
Request,
Redirect,
#[cfg(not(target_arch = "wasm32"))]
Status(StatusCode, Option<hyper::ext::ReasonPhrase>),
#[cfg(target_arch = "wasm32")]
Status(StatusCode),
Body,
Decode,
Upgrade,
}
// constructors
pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Builder, Some(e))
}
pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Body, Some(e))
}
pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Decode, Some(e))
}
pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Request, Some(e))
}
pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
Error::new(Kind::Redirect, Some(e)).with_url(url)
}
pub(crate) fn status_code(
url: Url,
status: StatusCode,
#[cfg(not(target_arch = "wasm32"))] reason: Option<hyper::ext::ReasonPhrase>,
) -> Error {
Error::new(
Kind::Status(
status,
#[cfg(not(target_arch = "wasm32"))]
reason,
),
None::<Error>,
)
.with_url(url)
}
pub(crate) fn url_bad_scheme(url: Url) -> Error {
Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
}
pub(crate) fn url_invalid_uri(url: Url) -> Error {
Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
}
if_wasm! {
pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
format!("{js_val:?}").into()
}
}
pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
Error::new(Kind::Upgrade, Some(e))
}
// io::Error helpers
#[allow(unused)]
pub(crate) fn decode_io(e: io::Error) -> Error {
if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
*e.into_inner()
.expect("io::Error::get_ref was Some(_)")
.downcast::<Error>()
.expect("StdError::is() was true")
} else {
decode(e)
}
}
// internal Error "sources"
#[derive(Debug)]
pub(crate) struct TimedOut;
impl fmt::Display for TimedOut {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("operation timed out")
}
}
impl StdError for TimedOut {}
#[derive(Debug)]
pub(crate) struct BadScheme;
impl fmt::Display for BadScheme {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("URL scheme is not allowed")
}
}
impl StdError for BadScheme {}
#[cfg(test)]
mod tests {
use super::*;
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
#[test]
fn test_source_chain() {
let root = Error::new(Kind::Request, None::<Error>);
assert!(root.source().is_none());
let link = super::body(root);
assert!(link.source().is_some());
assert_send::<Error>();
assert_sync::<Error>();
}
#[test]
fn mem_size_of() {
use std::mem::size_of;
assert_eq!(size_of::<Error>(), size_of::<usize>());
}
#[test]
fn roundtrip_io_error() {
let orig = super::request("orig");
// Convert reqwest::Error into an io::Error...
let io = orig.into_io();
// Convert that io::Error back into a reqwest::Error...
let err = super::decode_io(io);
// It should have pulled out the original, not nested it...
match err.inner.kind {
Kind::Request => (),
_ => panic!("{err:?}"),
}
}
#[test]
fn from_unknown_io_error() {
let orig = io::Error::new(io::ErrorKind::Other, "orly");
let err = super::decode_io(orig);
match err.inner.kind {
Kind::Decode => (),
_ => panic!("{err:?}"),
}
}
#[test]
fn is_timeout() {
let err = super::request(super::TimedOut);
assert!(err.is_timeout());
// todo: test `hyper::Error::is_timeout` when we can easily construct one
let io = io::Error::from(io::ErrorKind::TimedOut);
let nested = super::request(io);
assert!(nested.is_timeout());
}
}

117
vendor/reqwest/src/into_url.rs vendored Normal file
View File

@@ -0,0 +1,117 @@
use url::Url;
/// A trait to try to convert some type into a `Url`.
///
/// This trait is "sealed", such that only types within reqwest can
/// implement it.
pub trait IntoUrl: IntoUrlSealed {}
impl IntoUrl for Url {}
impl IntoUrl for String {}
impl<'a> IntoUrl for &'a str {}
impl<'a> IntoUrl for &'a String {}
pub trait IntoUrlSealed {
// Besides parsing as a valid `Url`, the `Url` must be a valid
// `http::Uri`, in that it makes sense to use in a network request.
fn into_url(self) -> crate::Result<Url>;
fn as_str(&self) -> &str;
}
impl IntoUrlSealed for Url {
fn into_url(self) -> crate::Result<Url> {
// With blob url the `self.has_host()` check is always false, so we
// remove the `blob:` scheme and check again if the url is valid.
#[cfg(target_arch = "wasm32")]
if self.scheme() == "blob"
&& self.path().starts_with("http") // Check if the path starts with http or https to avoid validating a `blob:blob:...` url.
&& self.as_str()[5..].into_url().is_ok()
{
return Ok(self);
}
if self.has_host() {
Ok(self)
} else {
Err(crate::error::url_bad_scheme(self))
}
}
fn as_str(&self) -> &str {
self.as_ref()
}
}
impl<'a> IntoUrlSealed for &'a str {
fn into_url(self) -> crate::Result<Url> {
Url::parse(self).map_err(crate::error::builder)?.into_url()
}
fn as_str(&self) -> &str {
self
}
}
impl<'a> IntoUrlSealed for &'a String {
fn into_url(self) -> crate::Result<Url> {
(&**self).into_url()
}
fn as_str(&self) -> &str {
self.as_ref()
}
}
impl IntoUrlSealed for String {
fn into_url(self) -> crate::Result<Url> {
(&*self).into_url()
}
fn as_str(&self) -> &str {
self.as_ref()
}
}
if_hyper! {
pub(crate) fn try_uri(url: &Url) -> crate::Result<http::Uri> {
url.as_str()
.parse()
.map_err(|_| crate::error::url_invalid_uri(url.clone()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn into_url_file_scheme() {
let err = "file:///etc/hosts".into_url().unwrap_err();
assert_eq!(
err.source().unwrap().to_string(),
"URL scheme is not allowed"
);
}
#[test]
fn into_url_blob_scheme() {
let err = "blob:https://example.com".into_url().unwrap_err();
assert_eq!(
err.source().unwrap().to_string(),
"URL scheme is not allowed"
);
}
if_wasm! {
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn into_url_blob_scheme_wasm() {
let url = "blob:http://example.com".into_url().unwrap();
assert_eq!(url.as_str(), "blob:http://example.com");
}
}
}

395
vendor/reqwest/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,395 @@
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(test, deny(warnings))]
//! # reqwest
//!
//! The `reqwest` crate provides a convenient, higher-level HTTP
//! [`Client`][client].
//!
//! It handles many of the things that most people just expect an HTTP client
//! to do for them.
//!
//! - Async and [blocking] Clients
//! - Plain bodies, [JSON](#json), [urlencoded](#forms), [multipart]
//! - Customizable [redirect policy](#redirect-policies)
//! - HTTP [Proxies](#proxies)
//! - Uses [TLS](#tls) by default
//! - Cookies
//!
//! The [`reqwest::Client`][client] is asynchronous (requiring Tokio). For
//! applications wishing to only make a few HTTP requests, the
//! [`reqwest::blocking`](blocking) API may be more convenient.
//!
//! Additional learning resources include:
//!
//! - [The Rust Cookbook](https://rust-lang-nursery.github.io/rust-cookbook/web/clients.html)
//! - [reqwest Repository Examples](https://github.com/seanmonstar/reqwest/tree/master/examples)
//!
//! ## Commercial Support
//!
//! For private advice, support, reviews, access to the maintainer, and the
//! like, reach out for [commercial support][sponsor].
//!
//! ## Making a GET request
//!
//! For a single request, you can use the [`get`][get] shortcut method.
//!
//! ```rust
//! # async fn run() -> Result<(), reqwest::Error> {
//! let body = reqwest::get("https://www.rust-lang.org")
//! .await?
//! .text()
//! .await?;
//!
//! println!("body = {body:?}");
//! # Ok(())
//! # }
//! ```
//!
//! **NOTE**: If you plan to perform multiple requests, it is best to create a
//! [`Client`][client] and reuse it, taking advantage of keep-alive connection
//! pooling.
//!
//! ## Making POST requests (or setting request bodies)
//!
//! There are several ways you can set the body of a request. The basic one is
//! by using the `body()` method of a [`RequestBuilder`][builder]. This lets you set the
//! exact raw bytes of what the body should be. It accepts various types,
//! including `String` and `Vec<u8>`. If you wish to pass a custom
//! type, you can use the `reqwest::Body` constructors.
//!
//! ```rust
//! # use reqwest::Error;
//! #
//! # async fn run() -> Result<(), Error> {
//! let client = reqwest::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .body("the exact body that is sent")
//! .send()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### Forms
//!
//! It's very common to want to send form data in a request body. This can be
//! done with any type that can be serialized into form data.
//!
//! This can be an array of tuples, or a `HashMap`, or a custom type that
//! implements [`Serialize`][serde].
//!
//! ```rust
//! # use reqwest::Error;
//! #
//! # async fn run() -> Result<(), Error> {
//! // This will POST a body of `foo=bar&baz=quux`
//! let params = [("foo", "bar"), ("baz", "quux")];
//! let client = reqwest::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .form(&params)
//! .send()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ### JSON
//!
//! There is also a `json` method helper on the [`RequestBuilder`][builder] that works in
//! a similar fashion the `form` method. It can take any value that can be
//! serialized into JSON. The feature `json` is required.
//!
//! ```rust
//! # use reqwest::Error;
//! # use std::collections::HashMap;
//! #
//! # #[cfg(feature = "json")]
//! # async fn run() -> Result<(), Error> {
//! // This will POST a body of `{"lang":"rust","body":"json"}`
//! let mut map = HashMap::new();
//! map.insert("lang", "rust");
//! map.insert("body", "json");
//!
//! let client = reqwest::Client::new();
//! let res = client.post("http://httpbin.org/post")
//! .json(&map)
//! .send()
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Redirect Policies
//!
//! By default, a `Client` will automatically handle HTTP redirects, having a
//! maximum redirect chain of 10 hops. To customize this behavior, a
//! [`redirect::Policy`][redirect] can be used with a `ClientBuilder`.
//!
//! ## Cookies
//!
//! The automatic storing and sending of session cookies can be enabled with
//! the [`cookie_store`][ClientBuilder::cookie_store] method on `ClientBuilder`.
//!
//! ## Proxies
//!
//! **NOTE**: System proxies are enabled by default.
//!
//! System proxies look in environment variables to set HTTP or HTTPS proxies.
//!
//! `HTTP_PROXY` or `http_proxy` provide HTTP proxies for HTTP connections while
//! `HTTPS_PROXY` or `https_proxy` provide HTTPS proxies for HTTPS connections.
//! `ALL_PROXY` or `all_proxy` provide proxies for both HTTP and HTTPS connections.
//! If both the all proxy and HTTP or HTTPS proxy variables are set the more specific
//! HTTP or HTTPS proxies take precedence.
//!
//! These can be overwritten by adding a [`Proxy`] to `ClientBuilder`
//! i.e. `let proxy = reqwest::Proxy::http("https://secure.example")?;`
//! or disabled by calling `ClientBuilder::no_proxy()`.
//!
//! `socks` feature is required if you have configured socks proxy like this:
//!
//! ```bash
//! export https_proxy=socks5://127.0.0.1:1086
//! ```
//!
//! ## TLS
//!
//! A `Client` will use transport layer security (TLS) by default to connect to
//! HTTPS destinations.
//!
//! - Additional server certificates can be configured on a `ClientBuilder`
//! with the [`Certificate`] type.
//! - Client certificates can be added to a `ClientBuilder` with the
//! [`Identity`] type.
//! - Various parts of TLS can also be configured or even disabled on the
//! `ClientBuilder`.
//!
//! See more details in the [`tls`] module.
//!
//! ## WASM
//!
//! The Client implementation automatically switches to the WASM one when the target_arch is wasm32,
//! the usage is basically the same as the async api. Some of the features are disabled in wasm
//! : [`tls`], [`cookie`], [`blocking`], as well as various `ClientBuilder` methods such as `timeout()` and `connector_layer()`.
//!
//! TLS and cookies are provided through the browser environment, so reqwest can issue TLS requests with cookies,
//! but has limited configuration.
//!
//! ## Optional Features
//!
//! The following are a list of [Cargo features][cargo-features] that can be
//! enabled or disabled:
//!
//! - **http2** *(enabled by default)*: Enables HTTP/2 support.
//! - **default-tls** *(enabled by default)*: Provides TLS support to connect
//! over HTTPS.
//! - **native-tls**: Enables TLS functionality provided by `native-tls`.
//! - **native-tls-vendored**: Enables the `vendored` feature of `native-tls`.
//! - **native-tls-alpn**: Enables the `alpn` feature of `native-tls`.
//! - **rustls-tls**: Enables TLS functionality provided by `rustls`.
//! Equivalent to `rustls-tls-webpki-roots`.
//! - **rustls-tls-manual-roots**: Enables TLS functionality provided by `rustls`,
//! without setting any root certificates. Roots have to be specified manually.
//! - **rustls-tls-webpki-roots**: Enables TLS functionality provided by `rustls`,
//! while using root certificates from the `webpki-roots` crate.
//! - **rustls-tls-native-roots**: Enables TLS functionality provided by `rustls`,
//! while using root certificates from the `rustls-native-certs` crate.
//! - **blocking**: Provides the [blocking][] client API.
//! - **charset** *(enabled by default)*: Improved support for decoding text.
//! - **cookies**: Provides cookie session support.
//! - **gzip**: Provides response body gzip decompression.
//! - **brotli**: Provides response body brotli decompression.
//! - **zstd**: Provides response body zstd decompression.
//! - **deflate**: Provides response body deflate decompression.
//! - **json**: Provides serialization and deserialization for JSON bodies.
//! - **multipart**: Provides functionality for multipart forms.
//! - **stream**: Adds support for `futures::Stream`.
//! - **socks**: Provides SOCKS5 proxy support.
//! - **hickory-dns**: Enables a hickory-dns async resolver instead of default
//! threadpool using `getaddrinfo`.
//! - **system-proxy** *(enabled by default)*: Use Windows and macOS system
//! proxy settings automatically.
//!
//! ## Unstable Features
//!
//! Some feature flags require additional opt-in by the application, by setting
//! a `reqwest_unstable` flag.
//!
//! - **http3** *(unstable)*: Enables support for sending HTTP/3 requests.
//!
//! These features are unstable, and experimental. Details about them may be
//! changed in patch releases.
//!
//! You can pass such a flag to the compiler via `.cargo/config`, or
//! environment variables, such as:
//!
//! ```notrust
//! RUSTFLAGS="--cfg reqwest_unstable" cargo build
//! ```
//!
//! ## Sponsors
//!
//! Support this project by becoming a [sponsor][].
//!
//! [hyper]: https://hyper.rs
//! [blocking]: ./blocking/index.html
//! [client]: ./struct.Client.html
//! [response]: ./struct.Response.html
//! [get]: ./fn.get.html
//! [builder]: ./struct.RequestBuilder.html
//! [serde]: http://serde.rs
//! [redirect]: crate::redirect
//! [Proxy]: ./struct.Proxy.html
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
//! [sponsor]: https://seanmonstar.com/sponsor
#[cfg(all(feature = "http3", not(reqwest_unstable)))]
compile_error!(
"\
The `http3` feature is unstable, and requires the \
`RUSTFLAGS='--cfg reqwest_unstable'` environment variable to be set.\
"
);
// Ignore `unused_crate_dependencies` warnings.
// Used in many features that they're not worth making it optional.
use futures_core as _;
use sync_wrapper as _;
macro_rules! if_wasm {
($($item:item)*) => {$(
#[cfg(target_arch = "wasm32")]
$item
)*}
}
macro_rules! if_hyper {
($($item:item)*) => {$(
#[cfg(not(target_arch = "wasm32"))]
$item
)*}
}
pub use http::header;
pub use http::Method;
pub use http::{StatusCode, Version};
pub use url::Url;
// universal mods
#[macro_use]
mod error;
// TODO: remove `if_hyper` if wasm has been migrated to new config system.
if_hyper! {
mod config;
}
mod into_url;
mod response;
pub use self::error::{Error, Result};
pub use self::into_url::IntoUrl;
pub use self::response::ResponseBuilderExt;
/// Shortcut method to quickly make a `GET` request.
///
/// See also the methods on the [`reqwest::Response`](./struct.Response.html)
/// type.
///
/// **NOTE**: This function creates a new internal `Client` on each call,
/// and so should not be used if making many requests. Create a
/// [`Client`](./struct.Client.html) instead.
///
/// # Examples
///
/// ```rust
/// # async fn run() -> Result<(), reqwest::Error> {
/// let body = reqwest::get("https://www.rust-lang.org").await?
/// .text().await?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// This function fails if:
///
/// - native TLS backend cannot be initialized
/// - supplied `Url` cannot be parsed
/// - there was an error while sending request
/// - redirect limit was exhausted
pub async fn get<T: IntoUrl>(url: T) -> crate::Result<Response> {
Client::builder().build()?.get(url).send().await
}
fn _assert_impls() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn assert_clone<T: Clone>() {}
assert_send::<Client>();
assert_sync::<Client>();
assert_clone::<Client>();
assert_send::<Request>();
assert_send::<RequestBuilder>();
#[cfg(not(target_arch = "wasm32"))]
{
assert_send::<Response>();
}
assert_send::<Error>();
assert_sync::<Error>();
assert_send::<Body>();
assert_sync::<Body>();
}
if_hyper! {
#[cfg(test)]
#[macro_use]
extern crate doc_comment;
#[cfg(test)]
doctest!("../README.md");
pub use self::async_impl::{
Body, Client, ClientBuilder, Request, RequestBuilder, Response, Upgraded,
};
pub use self::proxy::{Proxy,NoProxy};
#[cfg(feature = "__tls")]
// Re-exports, to be removed in a future release
pub use tls::{Certificate, Identity};
#[cfg(feature = "multipart")]
pub use self::async_impl::multipart;
mod async_impl;
#[cfg(feature = "blocking")]
pub mod blocking;
mod connect;
#[cfg(feature = "cookies")]
pub mod cookie;
pub mod dns;
mod proxy;
pub mod redirect;
pub mod retry;
#[cfg(feature = "__tls")]
pub mod tls;
mod util;
#[cfg(docsrs)]
pub use connect::uds::UnixSocketProvider;
}
if_wasm! {
mod wasm;
mod util;
pub use self::wasm::{Body, Client, ClientBuilder, Request, RequestBuilder, Response};
#[cfg(feature = "multipart")]
pub use self::wasm::multipart;
}

952
vendor/reqwest/src/proxy.rs vendored Normal file
View File

@@ -0,0 +1,952 @@
use std::error::Error;
use std::fmt;
use std::sync::Arc;
use http::uri::Scheme;
use http::{header::HeaderValue, HeaderMap, Uri};
use hyper_util::client::proxy::matcher;
use crate::into_url::{IntoUrl, IntoUrlSealed};
use crate::Url;
// # Internals
//
// This module is a couple pieces:
//
// - The public builder API
// - The internal built types that our Connector knows how to use.
//
// The user creates a builder (`reqwest::Proxy`), and configures any extras.
// Once that type is passed to the `ClientBuilder`, we convert it into the
// built matcher types, making use of `hyper-util`'s matchers.
/// Configuration of a proxy that a `Client` should pass requests to.
///
/// A `Proxy` has a couple pieces to it:
///
/// - a URL of how to talk to the proxy
/// - rules on what `Client` requests should be directed to the proxy
///
/// For instance, let's look at `Proxy::http`:
///
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::http("https://secure.example")?;
/// # Ok(())
/// # }
/// ```
///
/// This proxy will intercept all HTTP requests, and make use of the proxy
/// at `https://secure.example`. A request to `http://hyper.rs` will talk
/// to your proxy. A request to `https://hyper.rs` will not.
///
/// Multiple `Proxy` rules can be configured for a `Client`. The `Client` will
/// check each `Proxy` in the order it was added. This could mean that a
/// `Proxy` added first with eager intercept rules, such as `Proxy::all`,
/// would prevent a `Proxy` later in the list from ever working, so take care.
///
/// By enabling the `"socks"` feature it is possible to use a socks proxy:
/// ```rust
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::http("socks5://192.168.1.1:9000")?;
/// # Ok(())
/// # }
/// ```
#[derive(Clone)]
pub struct Proxy {
extra: Extra,
intercept: Intercept,
no_proxy: Option<NoProxy>,
}
/// A configuration for filtering out requests that shouldn't be proxied
#[derive(Clone, Debug, Default)]
pub struct NoProxy {
inner: String,
}
#[derive(Clone)]
struct Extra {
auth: Option<HeaderValue>,
misc: Option<HeaderMap>,
}
// ===== Internal =====
pub(crate) struct Matcher {
inner: Matcher_,
extra: Extra,
maybe_has_http_auth: bool,
maybe_has_http_custom_headers: bool,
}
enum Matcher_ {
Util(matcher::Matcher),
Custom(Custom),
}
/// Our own type, wrapping an `Intercept`, since we may have a few additional
/// pieces attached thanks to `reqwest`s extra proxy configuration.
pub(crate) struct Intercepted {
inner: matcher::Intercept,
/// This is because of `reqwest::Proxy`'s design which allows configuring
/// an explicit auth, besides what might have been in the URL (or Custom).
extra: Extra,
}
/*
impl ProxyScheme {
fn maybe_http_auth(&self) -> Option<&HeaderValue> {
match self {
ProxyScheme::Http { auth, .. } | ProxyScheme::Https { auth, .. } => auth.as_ref(),
#[cfg(feature = "socks")]
_ => None,
}
}
fn maybe_http_custom_headers(&self) -> Option<&HeaderMap> {
match self {
ProxyScheme::Http { misc, .. } | ProxyScheme::Https { misc, .. } => misc.as_ref(),
#[cfg(feature = "socks")]
_ => None,
}
}
}
*/
/// Trait used for converting into a proxy scheme. This trait supports
/// parsing from a URL-like type, whilst also supporting proxy schemes
/// built directly using the factory methods.
pub trait IntoProxy {
fn into_proxy(self) -> crate::Result<Url>;
}
impl<S: IntoUrl> IntoProxy for S {
fn into_proxy(self) -> crate::Result<Url> {
match self.as_str().into_url() {
Ok(mut url) => {
// If the scheme is a SOCKS protocol and no port is specified, set the default
if url.port().is_none()
&& matches!(url.scheme(), "socks4" | "socks4a" | "socks5" | "socks5h")
{
let _ = url.set_port(Some(1080));
}
Ok(url)
}
Err(e) => {
let mut presumed_to_have_scheme = true;
let mut source = e.source();
while let Some(err) = source {
if let Some(parse_error) = err.downcast_ref::<url::ParseError>() {
if *parse_error == url::ParseError::RelativeUrlWithoutBase {
presumed_to_have_scheme = false;
break;
}
} else if err.downcast_ref::<crate::error::BadScheme>().is_some() {
presumed_to_have_scheme = false;
break;
}
source = err.source();
}
if presumed_to_have_scheme {
return Err(crate::error::builder(e));
}
// the issue could have been caused by a missing scheme, so we try adding http://
let try_this = format!("http://{}", self.as_str());
try_this.into_url().map_err(|_| {
// return the original error
crate::error::builder(e)
})
}
}
}
}
// These bounds are accidentally leaked by the blanket impl of IntoProxy
// for all types that implement IntoUrl. So, this function exists to detect
// if we were to break those bounds for a user.
fn _implied_bounds() {
fn prox<T: IntoProxy>(_t: T) {}
fn url<T: IntoUrl>(t: T) {
prox(t);
}
}
impl Proxy {
/// Proxy all HTTP traffic to the passed URL.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::http("https://my.prox")?)
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn http<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::Http(proxy_scheme.into_proxy()?)))
}
/// Proxy all HTTPS traffic to the passed URL.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::https("https://example.prox:4545")?)
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn https<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::Https(proxy_scheme.into_proxy()?)))
}
/// Proxy **all** traffic to the passed URL.
///
/// "All" refers to `https` and `http` URLs. Other schemes are not
/// recognized by reqwest.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::all("http://pro.xy")?)
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn all<U: IntoProxy>(proxy_scheme: U) -> crate::Result<Proxy> {
Ok(Proxy::new(Intercept::All(proxy_scheme.into_proxy()?)))
}
/// Provide a custom function to determine what traffic to proxy to where.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let target = reqwest::Url::parse("https://my.prox")?;
/// let client = reqwest::Client::builder()
/// .proxy(reqwest::Proxy::custom(move |url| {
/// if url.host_str() == Some("hyper.rs") {
/// Some(target.clone())
/// } else {
/// None
/// }
/// }))
/// .build()?;
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn custom<F, U: IntoProxy>(fun: F) -> Proxy
where
F: Fn(&Url) -> Option<U> + Send + Sync + 'static,
{
Proxy::new(Intercept::Custom(Custom {
func: Arc::new(move |url| fun(url).map(IntoProxy::into_proxy)),
no_proxy: None,
}))
}
fn new(intercept: Intercept) -> Proxy {
Proxy {
extra: Extra {
auth: None,
misc: None,
},
intercept,
no_proxy: None,
}
}
/// Set the `Proxy-Authorization` header using Basic auth.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .basic_auth("Aladdin", "open sesame");
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
match self.intercept {
Intercept::All(ref mut s)
| Intercept::Http(ref mut s)
| Intercept::Https(ref mut s) => url_auth(s, username, password),
Intercept::Custom(_) => {
let header = encode_basic_auth(username, password);
self.extra.auth = Some(header);
}
}
self
}
/// Set the `Proxy-Authorization` header to a specified value.
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # use reqwest::header::*;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .custom_http_auth(HeaderValue::from_static("justletmeinalreadyplease"));
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn custom_http_auth(mut self, header_value: HeaderValue) -> Proxy {
self.extra.auth = Some(header_value);
self
}
/// Adds a Custom Headers to Proxy
/// Adds custom headers to this Proxy
///
/// # Example
/// ```
/// # extern crate reqwest;
/// # use reqwest::header::*;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let mut headers = HeaderMap::new();
/// headers.insert(USER_AGENT, "reqwest".parse().unwrap());
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .headers(headers);
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn headers(mut self, headers: HeaderMap) -> Proxy {
match self.intercept {
Intercept::All(_) | Intercept::Http(_) | Intercept::Https(_) | Intercept::Custom(_) => {
self.extra.misc = Some(headers);
}
}
self
}
/// Adds a `No Proxy` exclusion list to this Proxy
///
/// # Example
///
/// ```
/// # extern crate reqwest;
/// # fn run() -> Result<(), Box<dyn std::error::Error>> {
/// let proxy = reqwest::Proxy::https("http://localhost:1234")?
/// .no_proxy(reqwest::NoProxy::from_string("direct.tld, sub.direct2.tld"));
/// # Ok(())
/// # }
/// # fn main() {}
/// ```
pub fn no_proxy(mut self, no_proxy: Option<NoProxy>) -> Proxy {
self.no_proxy = no_proxy;
self
}
pub(crate) fn into_matcher(self) -> Matcher {
let Proxy {
intercept,
extra,
no_proxy,
} = self;
let maybe_has_http_auth;
let maybe_has_http_custom_headers;
let inner = match intercept {
Intercept::All(url) => {
maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
maybe_has_http_custom_headers =
cache_maybe_has_http_custom_headers(&url, &extra.misc);
Matcher_::Util(
matcher::Matcher::builder()
.all(String::from(url))
.no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
.build(),
)
}
Intercept::Http(url) => {
maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
maybe_has_http_custom_headers =
cache_maybe_has_http_custom_headers(&url, &extra.misc);
Matcher_::Util(
matcher::Matcher::builder()
.http(String::from(url))
.no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
.build(),
)
}
Intercept::Https(url) => {
maybe_has_http_auth = cache_maybe_has_http_auth(&url, &extra.auth);
maybe_has_http_custom_headers =
cache_maybe_has_http_custom_headers(&url, &extra.misc);
Matcher_::Util(
matcher::Matcher::builder()
.https(String::from(url))
.no(no_proxy.as_ref().map(|n| n.inner.as_ref()).unwrap_or(""))
.build(),
)
}
Intercept::Custom(mut custom) => {
maybe_has_http_auth = true; // never know
maybe_has_http_custom_headers = true;
custom.no_proxy = no_proxy;
Matcher_::Custom(custom)
}
};
Matcher {
inner,
extra,
maybe_has_http_auth,
maybe_has_http_custom_headers,
}
}
/*
pub(crate) fn maybe_has_http_auth(&self) -> bool {
match &self.intercept {
Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().is_some(),
// Custom *may* match 'http', so assume so.
Intercept::Custom(_) => true,
Intercept::System(system) => system
.get("http")
.and_then(|s| s.maybe_http_auth())
.is_some(),
Intercept::Https(_) => false,
}
}
pub(crate) fn http_basic_auth<D: Dst>(&self, uri: &D) -> Option<HeaderValue> {
match &self.intercept {
Intercept::All(p) | Intercept::Http(p) => p.maybe_http_auth().cloned(),
Intercept::System(system) => system
.get("http")
.and_then(|s| s.maybe_http_auth().cloned()),
Intercept::Custom(custom) => {
custom.call(uri).and_then(|s| s.maybe_http_auth().cloned())
}
Intercept::Https(_) => None,
}
}
*/
}
fn cache_maybe_has_http_auth(url: &Url, extra: &Option<HeaderValue>) -> bool {
(url.scheme() == "http" || url.scheme() == "https")
&& (url.username().len() > 0 || url.password().is_some() || extra.is_some())
}
fn cache_maybe_has_http_custom_headers(url: &Url, extra: &Option<HeaderMap>) -> bool {
(url.scheme() == "http" || url.scheme() == "https") && extra.is_some()
}
impl fmt::Debug for Proxy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Proxy")
.field(&self.intercept)
.field(&self.no_proxy)
.finish()
}
}
impl NoProxy {
/// Returns a new no-proxy configuration based on environment variables (or `None` if no variables are set)
/// see [self::NoProxy::from_string()] for the string format
pub fn from_env() -> Option<NoProxy> {
let raw = std::env::var("NO_PROXY")
.or_else(|_| std::env::var("no_proxy"))
.ok()?;
// Per the docs, this returns `None` if no environment variable is set. We can only reach
// here if an env var is set, so we return `Some(NoProxy::default)` if `from_string`
// returns None, which occurs with an empty string.
Some(Self::from_string(&raw).unwrap_or_default())
}
/// Returns a new no-proxy configuration based on a `no_proxy` string (or `None` if no variables
/// are set)
/// The rules are as follows:
/// * The environment variable `NO_PROXY` is checked, if it is not set, `no_proxy` is checked
/// * If neither environment variable is set, `None` is returned
/// * Entries are expected to be comma-separated (whitespace between entries is ignored)
/// * IP addresses (both IPv4 and IPv6) are allowed, as are optional subnet masks (by adding /size,
/// for example "`192.168.1.0/24`").
/// * An entry "`*`" matches all hostnames (this is the only wildcard allowed)
/// * Any other entry is considered a domain name (and may contain a leading dot, for example `google.com`
/// and `.google.com` are equivalent) and would match both that domain AND all subdomains.
///
/// For example, if `"NO_PROXY=google.com, 192.168.1.0/24"` was set, all the following would match
/// (and therefore would bypass the proxy):
/// * `http://google.com/`
/// * `http://www.google.com/`
/// * `http://192.168.1.42/`
///
/// The URL `http://notgoogle.com/` would not match.
pub fn from_string(no_proxy_list: &str) -> Option<Self> {
// lazy parsed, to not make the type public in hyper-util
Some(NoProxy {
inner: no_proxy_list.into(),
})
}
}
impl Matcher {
pub(crate) fn system() -> Self {
Self {
inner: Matcher_::Util(matcher::Matcher::from_system()),
extra: Extra {
auth: None,
misc: None,
},
// maybe env vars have auth!
maybe_has_http_auth: true,
maybe_has_http_custom_headers: true,
}
}
pub(crate) fn intercept(&self, dst: &Uri) -> Option<Intercepted> {
let inner = match self.inner {
Matcher_::Util(ref m) => m.intercept(dst),
Matcher_::Custom(ref c) => c.call(dst),
};
inner.map(|inner| Intercepted {
inner,
extra: self.extra.clone(),
})
}
/// Return whether this matcher might provide HTTP (not s) auth.
///
/// This is very specific. If this proxy needs auth to be part of a Forward
/// request (instead of a tunnel), this should return true.
///
/// If it's not sure, this should return true.
///
/// This is meant as a hint to allow skipping a more expensive check
/// (calling `intercept()`) if it will never need auth when Forwarding.
pub(crate) fn maybe_has_http_auth(&self) -> bool {
self.maybe_has_http_auth
}
pub(crate) fn http_non_tunnel_basic_auth(&self, dst: &Uri) -> Option<HeaderValue> {
if let Some(proxy) = self.intercept(dst) {
let scheme = proxy.uri().scheme();
if scheme == Some(&Scheme::HTTP) || scheme == Some(&Scheme::HTTPS) {
return proxy.basic_auth().cloned();
}
}
None
}
pub(crate) fn maybe_has_http_custom_headers(&self) -> bool {
self.maybe_has_http_custom_headers
}
pub(crate) fn http_non_tunnel_custom_headers(&self, dst: &Uri) -> Option<HeaderMap> {
if let Some(proxy) = self.intercept(dst) {
let scheme = proxy.uri().scheme();
if scheme == Some(&Scheme::HTTP) || scheme == Some(&Scheme::HTTPS) {
return proxy.custom_headers().cloned();
}
}
None
}
}
impl fmt::Debug for Matcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.inner {
Matcher_::Util(ref m) => m.fmt(f),
Matcher_::Custom(ref m) => m.fmt(f),
}
}
}
impl Intercepted {
pub(crate) fn uri(&self) -> &http::Uri {
self.inner.uri()
}
pub(crate) fn basic_auth(&self) -> Option<&HeaderValue> {
if let Some(ref val) = self.extra.auth {
return Some(val);
}
self.inner.basic_auth()
}
pub(crate) fn custom_headers(&self) -> Option<&HeaderMap> {
if let Some(ref val) = self.extra.misc {
return Some(val);
}
None
}
#[cfg(feature = "socks")]
pub(crate) fn raw_auth(&self) -> Option<(&str, &str)> {
self.inner.raw_auth()
}
}
impl fmt::Debug for Intercepted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.uri().fmt(f)
}
}
/*
impl ProxyScheme {
/// Use a username and password when connecting to the proxy server
fn with_basic_auth<T: Into<String>, U: Into<String>>(
mut self,
username: T,
password: U,
) -> Self {
self.set_basic_auth(username, password);
self
}
fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
match *self {
ProxyScheme::Http { ref mut auth, .. } => {
let header = encode_basic_auth(&username.into(), &password.into());
*auth = Some(header);
}
ProxyScheme::Https { ref mut auth, .. } => {
let header = encode_basic_auth(&username.into(), &password.into());
*auth = Some(header);
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {
panic!("Socks4 is not supported for this method")
}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { ref mut auth, .. } => {
*auth = Some((username.into(), password.into()));
}
}
}
fn set_custom_http_auth(&mut self, header_value: HeaderValue) {
match *self {
ProxyScheme::Http { ref mut auth, .. } => {
*auth = Some(header_value);
}
ProxyScheme::Https { ref mut auth, .. } => {
*auth = Some(header_value);
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {
panic!("Socks4 is not supported for this method")
}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { .. } => {
panic!("Socks5 is not supported for this method")
}
}
}
fn set_custom_headers(&mut self, headers: HeaderMap) {
match *self {
ProxyScheme::Http { ref mut misc, .. } => {
misc.get_or_insert_with(HeaderMap::new).extend(headers)
}
ProxyScheme::Https { ref mut misc, .. } => {
misc.get_or_insert_with(HeaderMap::new).extend(headers)
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {
panic!("Socks4 is not supported for this method")
}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { .. } => {
panic!("Socks5 is not supported for this method")
}
}
}
fn if_no_auth(mut self, update: &Option<HeaderValue>) -> Self {
match self {
ProxyScheme::Http { ref mut auth, .. } => {
if auth.is_none() {
*auth = update.clone();
}
}
ProxyScheme::Https { ref mut auth, .. } => {
if auth.is_none() {
*auth = update.clone();
}
}
#[cfg(feature = "socks")]
ProxyScheme::Socks4 { .. } => {}
#[cfg(feature = "socks")]
ProxyScheme::Socks5 { .. } => {}
}
self
}
/// Convert a URL into a proxy scheme
///
/// Supported schemes: HTTP, HTTPS, (SOCKS4, SOCKS5, SOCKS5H if `socks` feature is enabled).
// Private for now...
fn parse(url: Url) -> crate::Result<Self> {
use url::Position;
// Resolve URL to a host and port
#[cfg(feature = "socks")]
let to_addr = || {
let addrs = url
.socket_addrs(|| match url.scheme() {
"socks4" | "socks4a" | "socks5" | "socks5h" => Some(1080),
_ => None,
})
.map_err(crate::error::builder)?;
addrs
.into_iter()
.next()
.ok_or_else(|| crate::error::builder("unknown proxy scheme"))
};
let mut scheme = match url.scheme() {
"http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
"https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
#[cfg(feature = "socks")]
"socks4" => Self::socks4(to_addr()?)?,
#[cfg(feature = "socks")]
"socks4a" => Self::socks4a(to_addr()?)?,
#[cfg(feature = "socks")]
"socks5" => Self::socks5(to_addr()?)?,
#[cfg(feature = "socks")]
"socks5h" => Self::socks5h(to_addr()?)?,
_ => return Err(crate::error::builder("unknown proxy scheme")),
};
if let Some(pwd) = url.password() {
let decoded_username = percent_decode(url.username().as_bytes()).decode_utf8_lossy();
let decoded_password = percent_decode(pwd.as_bytes()).decode_utf8_lossy();
scheme = scheme.with_basic_auth(decoded_username, decoded_password);
}
Ok(scheme)
}
}
*/
#[derive(Clone, Debug)]
enum Intercept {
All(Url),
Http(Url),
Https(Url),
Custom(Custom),
}
fn url_auth(url: &mut Url, username: &str, password: &str) {
url.set_username(username).expect("is a base");
url.set_password(Some(password)).expect("is a base");
}
#[derive(Clone)]
struct Custom {
func: Arc<dyn Fn(&Url) -> Option<crate::Result<Url>> + Send + Sync + 'static>,
no_proxy: Option<NoProxy>,
}
impl Custom {
fn call(&self, uri: &http::Uri) -> Option<matcher::Intercept> {
let url = format!(
"{}://{}{}{}",
uri.scheme()?,
uri.host()?,
uri.port().map_or("", |_| ":"),
uri.port().map_or(String::new(), |p| p.to_string())
)
.parse()
.expect("should be valid Url");
(self.func)(&url)
.and_then(|result| result.ok())
.and_then(|target| {
let m = matcher::Matcher::builder()
.all(String::from(target))
.build();
m.intercept(uri)
})
//.map(|scheme| scheme.if_no_auth(&self.auth))
}
}
impl fmt::Debug for Custom {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("_")
}
}
pub(crate) fn encode_basic_auth(username: &str, password: &str) -> HeaderValue {
crate::util::basic_auth(username, Some(password))
}
#[cfg(test)]
mod tests {
use super::*;
fn url(s: &str) -> http::Uri {
s.parse().unwrap()
}
fn intercepted_uri(p: &Matcher, s: &str) -> Uri {
p.intercept(&s.parse().unwrap()).unwrap().uri().clone()
}
#[test]
fn test_http() {
let target = "http://example.domain/";
let p = Proxy::http(target).unwrap().into_matcher();
let http = "http://hyper.rs";
let other = "https://hyper.rs";
assert_eq!(intercepted_uri(&p, http), target);
assert!(p.intercept(&url(other)).is_none());
}
#[test]
fn test_https() {
let target = "http://example.domain/";
let p = Proxy::https(target).unwrap().into_matcher();
let http = "http://hyper.rs";
let other = "https://hyper.rs";
assert!(p.intercept(&url(http)).is_none());
assert_eq!(intercepted_uri(&p, other), target);
}
#[test]
fn test_all() {
let target = "http://example.domain/";
let p = Proxy::all(target).unwrap().into_matcher();
let http = "http://hyper.rs";
let https = "https://hyper.rs";
// no longer supported
//let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs";
assert_eq!(intercepted_uri(&p, http), target);
assert_eq!(intercepted_uri(&p, https), target);
//assert_eq!(intercepted_uri(&p, other), target);
}
#[test]
fn test_custom() {
let target1 = "http://example.domain/";
let target2 = "https://example.domain/";
let p = Proxy::custom(move |url| {
if url.host_str() == Some("hyper.rs") {
target1.parse().ok()
} else if url.scheme() == "http" {
target2.parse().ok()
} else {
None::<Url>
}
})
.into_matcher();
let http = "http://seanmonstar.com";
let https = "https://hyper.rs";
let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com";
assert_eq!(intercepted_uri(&p, http), target2);
assert_eq!(intercepted_uri(&p, https), target1);
assert!(p.intercept(&url(other)).is_none());
}
#[test]
fn test_standard_with_custom_auth_header() {
let target = "http://example.domain/";
let p = Proxy::all(target)
.unwrap()
.custom_http_auth(http::HeaderValue::from_static("testme"))
.into_matcher();
let got = p.intercept(&url("http://anywhere.local")).unwrap();
let auth = got.basic_auth().unwrap();
assert_eq!(auth, "testme");
}
#[test]
fn test_custom_with_custom_auth_header() {
let target = "http://example.domain/";
let p = Proxy::custom(move |_| target.parse::<Url>().ok())
.custom_http_auth(http::HeaderValue::from_static("testme"))
.into_matcher();
let got = p.intercept(&url("http://anywhere.local")).unwrap();
let auth = got.basic_auth().unwrap();
assert_eq!(auth, "testme");
}
#[test]
fn test_maybe_has_http_auth() {
let m = Proxy::all("https://letme:in@yo.local")
.unwrap()
.into_matcher();
assert!(m.maybe_has_http_auth(), "https forwards");
let m = Proxy::all("http://letme:in@yo.local")
.unwrap()
.into_matcher();
assert!(m.maybe_has_http_auth(), "http forwards");
let m = Proxy::all("http://:in@yo.local").unwrap().into_matcher();
assert!(m.maybe_has_http_auth(), "http forwards with empty username");
let m = Proxy::all("http://letme:@yo.local").unwrap().into_matcher();
assert!(m.maybe_has_http_auth(), "http forwards with empty password");
}
#[test]
fn test_socks_proxy_default_port() {
{
let m = Proxy::all("socks5://example.com").unwrap().into_matcher();
let http = "http://hyper.rs";
let https = "https://hyper.rs";
assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1080));
assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1080));
// custom port
let m = Proxy::all("socks5://example.com:1234")
.unwrap()
.into_matcher();
assert_eq!(intercepted_uri(&m, http).port_u16(), Some(1234));
assert_eq!(intercepted_uri(&m, https).port_u16(), Some(1234));
}
}
}

433
vendor/reqwest/src/redirect.rs vendored Normal file
View File

@@ -0,0 +1,433 @@
//! Redirect Handling
//!
//! By default, a `Client` will automatically handle HTTP redirects, having a
//! maximum redirect chain of 10 hops. To customize this behavior, a
//! `redirect::Policy` can be used with a `ClientBuilder`.
use std::fmt;
use std::{error::Error as StdError, sync::Arc};
use crate::header::{AUTHORIZATION, COOKIE, PROXY_AUTHORIZATION, REFERER, WWW_AUTHENTICATE};
use http::{HeaderMap, HeaderValue};
use hyper::StatusCode;
use crate::{async_impl, Url};
use tower_http::follow_redirect::policy::{
Action as TowerAction, Attempt as TowerAttempt, Policy as TowerPolicy,
};
/// A type that controls the policy on how to handle the following of redirects.
///
/// The default value will catch redirect loops, and has a maximum of 10
/// redirects it will follow in a chain before returning an error.
///
/// - `limited` can be used have the same as the default behavior, but adjust
/// the allowed maximum redirect hops in a chain.
/// - `none` can be used to disable all redirect behavior.
/// - `custom` can be used to create a customized policy.
pub struct Policy {
inner: PolicyKind,
}
/// A type that holds information on the next request and previous requests
/// in redirect chain.
#[derive(Debug)]
pub struct Attempt<'a> {
status: StatusCode,
next: &'a Url,
previous: &'a [Url],
}
/// An action to perform when a redirect status code is found.
#[derive(Debug)]
pub struct Action {
inner: ActionKind,
}
impl Policy {
/// Create a `Policy` with a maximum number of redirects.
///
/// An `Error` will be returned if the max is reached.
pub fn limited(max: usize) -> Self {
Self {
inner: PolicyKind::Limit(max),
}
}
/// Create a `Policy` that does not follow any redirect.
pub fn none() -> Self {
Self {
inner: PolicyKind::None,
}
}
/// Create a custom `Policy` using the passed function.
///
/// # Note
///
/// The default `Policy` handles a maximum loop
/// chain, but the custom variant does not do that for you automatically.
/// The custom policy should have some way of handling those.
///
/// Information on the next request and previous requests can be found
/// on the [`Attempt`] argument passed to the closure.
///
/// Actions can be conveniently created from methods on the
/// [`Attempt`].
///
/// # Example
///
/// ```rust
/// # use reqwest::{Error, redirect};
/// #
/// # fn run() -> Result<(), Error> {
/// let custom = redirect::Policy::custom(|attempt| {
/// if attempt.previous().len() > 5 {
/// attempt.error("too many redirects")
/// } else if attempt.url().host_str() == Some("example.domain") {
/// // prevent redirects to 'example.domain'
/// attempt.stop()
/// } else {
/// attempt.follow()
/// }
/// });
/// let client = reqwest::Client::builder()
/// .redirect(custom)
/// .build()?;
/// # Ok(())
/// # }
/// ```
///
/// [`Attempt`]: struct.Attempt.html
pub fn custom<T>(policy: T) -> Self
where
T: Fn(Attempt) -> Action + Send + Sync + 'static,
{
Self {
inner: PolicyKind::Custom(Box::new(policy)),
}
}
/// Apply this policy to a given [`Attempt`] to produce a [`Action`].
///
/// # Note
///
/// This method can be used together with `Policy::custom()`
/// to construct one `Policy` that wraps another.
///
/// # Example
///
/// ```rust
/// # use reqwest::{Error, redirect};
/// #
/// # fn run() -> Result<(), Error> {
/// let custom = redirect::Policy::custom(|attempt| {
/// eprintln!("{}, Location: {:?}", attempt.status(), attempt.url());
/// redirect::Policy::default().redirect(attempt)
/// });
/// # Ok(())
/// # }
/// ```
pub fn redirect(&self, attempt: Attempt) -> Action {
match self.inner {
PolicyKind::Custom(ref custom) => custom(attempt),
PolicyKind::Limit(max) => {
// The first URL in the previous is the initial URL and not a redirection. It needs to be excluded.
if attempt.previous.len() > max {
attempt.error(TooManyRedirects)
} else {
attempt.follow()
}
}
PolicyKind::None => attempt.stop(),
}
}
pub(crate) fn check(&self, status: StatusCode, next: &Url, previous: &[Url]) -> ActionKind {
self.redirect(Attempt {
status,
next,
previous,
})
.inner
}
pub(crate) fn is_default(&self) -> bool {
matches!(self.inner, PolicyKind::Limit(10))
}
}
impl Default for Policy {
fn default() -> Policy {
// Keep `is_default` in sync
Policy::limited(10)
}
}
impl<'a> Attempt<'a> {
/// Get the type of redirect.
pub fn status(&self) -> StatusCode {
self.status
}
/// Get the next URL to redirect to.
pub fn url(&self) -> &Url {
self.next
}
/// Get the list of previous URLs that have already been requested in this chain.
pub fn previous(&self) -> &[Url] {
self.previous
}
/// Returns an action meaning reqwest should follow the next URL.
pub fn follow(self) -> Action {
Action {
inner: ActionKind::Follow,
}
}
/// Returns an action meaning reqwest should not follow the next URL.
///
/// The 30x response will be returned as the `Ok` result.
pub fn stop(self) -> Action {
Action {
inner: ActionKind::Stop,
}
}
/// Returns an action failing the redirect with an error.
///
/// The `Error` will be returned for the result of the sent request.
pub fn error<E: Into<Box<dyn StdError + Send + Sync>>>(self, error: E) -> Action {
Action {
inner: ActionKind::Error(error.into()),
}
}
}
enum PolicyKind {
Custom(Box<dyn Fn(Attempt) -> Action + Send + Sync + 'static>),
Limit(usize),
None,
}
impl fmt::Debug for Policy {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Policy").field(&self.inner).finish()
}
}
impl fmt::Debug for PolicyKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PolicyKind::Custom(..) => f.pad("Custom"),
PolicyKind::Limit(max) => f.debug_tuple("Limit").field(&max).finish(),
PolicyKind::None => f.pad("None"),
}
}
}
// pub(crate)
#[derive(Debug)]
pub(crate) enum ActionKind {
Follow,
Stop,
Error(Box<dyn StdError + Send + Sync>),
}
pub(crate) fn remove_sensitive_headers(headers: &mut HeaderMap, next: &Url, previous: &[Url]) {
if let Some(previous) = previous.last() {
let cross_host = next.host_str() != previous.host_str()
|| next.port_or_known_default() != previous.port_or_known_default();
if cross_host {
headers.remove(AUTHORIZATION);
headers.remove(COOKIE);
headers.remove("cookie2");
headers.remove(PROXY_AUTHORIZATION);
headers.remove(WWW_AUTHENTICATE);
}
}
}
#[derive(Debug)]
struct TooManyRedirects;
impl fmt::Display for TooManyRedirects {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("too many redirects")
}
}
impl StdError for TooManyRedirects {}
#[derive(Clone)]
pub(crate) struct TowerRedirectPolicy {
policy: Arc<Policy>,
referer: bool,
urls: Vec<Url>,
https_only: bool,
}
impl TowerRedirectPolicy {
pub(crate) fn new(policy: Policy) -> Self {
Self {
policy: Arc::new(policy),
referer: false,
urls: Vec::new(),
https_only: false,
}
}
pub(crate) fn with_referer(&mut self, referer: bool) -> &mut Self {
self.referer = referer;
self
}
pub(crate) fn with_https_only(&mut self, https_only: bool) -> &mut Self {
self.https_only = https_only;
self
}
}
fn make_referer(next: &Url, previous: &Url) -> Option<HeaderValue> {
if next.scheme() == "http" && previous.scheme() == "https" {
return None;
}
let mut referer = previous.clone();
let _ = referer.set_username("");
let _ = referer.set_password(None);
referer.set_fragment(None);
referer.as_str().parse().ok()
}
impl TowerPolicy<async_impl::body::Body, crate::Error> for TowerRedirectPolicy {
fn redirect(&mut self, attempt: &TowerAttempt<'_>) -> Result<TowerAction, crate::Error> {
let previous_url =
Url::parse(&attempt.previous().to_string()).expect("Previous URL must be valid");
let next_url = match Url::parse(&attempt.location().to_string()) {
Ok(url) => url,
Err(e) => return Err(crate::error::builder(e)),
};
self.urls.push(previous_url.clone());
match self.policy.check(attempt.status(), &next_url, &self.urls) {
ActionKind::Follow => {
if next_url.scheme() != "http" && next_url.scheme() != "https" {
return Err(crate::error::url_bad_scheme(next_url));
}
if self.https_only && next_url.scheme() != "https" {
return Err(crate::error::redirect(
crate::error::url_bad_scheme(next_url.clone()),
next_url,
));
}
Ok(TowerAction::Follow)
}
ActionKind::Stop => Ok(TowerAction::Stop),
ActionKind::Error(e) => Err(crate::error::redirect(e, previous_url)),
}
}
fn on_request(&mut self, req: &mut http::Request<async_impl::body::Body>) {
if let Ok(next_url) = Url::parse(&req.uri().to_string()) {
remove_sensitive_headers(req.headers_mut(), &next_url, &self.urls);
if self.referer {
if let Some(previous_url) = self.urls.last() {
if let Some(v) = make_referer(&next_url, previous_url) {
req.headers_mut().insert(REFERER, v);
}
}
}
};
}
// This must be implemented to make 307 and 308 redirects work
fn clone_body(&self, body: &async_impl::body::Body) -> Option<async_impl::body::Body> {
body.try_clone()
}
}
#[test]
fn test_redirect_policy_limit() {
let policy = Policy::default();
let next = Url::parse("http://x.y/z").unwrap();
let mut previous = (0..=9)
.map(|i| Url::parse(&format!("http://a.b/c/{i}")).unwrap())
.collect::<Vec<_>>();
match policy.check(StatusCode::FOUND, &next, &previous) {
ActionKind::Follow => (),
other => panic!("unexpected {other:?}"),
}
previous.push(Url::parse("http://a.b.d/e/33").unwrap());
match policy.check(StatusCode::FOUND, &next, &previous) {
ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn test_redirect_policy_limit_to_0() {
let policy = Policy::limited(0);
let next = Url::parse("http://x.y/z").unwrap();
let previous = vec![Url::parse("http://a.b/c").unwrap()];
match policy.check(StatusCode::FOUND, &next, &previous) {
ActionKind::Error(err) if err.is::<TooManyRedirects>() => (),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn test_redirect_policy_custom() {
let policy = Policy::custom(|attempt| {
if attempt.url().host_str() == Some("foo") {
attempt.stop()
} else {
attempt.follow()
}
});
let next = Url::parse("http://bar/baz").unwrap();
match policy.check(StatusCode::FOUND, &next, &[]) {
ActionKind::Follow => (),
other => panic!("unexpected {other:?}"),
}
let next = Url::parse("http://foo/baz").unwrap();
match policy.check(StatusCode::FOUND, &next, &[]) {
ActionKind::Stop => (),
other => panic!("unexpected {other:?}"),
}
}
#[test]
fn test_remove_sensitive_headers() {
use hyper::header::{HeaderValue, ACCEPT, AUTHORIZATION, COOKIE};
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
headers.insert(AUTHORIZATION, HeaderValue::from_static("let me in"));
headers.insert(COOKIE, HeaderValue::from_static("foo=bar"));
let next = Url::parse("http://initial-domain.com/path").unwrap();
let mut prev = vec![Url::parse("http://initial-domain.com/new_path").unwrap()];
let mut filtered_headers = headers.clone();
remove_sensitive_headers(&mut headers, &next, &prev);
assert_eq!(headers, filtered_headers);
prev.push(Url::parse("http://new-domain.com/path").unwrap());
filtered_headers.remove(AUTHORIZATION);
filtered_headers.remove(COOKIE);
remove_sensitive_headers(&mut headers, &next, &prev);
assert_eq!(headers, filtered_headers);
}

41
vendor/reqwest/src/response.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use url::Url;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ResponseUrl(pub Url);
/// Extension trait for http::response::Builder objects
///
/// Allows the user to add a `Url` to the http::Response
pub trait ResponseBuilderExt {
/// A builder method for the `http::response::Builder` type that allows the user to add a `Url`
/// to the `http::Response`
fn url(self, url: Url) -> Self;
}
impl ResponseBuilderExt for http::response::Builder {
fn url(self, url: Url) -> Self {
self.extension(ResponseUrl(url))
}
}
#[cfg(test)]
mod tests {
use super::{ResponseBuilderExt, ResponseUrl};
use http::response::Builder;
use url::Url;
#[test]
fn test_response_builder_ext() {
let url = Url::parse("http://example.com").unwrap();
let response = Builder::new()
.status(200)
.url(url.clone())
.body(())
.unwrap();
assert_eq!(
response.extensions().get::<ResponseUrl>(),
Some(&ResponseUrl(url))
);
}
}

477
vendor/reqwest/src/retry.rs vendored Normal file
View File

@@ -0,0 +1,477 @@
//! Retry requests
//!
//! A `Client` has the ability to retry requests, by sending additional copies
//! to the server if a response is considered retryable.
//!
//! The [`Builder`] makes it easier to configure what requests to retry, along
//! with including best practices by default, such as a retry budget.
//!
//! # Defaults
//!
//! The default retry behavior of a `Client` is to only retry requests where an
//! error or low-level protocol NACK is encountered that is known to be safe to
//! retry. Note however that providing a specific retry policy will override
//! the default, and you will need to explicitly include that behavior.
//!
//! All policies default to including a retry budget that permits 20% extra
//! requests to be sent.
//!
//! # Scoped
//!
//! A client's retry policy is scoped. That means that the policy doesn't
//! apply to all requests, but only those within a user-defined scope.
//!
//! Since all policies include a budget by default, it doesn't make sense to
//! apply it on _all_ requests. Rather, the retry history applied by a budget
//! should likely only be applied to the same host.
//!
//! # Classifiers
//!
//! A retry policy needs to be configured with a classifier that determines
//! if a request should be retried. Knowledge of the destination server's
//! behavior is required to make a safe classifier. **Requests should not be
//! retried** if the server cannot safely handle the same request twice, or if
//! it causes side effects.
//!
//! Some common properties to check include if the request method is
//! idempotent, or if the response status code indicates a transient error.
use std::sync::Arc;
use std::time::Duration;
use tower::retry::budget::{Budget as _, TpsBudget as Budget};
/// Builder to configure retries
///
/// Construct with [`for_host()`].
#[derive(Debug)]
pub struct Builder {
//backoff: Backoff,
budget: Option<f32>,
classifier: classify::Classifier,
max_retries_per_request: u32,
scope: scope::Scoped,
}
/// The internal type that we convert the builder into, that implements
/// tower::retry::Policy privately.
#[derive(Clone, Debug)]
pub(crate) struct Policy {
budget: Option<Arc<Budget>>,
classifier: classify::Classifier,
max_retries_per_request: u32,
retry_cnt: u32,
scope: scope::Scoped,
}
//#[derive(Debug)]
//struct Backoff;
/// Create a retry builder with a request scope.
///
/// To provide a scope that isn't a closure, use the more general
/// [`Builder::scoped()`].
pub fn for_host<S>(host: S) -> Builder
where
S: for<'a> PartialEq<&'a str> + Send + Sync + 'static,
{
scoped(move |req| host == req.uri().host().unwrap_or(""))
}
/// Create a retry policy that will never retry any request.
///
/// This is useful for disabling the `Client`s default behavior of retrying
/// protocol nacks.
pub fn never() -> Builder {
scoped(|_| false).no_budget()
}
fn scoped<F>(func: F) -> Builder
where
F: Fn(&Req) -> bool + Send + Sync + 'static,
{
Builder::scoped(scope::ScopeFn(func))
}
// ===== impl Builder =====
impl Builder {
/// Create a scoped retry policy.
///
/// For a more convenient constructor, see [`for_host()`].
pub fn scoped(scope: impl scope::Scope) -> Self {
Self {
budget: Some(0.2),
classifier: classify::Classifier::Never,
max_retries_per_request: 2, // on top of the original
scope: scope::Scoped::Dyn(Arc::new(scope)),
}
}
/// Set no retry budget.
///
/// Sets that no budget will be enforced. This could also be considered
/// to be an infinite budget.
///
/// This is NOT recommended. Disabling the budget can make your system more
/// susceptible to retry storms.
pub fn no_budget(mut self) -> Self {
self.budget = None;
self
}
/// Sets the max extra load the budget will allow.
///
/// Think of the amount of requests your client generates, and how much
/// load that puts on the server. This option configures as a percentage
/// how much extra load is allowed via retries.
///
/// For example, if you send 1,000 requests per second, setting a maximum
/// extra load value of `0.3` would allow 300 more requests per second
/// in retries. A value of `2.5` would allow 2,500 more requests.
///
/// # Panics
///
/// The `extra_percent` value must be within reasonable values for a
/// percentage. This method will panic if it is less than `0.0`, or greater
/// than `1000.0`.
pub fn max_extra_load(mut self, extra_percent: f32) -> Self {
assert!(extra_percent >= 0.0);
assert!(extra_percent <= 1000.0);
self.budget = Some(extra_percent);
self
}
// pub fn max_replay_body
/// Set the max retries allowed per request.
///
/// For each logical (initial) request, only retry up to `max` times.
///
/// This value is used in combination with a token budget that is applied
/// to all requests. Even if the budget would allow more requests, this
/// limit will prevent. Likewise, the budget may prevent retrying up to
/// `max` times. This setting prevents a single request from consuming
/// the entire budget.
///
/// Default is currently 2 retries.
pub fn max_retries_per_request(mut self, max: u32) -> Self {
self.max_retries_per_request = max;
self
}
/// Provide a classifier to determine if a request should be retried.
///
/// # Example
///
/// ```rust
/// # fn with_builder(builder: reqwest::retry::Builder) -> reqwest::retry::Builder {
/// builder.classify_fn(|req_rep| {
/// match (req_rep.method(), req_rep.status()) {
/// (&http::Method::GET, Some(http::StatusCode::SERVICE_UNAVAILABLE)) => {
/// req_rep.retryable()
/// },
/// _ => req_rep.success()
/// }
/// })
/// # }
/// ```
pub fn classify_fn<F>(self, func: F) -> Self
where
F: Fn(classify::ReqRep<'_>) -> classify::Action + Send + Sync + 'static,
{
self.classify(classify::ClassifyFn(func))
}
/// Provide a classifier to determine if a request should be retried.
pub fn classify(mut self, classifier: impl classify::Classify) -> Self {
self.classifier = classify::Classifier::Dyn(Arc::new(classifier));
self
}
pub(crate) fn default() -> Builder {
Self {
// unscoped protocols nacks doesn't need a budget
budget: None,
classifier: classify::Classifier::ProtocolNacks,
max_retries_per_request: 2, // on top of the original
scope: scope::Scoped::Unscoped,
}
}
pub(crate) fn into_policy(self) -> Policy {
let budget = self
.budget
.map(|p| Arc::new(Budget::new(Duration::from_secs(10), 10, p)));
Policy {
budget,
classifier: self.classifier,
max_retries_per_request: self.max_retries_per_request,
retry_cnt: 0,
scope: self.scope,
}
}
}
// ===== internal ======
type Req = http::Request<crate::async_impl::body::Body>;
impl<B> tower::retry::Policy<Req, http::Response<B>, crate::Error> for Policy {
// TODO? backoff futures...
type Future = std::future::Ready<()>;
fn retry(
&mut self,
req: &mut Req,
result: &mut crate::Result<http::Response<B>>,
) -> Option<Self::Future> {
match self.classifier.classify(req, result) {
classify::Action::Success => {
log::trace!("shouldn't retry!");
if let Some(ref budget) = self.budget {
budget.deposit();
}
None
}
classify::Action::Retryable => {
log::trace!("could retry!");
if self.budget.as_ref().map(|b| b.withdraw()).unwrap_or(true) {
self.retry_cnt += 1;
Some(std::future::ready(()))
} else {
log::debug!("retryable but could not withdraw from budget");
None
}
}
}
}
fn clone_request(&mut self, req: &Req) -> Option<Req> {
if self.retry_cnt > 0 && !self.scope.applies_to(req) {
return None;
}
if self.retry_cnt >= self.max_retries_per_request {
log::trace!("max_retries_per_request hit");
return None;
}
let body = req.body().try_clone()?;
let mut new = http::Request::new(body);
*new.method_mut() = req.method().clone();
*new.uri_mut() = req.uri().clone();
*new.version_mut() = req.version();
*new.headers_mut() = req.headers().clone();
*new.extensions_mut() = req.extensions().clone();
Some(new)
}
}
fn is_retryable_error(err: &crate::Error) -> bool {
use std::error::Error as _;
// pop the reqwest::Error
let err = if let Some(err) = err.source() {
err
} else {
return false;
};
// pop the legacy::Error
let err = if let Some(err) = err.source() {
err
} else {
return false;
};
#[cfg(not(any(feature = "http3", feature = "http2")))]
let _err = err;
#[cfg(feature = "http3")]
if let Some(cause) = err.source() {
if let Some(err) = cause.downcast_ref::<h3::error::ConnectionError>() {
log::trace!("determining if HTTP/3 error {err} can be retried");
// TODO: Does h3 provide an API for checking the error?
return err.to_string().as_str() == "timeout";
}
}
#[cfg(feature = "http2")]
if let Some(cause) = err.source() {
if let Some(err) = cause.downcast_ref::<h2::Error>() {
// They sent us a graceful shutdown, try with a new connection!
if err.is_go_away() && err.is_remote() && err.reason() == Some(h2::Reason::NO_ERROR) {
return true;
}
// REFUSED_STREAM was sent from the server, which is safe to retry.
// https://www.rfc-editor.org/rfc/rfc9113.html#section-8.7-3.2
if err.is_reset() && err.is_remote() && err.reason() == Some(h2::Reason::REFUSED_STREAM)
{
return true;
}
}
}
false
}
// sealed types and traits on purpose while exploring design space
mod scope {
pub trait Scope: Send + Sync + 'static {
fn applies_to(&self, req: &super::Req) -> bool;
}
// I think scopes likely make the most sense being to hosts.
// If that's the case, then it should probably be easiest to check for
// the host. Perhaps also considering the ability to add more things
// to scope off in the future...
// For Future Whoever: making a blanket impl for any closure sounds nice,
// but it causes inference issues at the call site. Every closure would
// need to include `: ReqRep` in the arguments.
//
// An alternative is to make things like `ScopeFn`. Slightly more annoying,
// but also more forwards-compatible. :shrug:
pub struct ScopeFn<F>(pub(super) F);
impl<F> Scope for ScopeFn<F>
where
F: Fn(&super::Req) -> bool + Send + Sync + 'static,
{
fn applies_to(&self, req: &super::Req) -> bool {
(self.0)(req)
}
}
#[derive(Clone)]
pub(super) enum Scoped {
Unscoped,
Dyn(std::sync::Arc<dyn Scope>),
}
impl Scoped {
pub(super) fn applies_to(&self, req: &super::Req) -> bool {
let ret = match self {
Self::Unscoped => true,
Self::Dyn(s) => s.applies_to(req),
};
log::trace!("retry in scope: {ret}");
ret
}
}
impl std::fmt::Debug for Scoped {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unscoped => f.write_str("Unscoped"),
Self::Dyn(_) => f.write_str("Scoped"),
}
}
}
}
// sealed types and traits on purpose while exploring design space
mod classify {
pub trait Classify: Send + Sync + 'static {
fn classify(&self, req_rep: ReqRep<'_>) -> Action;
}
// For Future Whoever: making a blanket impl for any closure sounds nice,
// but it causes inference issues at the call site. Every closure would
// need to include `: ReqRep` in the arguments.
//
// An alternative is to make things like `ClassifyFn`. Slightly more
// annoying, but also more forwards-compatible. :shrug:
pub struct ClassifyFn<F>(pub(super) F);
impl<F> Classify for ClassifyFn<F>
where
F: Fn(ReqRep<'_>) -> Action + Send + Sync + 'static,
{
fn classify(&self, req_rep: ReqRep<'_>) -> Action {
(self.0)(req_rep)
}
}
#[derive(Debug)]
pub struct ReqRep<'a>(&'a super::Req, Result<http::StatusCode, &'a crate::Error>);
impl ReqRep<'_> {
pub fn method(&self) -> &http::Method {
self.0.method()
}
pub fn uri(&self) -> &http::Uri {
self.0.uri()
}
pub fn status(&self) -> Option<http::StatusCode> {
self.1.ok()
}
pub fn error(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.1.as_ref().err().map(|e| &**e as _)
}
pub fn retryable(self) -> Action {
Action::Retryable
}
pub fn success(self) -> Action {
Action::Success
}
fn is_protocol_nack(&self) -> bool {
self.1
.as_ref()
.err()
.map(|&e| super::is_retryable_error(e))
.unwrap_or(false)
}
}
#[must_use]
#[derive(Debug)]
pub enum Action {
Success,
Retryable,
}
#[derive(Clone)]
pub(super) enum Classifier {
Never,
ProtocolNacks,
Dyn(std::sync::Arc<dyn Classify>),
}
impl Classifier {
pub(super) fn classify<B>(
&self,
req: &super::Req,
res: &Result<http::Response<B>, crate::Error>,
) -> Action {
let req_rep = ReqRep(req, res.as_ref().map(|r| r.status()));
match self {
Self::Never => Action::Success,
Self::ProtocolNacks => {
if req_rep.is_protocol_nack() {
Action::Retryable
} else {
Action::Success
}
}
Self::Dyn(c) => c.classify(req_rep),
}
}
}
impl std::fmt::Debug for Classifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Never => f.write_str("Never"),
Self::ProtocolNacks => f.write_str("ProtocolNacks"),
Self::Dyn(_) => f.write_str("Classifier"),
}
}
}
}

843
vendor/reqwest/src/tls.rs vendored Normal file
View File

@@ -0,0 +1,843 @@
//! TLS configuration and types
//!
//! A `Client` will use transport layer security (TLS) by default to connect to
//! HTTPS destinations.
//!
//! # Backends
//!
//! reqwest supports several TLS backends, enabled with Cargo features.
//!
//! ## default-tls
//!
//! reqwest will pick a TLS backend by default. This is true when the
//! `default-tls` feature is enabled.
//!
//! While it currently uses `native-tls`, the feature set is designed to only
//! enable configuration that is shared among available backends. This allows
//! reqwest to change the default to `rustls` (or another) at some point in the
//! future.
//!
//! <div class="warning">This feature is enabled by default, and takes
//! precedence if any other crate enables it. This is true even if you declare
//! `features = []`. You must set `default-features = false` instead.</div>
//!
//! Since Cargo features are additive, other crates in your dependency tree can
//! cause the default backend to be enabled. If you wish to ensure your
//! `Client` uses a specific backend, call the appropriate builder methods
//! (such as [`use_rustls_tls()`][]).
//!
//! [`use_rustls_tls()`]: crate::ClientBuilder::use_rustls_tls()
//!
//! ## native-tls
//!
//! This backend uses the [native-tls][] crate. That will try to use the system
//! TLS on Windows and Mac, and OpenSSL on Linux targets.
//!
//! Enabling the feature explicitly allows for `native-tls`-specific
//! configuration options.
//!
//! [native-tls]: https://crates.io/crates/native-tls
//!
//! ## rustls-tls
//!
//! This backend uses the [rustls][] crate, a TLS library written in Rust.
//!
//! [rustls]: https://crates.io/crates/rustls
#[cfg(feature = "__rustls")]
use rustls::{
client::danger::HandshakeSignatureValid, client::danger::ServerCertVerified,
client::danger::ServerCertVerifier, crypto::WebPkiSupportedAlgorithms,
server::ParsedCertificate, DigitallySignedStruct, Error as TLSError, RootCertStore,
SignatureScheme,
};
use rustls_pki_types::pem::PemObject;
#[cfg(feature = "__rustls")]
use rustls_pki_types::{ServerName, UnixTime};
use std::{
fmt,
io::{BufRead, BufReader},
};
/// Represents a X509 certificate revocation list.
#[cfg(feature = "__rustls")]
pub struct CertificateRevocationList {
#[cfg(feature = "__rustls")]
inner: rustls_pki_types::CertificateRevocationListDer<'static>,
}
/// Represents a server X509 certificate.
#[derive(Clone)]
pub struct Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate,
#[cfg(feature = "__rustls")]
original: Cert,
}
#[cfg(feature = "__rustls")]
#[derive(Clone)]
enum Cert {
Der(Vec<u8>),
Pem(Vec<u8>),
}
/// Represents a private key and X509 cert as a client certificate.
#[derive(Clone)]
pub struct Identity {
#[cfg_attr(not(any(feature = "native-tls", feature = "__rustls")), allow(unused))]
inner: ClientCert,
}
enum ClientCert {
#[cfg(feature = "native-tls")]
Pkcs12(native_tls_crate::Identity),
#[cfg(feature = "native-tls")]
Pkcs8(native_tls_crate::Identity),
#[cfg(feature = "__rustls")]
Pem {
key: rustls_pki_types::PrivateKeyDer<'static>,
certs: Vec<rustls_pki_types::CertificateDer<'static>>,
},
}
impl Clone for ClientCert {
fn clone(&self) -> Self {
match self {
#[cfg(feature = "native-tls")]
Self::Pkcs8(i) => Self::Pkcs8(i.clone()),
#[cfg(feature = "native-tls")]
Self::Pkcs12(i) => Self::Pkcs12(i.clone()),
#[cfg(feature = "__rustls")]
ClientCert::Pem { key, certs } => ClientCert::Pem {
key: key.clone_key(),
certs: certs.clone(),
},
#[cfg_attr(
any(feature = "native-tls", feature = "__rustls"),
allow(unreachable_patterns)
)]
_ => unreachable!(),
}
}
}
impl Certificate {
/// Create a `Certificate` from a binary DER encoded certificate
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my_cert.der")?
/// .read_to_end(&mut buf)?;
/// let cert = reqwest::Certificate::from_der(&buf)?;
/// # drop(cert);
/// # Ok(())
/// # }
/// ```
pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate::from_der(der).map_err(crate::error::builder)?,
#[cfg(feature = "__rustls")]
original: Cert::Der(der.to_owned()),
})
}
/// Create a `Certificate` from a PEM encoded certificate
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my_cert.pem")?
/// .read_to_end(&mut buf)?;
/// let cert = reqwest::Certificate::from_pem(&buf)?;
/// # drop(cert);
/// # Ok(())
/// # }
/// ```
pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "default-tls")]
native: native_tls_crate::Certificate::from_pem(pem).map_err(crate::error::builder)?,
#[cfg(feature = "__rustls")]
original: Cert::Pem(pem.to_owned()),
})
}
/// Create a collection of `Certificate`s from a PEM encoded certificate bundle.
/// Example byte sources may be `.crt`, `.cer` or `.pem` files.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn cert() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("ca-bundle.crt")?
/// .read_to_end(&mut buf)?;
/// let certs = reqwest::Certificate::from_pem_bundle(&buf)?;
/// # drop(certs);
/// # Ok(())
/// # }
/// ```
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<Certificate>> {
let mut reader = BufReader::new(pem_bundle);
Self::read_pem_certs(&mut reader)?
.iter()
.map(|cert_vec| Certificate::from_der(cert_vec))
.collect::<crate::Result<Vec<Certificate>>>()
}
#[cfg(feature = "default-tls")]
pub(crate) fn add_to_native_tls(self, tls: &mut native_tls_crate::TlsConnectorBuilder) {
tls.add_root_certificate(self.native);
}
#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(
self,
root_cert_store: &mut rustls::RootCertStore,
) -> crate::Result<()> {
use std::io::Cursor;
match self.original {
Cert::Der(buf) => root_cert_store
.add(buf.into())
.map_err(crate::error::builder)?,
Cert::Pem(buf) => {
let mut reader = Cursor::new(buf);
let certs = Self::read_pem_certs(&mut reader)?;
for c in certs {
root_cert_store
.add(c.into())
.map_err(crate::error::builder)?;
}
}
}
Ok(())
}
fn read_pem_certs(reader: &mut impl BufRead) -> crate::Result<Vec<Vec<u8>>> {
rustls_pki_types::CertificateDer::pem_reader_iter(reader)
.map(|result| match result {
Ok(cert) => Ok(cert.as_ref().to_vec()),
Err(_) => Err(crate::error::builder("invalid certificate encoding")),
})
.collect()
}
}
impl Identity {
/// Parses a DER-formatted PKCS #12 archive, using the specified password to decrypt the key.
///
/// The archive should contain a leaf certificate and its private key, as well any intermediate
/// certificates that allow clients to build a chain to a trusted root.
/// The chain certificates should be in order from the leaf certificate towards the root.
///
/// PKCS #12 archives typically have the file extension `.p12` or `.pfx`, and can be created
/// with the OpenSSL `pkcs12` tool:
///
/// ```bash
/// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem -certfile chain_certs.pem
/// ```
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn pkcs12() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my-ident.pfx")?
/// .read_to_end(&mut buf)?;
/// let pkcs12 = reqwest::Identity::from_pkcs12_der(&buf, "my-privkey-password")?;
/// # drop(pkcs12);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `native-tls` Cargo feature enabled.
#[cfg(feature = "native-tls")]
pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result<Identity> {
Ok(Identity {
inner: ClientCert::Pkcs12(
native_tls_crate::Identity::from_pkcs12(der, password)
.map_err(crate::error::builder)?,
),
})
}
/// Parses a chain of PEM encoded X509 certificates, with the leaf certificate first.
/// `key` is a PEM encoded PKCS #8 formatted private key for the leaf certificate.
///
/// The certificate chain should contain any intermediate certificates that should be sent to
/// clients to allow them to build a chain to a trusted root.
///
/// A certificate chain here means a series of PEM encoded certificates concatenated together.
///
/// # Examples
///
/// ```
/// # use std::fs;
/// # fn pkcs8() -> Result<(), Box<dyn std::error::Error>> {
/// let cert = fs::read("client.pem")?;
/// let key = fs::read("key.pem")?;
/// let pkcs8 = reqwest::Identity::from_pkcs8_pem(&cert, &key)?;
/// # drop(pkcs8);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `native-tls` Cargo feature enabled.
#[cfg(feature = "native-tls")]
pub fn from_pkcs8_pem(pem: &[u8], key: &[u8]) -> crate::Result<Identity> {
Ok(Identity {
inner: ClientCert::Pkcs8(
native_tls_crate::Identity::from_pkcs8(pem, key).map_err(crate::error::builder)?,
),
})
}
/// Parses PEM encoded private key and certificate.
///
/// The input should contain a PEM encoded private key
/// and at least one PEM encoded certificate.
///
/// Note: The private key must be in RSA, SEC1 Elliptic Curve or PKCS#8 format.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn pem() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my-ident.pem")?
/// .read_to_end(&mut buf)?;
/// let id = reqwest::Identity::from_pem(&buf)?;
/// # drop(id);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem(buf: &[u8]) -> crate::Result<Identity> {
use rustls_pki_types::{pem::SectionKind, PrivateKeyDer};
use std::io::Cursor;
let (key, certs) = {
let mut pem = Cursor::new(buf);
let mut sk = Vec::<rustls_pki_types::PrivateKeyDer>::new();
let mut certs = Vec::<rustls_pki_types::CertificateDer>::new();
while let Some((kind, data)) =
rustls_pki_types::pem::from_buf(&mut pem).map_err(|_| {
crate::error::builder(TLSError::General(String::from(
"Invalid identity PEM file",
)))
})?
{
match kind {
SectionKind::Certificate => certs.push(data.into()),
SectionKind::PrivateKey => sk.push(PrivateKeyDer::Pkcs8(data.into())),
SectionKind::RsaPrivateKey => sk.push(PrivateKeyDer::Pkcs1(data.into())),
SectionKind::EcPrivateKey => sk.push(PrivateKeyDer::Sec1(data.into())),
_ => {
return Err(crate::error::builder(TLSError::General(String::from(
"No valid certificate was found",
))))
}
}
}
if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
(sk, certs)
} else {
return Err(crate::error::builder(TLSError::General(String::from(
"private key or certificate not found",
))));
}
};
Ok(Identity {
inner: ClientCert::Pem { key, certs },
})
}
#[cfg(feature = "native-tls")]
pub(crate) fn add_to_native_tls(
self,
tls: &mut native_tls_crate::TlsConnectorBuilder,
) -> crate::Result<()> {
match self.inner {
ClientCert::Pkcs12(id) | ClientCert::Pkcs8(id) => {
tls.identity(id);
Ok(())
}
#[cfg(feature = "__rustls")]
ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")),
}
}
#[cfg(feature = "__rustls")]
pub(crate) fn add_to_rustls(
self,
config_builder: rustls::ConfigBuilder<
rustls::ClientConfig,
// Not sure here
rustls::client::WantsClientCert,
>,
) -> crate::Result<rustls::ClientConfig> {
match self.inner {
ClientCert::Pem { key, certs } => config_builder
.with_client_auth_cert(certs, key)
.map_err(crate::error::builder),
#[cfg(feature = "native-tls")]
ClientCert::Pkcs12(..) | ClientCert::Pkcs8(..) => {
Err(crate::error::builder("incompatible TLS identity type"))
}
}
}
}
#[cfg(feature = "__rustls")]
impl CertificateRevocationList {
/// Parses a PEM encoded CRL.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn crl() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("my_crl.pem")?
/// .read_to_end(&mut buf)?;
/// let crl = reqwest::tls::CertificateRevocationList::from_pem(&buf)?;
/// # drop(crl);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem(pem: &[u8]) -> crate::Result<CertificateRevocationList> {
Ok(CertificateRevocationList {
#[cfg(feature = "__rustls")]
inner: rustls_pki_types::CertificateRevocationListDer::from(pem.to_vec()),
})
}
/// Creates a collection of `CertificateRevocationList`s from a PEM encoded CRL bundle.
/// Example byte sources may be `.crl` or `.pem` files.
///
/// # Examples
///
/// ```
/// # use std::fs::File;
/// # use std::io::Read;
/// # fn crls() -> Result<(), Box<dyn std::error::Error>> {
/// let mut buf = Vec::new();
/// File::open("crl-bundle.crl")?
/// .read_to_end(&mut buf)?;
/// let crls = reqwest::tls::CertificateRevocationList::from_pem_bundle(&buf)?;
/// # drop(crls);
/// # Ok(())
/// # }
/// ```
///
/// # Optional
///
/// This requires the `rustls-tls(-...)` Cargo feature enabled.
#[cfg(feature = "__rustls")]
pub fn from_pem_bundle(pem_bundle: &[u8]) -> crate::Result<Vec<CertificateRevocationList>> {
rustls_pki_types::CertificateRevocationListDer::pem_slice_iter(pem_bundle)
.map(|result| match result {
Ok(crl) => Ok(CertificateRevocationList { inner: crl }),
Err(_) => Err(crate::error::builder("invalid crl encoding")),
})
.collect::<crate::Result<Vec<CertificateRevocationList>>>()
}
#[cfg(feature = "__rustls")]
pub(crate) fn as_rustls_crl<'a>(&self) -> rustls_pki_types::CertificateRevocationListDer<'a> {
self.inner.clone()
}
}
impl fmt::Debug for Certificate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Certificate").finish()
}
}
impl fmt::Debug for Identity {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Identity").finish()
}
}
#[cfg(feature = "__rustls")]
impl fmt::Debug for CertificateRevocationList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("CertificateRevocationList").finish()
}
}
/// A TLS protocol version.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version(InnerVersion);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
enum InnerVersion {
Tls1_0,
Tls1_1,
Tls1_2,
Tls1_3,
}
// These could perhaps be From/TryFrom implementations, but those would be
// part of the public API so let's be careful
impl Version {
/// Version 1.0 of the TLS protocol.
pub const TLS_1_0: Version = Version(InnerVersion::Tls1_0);
/// Version 1.1 of the TLS protocol.
pub const TLS_1_1: Version = Version(InnerVersion::Tls1_1);
/// Version 1.2 of the TLS protocol.
pub const TLS_1_2: Version = Version(InnerVersion::Tls1_2);
/// Version 1.3 of the TLS protocol.
pub const TLS_1_3: Version = Version(InnerVersion::Tls1_3);
#[cfg(feature = "default-tls")]
pub(crate) fn to_native_tls(self) -> Option<native_tls_crate::Protocol> {
match self.0 {
InnerVersion::Tls1_0 => Some(native_tls_crate::Protocol::Tlsv10),
InnerVersion::Tls1_1 => Some(native_tls_crate::Protocol::Tlsv11),
InnerVersion::Tls1_2 => Some(native_tls_crate::Protocol::Tlsv12),
InnerVersion::Tls1_3 => None,
}
}
#[cfg(feature = "__rustls")]
pub(crate) fn from_rustls(version: rustls::ProtocolVersion) -> Option<Self> {
match version {
rustls::ProtocolVersion::SSLv2 => None,
rustls::ProtocolVersion::SSLv3 => None,
rustls::ProtocolVersion::TLSv1_0 => Some(Self(InnerVersion::Tls1_0)),
rustls::ProtocolVersion::TLSv1_1 => Some(Self(InnerVersion::Tls1_1)),
rustls::ProtocolVersion::TLSv1_2 => Some(Self(InnerVersion::Tls1_2)),
rustls::ProtocolVersion::TLSv1_3 => Some(Self(InnerVersion::Tls1_3)),
_ => None,
}
}
}
pub(crate) enum TlsBackend {
// This is the default and HTTP/3 feature does not use it so suppress it.
#[allow(dead_code)]
#[cfg(feature = "default-tls")]
Default,
#[cfg(feature = "native-tls")]
BuiltNativeTls(native_tls_crate::TlsConnector),
#[cfg(feature = "__rustls")]
Rustls,
#[cfg(feature = "__rustls")]
BuiltRustls(rustls::ClientConfig),
#[cfg(any(feature = "native-tls", feature = "__rustls",))]
UnknownPreconfigured,
}
impl fmt::Debug for TlsBackend {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "default-tls")]
TlsBackend::Default => write!(f, "Default"),
#[cfg(feature = "native-tls")]
TlsBackend::BuiltNativeTls(_) => write!(f, "BuiltNativeTls"),
#[cfg(feature = "__rustls")]
TlsBackend::Rustls => write!(f, "Rustls"),
#[cfg(feature = "__rustls")]
TlsBackend::BuiltRustls(_) => write!(f, "BuiltRustls"),
#[cfg(any(feature = "native-tls", feature = "__rustls",))]
TlsBackend::UnknownPreconfigured => write!(f, "UnknownPreconfigured"),
}
}
}
#[allow(clippy::derivable_impls)]
impl Default for TlsBackend {
fn default() -> TlsBackend {
#[cfg(all(feature = "default-tls", not(feature = "http3")))]
{
TlsBackend::Default
}
#[cfg(any(
all(feature = "__rustls", not(feature = "default-tls")),
feature = "http3"
))]
{
TlsBackend::Rustls
}
}
}
#[cfg(feature = "__rustls")]
#[derive(Debug)]
pub(crate) struct NoVerifier;
#[cfg(feature = "__rustls")]
impl ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls_pki_types::CertificateDer,
_intermediates: &[rustls_pki_types::CertificateDer],
_server_name: &ServerName,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, TLSError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls_pki_types::CertificateDer,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![
SignatureScheme::RSA_PKCS1_SHA1,
SignatureScheme::ECDSA_SHA1_Legacy,
SignatureScheme::RSA_PKCS1_SHA256,
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::RSA_PKCS1_SHA384,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::RSA_PKCS1_SHA512,
SignatureScheme::ECDSA_NISTP521_SHA512,
SignatureScheme::RSA_PSS_SHA256,
SignatureScheme::RSA_PSS_SHA384,
SignatureScheme::RSA_PSS_SHA512,
SignatureScheme::ED25519,
SignatureScheme::ED448,
]
}
}
#[cfg(feature = "__rustls")]
#[derive(Debug)]
pub(crate) struct IgnoreHostname {
roots: RootCertStore,
signature_algorithms: WebPkiSupportedAlgorithms,
}
#[cfg(feature = "__rustls")]
impl IgnoreHostname {
pub(crate) fn new(
roots: RootCertStore,
signature_algorithms: WebPkiSupportedAlgorithms,
) -> Self {
Self {
roots,
signature_algorithms,
}
}
}
#[cfg(feature = "__rustls")]
impl ServerCertVerifier for IgnoreHostname {
fn verify_server_cert(
&self,
end_entity: &rustls_pki_types::CertificateDer<'_>,
intermediates: &[rustls_pki_types::CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
now: UnixTime,
) -> Result<ServerCertVerified, TLSError> {
let cert = ParsedCertificate::try_from(end_entity)?;
rustls::client::verify_server_cert_signed_by_trust_anchor(
&cert,
&self.roots,
intermediates,
now,
self.signature_algorithms.all,
)?;
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls_pki_types::CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
rustls::crypto::verify_tls12_signature(message, cert, dss, &self.signature_algorithms)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls_pki_types::CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TLSError> {
rustls::crypto::verify_tls13_signature(message, cert, dss, &self.signature_algorithms)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.signature_algorithms.supported_schemes()
}
}
/// Hyper extension carrying extra TLS layer information.
/// Made available to clients on responses when `tls_info` is set.
#[derive(Clone)]
pub struct TlsInfo {
pub(crate) peer_certificate: Option<Vec<u8>>,
}
impl TlsInfo {
/// Get the DER encoded leaf certificate of the peer.
pub fn peer_certificate(&self) -> Option<&[u8]> {
self.peer_certificate.as_ref().map(|der| &der[..])
}
}
impl std::fmt::Debug for TlsInfo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("TlsInfo").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "default-tls")]
#[test]
fn certificate_from_der_invalid() {
Certificate::from_der(b"not der").unwrap_err();
}
#[cfg(feature = "default-tls")]
#[test]
fn certificate_from_pem_invalid() {
Certificate::from_pem(b"not pem").unwrap_err();
}
#[cfg(feature = "native-tls")]
#[test]
fn identity_from_pkcs12_der_invalid() {
Identity::from_pkcs12_der(b"not der", "nope").unwrap_err();
}
#[cfg(feature = "native-tls")]
#[test]
fn identity_from_pkcs8_pem_invalid() {
Identity::from_pkcs8_pem(b"not pem", b"not key").unwrap_err();
}
#[cfg(feature = "__rustls")]
#[test]
fn identity_from_pem_invalid() {
Identity::from_pem(b"not pem").unwrap_err();
}
#[cfg(feature = "__rustls")]
#[test]
fn identity_from_pem_pkcs1_key() {
let pem = b"-----BEGIN CERTIFICATE-----\n\
-----END CERTIFICATE-----\n\
-----BEGIN RSA PRIVATE KEY-----\n\
-----END RSA PRIVATE KEY-----\n";
Identity::from_pem(pem).unwrap();
}
#[test]
fn certificates_from_pem_bundle() {
const PEM_BUNDLE: &[u8] = b"
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
YyRIHN8wfdVoOw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----
";
assert!(Certificate::from_pem_bundle(PEM_BUNDLE).is_ok())
}
#[cfg(feature = "__rustls")]
#[test]
fn crl_from_pem() {
let pem = b"-----BEGIN X509 CRL-----\n-----END X509 CRL-----\n";
CertificateRevocationList::from_pem(pem).unwrap();
}
#[cfg(feature = "__rustls")]
#[test]
fn crl_from_pem_bundle() {
let pem_bundle = std::fs::read("tests/support/crl.pem").unwrap();
let result = CertificateRevocationList::from_pem_bundle(&pem_bundle);
assert!(result.is_ok());
let result = result.unwrap();
assert_eq!(result.len(), 1);
}
}

130
vendor/reqwest/src/util.rs vendored Normal file
View File

@@ -0,0 +1,130 @@
use crate::header::{Entry, HeaderMap, HeaderValue, OccupiedEntry};
use std::fmt;
pub fn basic_auth<U, P>(username: U, password: Option<P>) -> HeaderValue
where
U: std::fmt::Display,
P: std::fmt::Display,
{
use base64::prelude::BASE64_STANDARD;
use base64::write::EncoderWriter;
use std::io::Write;
let mut buf = b"Basic ".to_vec();
{
let mut encoder = EncoderWriter::new(&mut buf, &BASE64_STANDARD);
let _ = write!(encoder, "{username}:");
if let Some(password) = password {
let _ = write!(encoder, "{password}");
}
}
let mut header = HeaderValue::from_maybe_shared(bytes::Bytes::from(buf))
.expect("base64 is always valid HeaderValue");
header.set_sensitive(true);
header
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn fast_random() -> u64 {
use std::cell::Cell;
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
thread_local! {
static KEY: RandomState = RandomState::new();
static COUNTER: Cell<u64> = Cell::new(0);
}
KEY.with(|key| {
COUNTER.with(|ctr| {
let n = ctr.get().wrapping_add(1);
ctr.set(n);
let mut h = key.build_hasher();
h.write_u64(n);
h.finish()
})
})
}
pub(crate) fn replace_headers(dst: &mut HeaderMap, src: HeaderMap) {
// IntoIter of HeaderMap yields (Option<HeaderName>, HeaderValue).
// The first time a name is yielded, it will be Some(name), and if
// there are more values with the same name, the next yield will be
// None.
let mut prev_entry: Option<OccupiedEntry<_>> = None;
for (key, value) in src {
match key {
Some(key) => match dst.entry(key) {
Entry::Occupied(mut e) => {
e.insert(value);
prev_entry = Some(e);
}
Entry::Vacant(e) => {
let e = e.insert_entry(value);
prev_entry = Some(e);
}
},
None => match prev_entry {
Some(ref mut entry) => {
entry.append(value);
}
None => unreachable!("HeaderMap::into_iter yielded None first"),
},
}
}
}
#[cfg(feature = "cookies")]
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn add_cookie_header(
headers: &mut HeaderMap,
cookie_store: &dyn crate::cookie::CookieStore,
url: &url::Url,
) {
if let Some(header) = cookie_store.cookies(url) {
headers.insert(crate::header::COOKIE, header);
}
}
pub(crate) struct Escape<'a>(&'a [u8]);
#[cfg(not(target_arch = "wasm32"))]
impl<'a> Escape<'a> {
pub(crate) fn new(bytes: &'a [u8]) -> Self {
Escape(bytes)
}
}
impl fmt::Debug for Escape<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "b\"{}\"", self)?;
Ok(())
}
}
impl fmt::Display for Escape<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &c in self.0 {
// https://doc.rust-lang.org/reference.html#byte-escapes
if c == b'\n' {
write!(f, "\\n")?;
} else if c == b'\r' {
write!(f, "\\r")?;
} else if c == b'\t' {
write!(f, "\\t")?;
} else if c == b'\\' || c == b'"' {
write!(f, "\\{}", c as char)?;
} else if c == b'\0' {
write!(f, "\\0")?;
// ASCII printable
} else if c >= 0x20 && c < 0x7f {
write!(f, "{}", c as char)?;
} else {
write!(f, "\\x{c:02x}")?;
}
}
Ok(())
}
}

320
vendor/reqwest/src/wasm/body.rs vendored Normal file
View File

@@ -0,0 +1,320 @@
#[cfg(feature = "multipart")]
use super::multipart::Form;
/// dox
use bytes::Bytes;
use js_sys::Uint8Array;
use std::{borrow::Cow, fmt};
use wasm_bindgen::JsValue;
/// The body of a `Request`.
///
/// In most cases, this is not needed directly, as the
/// [`RequestBuilder.body`][builder] method uses `Into<Body>`, which allows
/// passing many things (like a string or vector of bytes).
///
/// [builder]: ./struct.RequestBuilder.html#method.body
pub struct Body {
inner: Inner,
}
enum Inner {
Single(Single),
/// MultipartForm holds a multipart/form-data body.
#[cfg(feature = "multipart")]
MultipartForm(Form),
}
#[derive(Clone)]
pub(crate) enum Single {
Bytes(Bytes),
Text(Cow<'static, str>),
}
impl Single {
fn as_bytes(&self) -> &[u8] {
match self {
Single::Bytes(bytes) => bytes.as_ref(),
Single::Text(text) => text.as_bytes(),
}
}
pub(crate) fn to_js_value(&self) -> JsValue {
match self {
Single::Bytes(bytes) => {
let body_bytes: &[u8] = bytes.as_ref();
let body_uint8_array: Uint8Array = body_bytes.into();
let js_value: &JsValue = body_uint8_array.as_ref();
js_value.to_owned()
}
Single::Text(text) => JsValue::from_str(text),
}
}
fn is_empty(&self) -> bool {
match self {
Single::Bytes(bytes) => bytes.is_empty(),
Single::Text(text) => text.is_empty(),
}
}
}
impl Body {
/// Returns a reference to the internal data of the `Body`.
///
/// `None` is returned, if the underlying data is a multipart form.
#[inline]
pub fn as_bytes(&self) -> Option<&[u8]> {
match &self.inner {
Inner::Single(single) => Some(single.as_bytes()),
#[cfg(feature = "multipart")]
Inner::MultipartForm(_) => None,
}
}
pub(crate) fn to_js_value(&self) -> crate::Result<JsValue> {
match &self.inner {
Inner::Single(single) => Ok(single.to_js_value()),
#[cfg(feature = "multipart")]
Inner::MultipartForm(form) => {
let form_data = form.to_form_data()?;
let js_value: &JsValue = form_data.as_ref();
Ok(js_value.to_owned())
}
}
}
#[cfg(feature = "multipart")]
pub(crate) fn as_single(&self) -> Option<&Single> {
match &self.inner {
Inner::Single(single) => Some(single),
Inner::MultipartForm(_) => None,
}
}
#[inline]
#[cfg(feature = "multipart")]
pub(crate) fn from_form(f: Form) -> Body {
Self {
inner: Inner::MultipartForm(f),
}
}
/// into_part turns a regular body into the body of a multipart/form-data part.
#[cfg(feature = "multipart")]
pub(crate) fn into_part(self) -> Body {
match self.inner {
Inner::Single(single) => Self {
inner: Inner::Single(single),
},
Inner::MultipartForm(form) => Self {
inner: Inner::MultipartForm(form),
},
}
}
pub(crate) fn is_empty(&self) -> bool {
match &self.inner {
Inner::Single(single) => single.is_empty(),
#[cfg(feature = "multipart")]
Inner::MultipartForm(form) => form.is_empty(),
}
}
pub(crate) fn try_clone(&self) -> Option<Body> {
match &self.inner {
Inner::Single(single) => Some(Self {
inner: Inner::Single(single.clone()),
}),
#[cfg(feature = "multipart")]
Inner::MultipartForm(_) => None,
}
}
}
impl From<Bytes> for Body {
#[inline]
fn from(bytes: Bytes) -> Body {
Body {
inner: Inner::Single(Single::Bytes(bytes)),
}
}
}
impl From<Vec<u8>> for Body {
#[inline]
fn from(vec: Vec<u8>) -> Body {
Body {
inner: Inner::Single(Single::Bytes(vec.into())),
}
}
}
impl From<&'static [u8]> for Body {
#[inline]
fn from(s: &'static [u8]) -> Body {
Body {
inner: Inner::Single(Single::Bytes(Bytes::from_static(s))),
}
}
}
impl From<String> for Body {
#[inline]
fn from(s: String) -> Body {
Body {
inner: Inner::Single(Single::Text(s.into())),
}
}
}
impl From<&'static str> for Body {
#[inline]
fn from(s: &'static str) -> Body {
Body {
inner: Inner::Single(Single::Text(s.into())),
}
}
}
impl fmt::Debug for Body {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Body").finish()
}
}
impl Default for Body {
fn default() -> Body {
Body {
inner: Inner::Single(Single::Bytes(Bytes::new())),
}
}
}
// Can use new methods in web-sys when requiring v0.2.93.
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
#[cfg(test)]
mod tests {
use crate::Body;
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
fn log(s: String);
}
#[wasm_bindgen_test]
async fn test_body() {
let body = Body::from("TEST");
assert_eq!([84, 69, 83, 84], body.as_bytes().unwrap());
}
#[wasm_bindgen_test]
async fn test_body_js_static_str() {
let body_value = "TEST";
let body = Body::from(body_value);
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get request body as text");
assert_eq!(text.as_string().expect("text is not a string"), body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_string() {
let body_value = "TEST".to_string();
let body = Body::from(body_value.clone());
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let text_promise = js_req.text().expect("could not get text promise");
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get request body as text");
assert_eq!(text.as_string().expect("text is not a string"), body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_static_u8_slice() {
let body_value: &'static [u8] = b"\x00\x42";
let body = Body::from(body_value);
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
.await
.expect("could not get request body as array buffer");
let v = Uint8Array::new(&array_buffer).to_vec();
assert_eq!(v, body_value);
}
#[wasm_bindgen_test]
async fn test_body_js_vec_u8() {
let body_value = vec![0u8, 42];
let body = Body::from(body_value.clone());
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
body.to_js_value()
.expect("could not convert body to JsValue")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let array_buffer_promise = js_req
.array_buffer()
.expect("could not get array_buffer promise");
let array_buffer = crate::wasm::promise::<JsValue>(array_buffer_promise)
.await
.expect("could not get request body as array buffer");
let v = Uint8Array::new(&array_buffer).to_vec();
assert_eq!(v, body_value);
}
}

473
vendor/reqwest/src/wasm/client.rs vendored Normal file
View File

@@ -0,0 +1,473 @@
use http::header::USER_AGENT;
use http::{HeaderMap, HeaderValue, Method};
use js_sys::{Promise, JSON};
use std::convert::TryInto;
use std::{fmt, future::Future, sync::Arc};
use url::Url;
use wasm_bindgen::prelude::{wasm_bindgen, UnwrapThrowExt as _};
use super::{AbortGuard, Request, RequestBuilder, Response};
use crate::IntoUrl;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = fetch)]
fn fetch_with_request(input: &web_sys::Request) -> Promise;
}
fn js_fetch(req: &web_sys::Request) -> Promise {
use wasm_bindgen::{JsCast, JsValue};
let global = js_sys::global();
if let Ok(true) = js_sys::Reflect::has(&global, &JsValue::from_str("ServiceWorkerGlobalScope"))
{
global
.unchecked_into::<web_sys::ServiceWorkerGlobalScope>()
.fetch_with_request(req)
} else {
// browser
fetch_with_request(req)
}
}
/// An HTTP Client for WebAssembly.
///
/// Uses the browser's Fetch API to make requests. The `Client` holds
/// configuration that applies to all requests. To configure a `Client`,
/// use `Client::builder()`.
#[derive(Clone)]
pub struct Client {
config: Arc<Config>,
}
/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
pub struct ClientBuilder {
config: Config,
}
impl Client {
/// Constructs a new `Client`.
pub fn new() -> Self {
Client::builder().build().unwrap_throw()
}
/// Creates a `ClientBuilder` to configure a `Client`.
///
/// This is the same as `ClientBuilder::new()`.
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
/// Convenience method to make a `GET` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::GET, url)
}
/// Convenience method to make a `POST` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::POST, url)
}
/// Convenience method to make a `PUT` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PUT, url)
}
/// Convenience method to make a `PATCH` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PATCH, url)
}
/// Convenience method to make a `DELETE` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::DELETE, url)
}
/// Convenience method to make a `HEAD` request to a URL.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::HEAD, url)
}
/// Start building a `Request` with the `Method` and `Url`.
///
/// Returns a `RequestBuilder`, which will allow setting headers and
/// request body before sending.
///
/// # Errors
///
/// This method fails whenever supplied `Url` cannot be parsed.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
let req = url.into_url().map(move |url| Request::new(method, url));
RequestBuilder::new(self.clone(), req)
}
/// Executes a `Request`.
///
/// A `Request` can be built manually with `Request::new()` or obtained
/// from a RequestBuilder with `RequestBuilder::build()`.
///
/// You should prefer to use the `RequestBuilder` and
/// `RequestBuilder::send()`.
///
/// # Errors
///
/// This method fails if there was an error while sending request,
/// redirect loop was detected or redirect limit was exhausted.
pub fn execute(
&self,
request: Request,
) -> impl Future<Output = Result<Response, crate::Error>> {
self.execute_request(request)
}
// merge request headers with Client default_headers, prior to external http fetch
fn merge_headers(&self, req: &mut Request) {
use http::header::Entry;
let headers: &mut HeaderMap = req.headers_mut();
// insert default headers in the request headers
// without overwriting already appended headers.
for (key, value) in self.config.headers.iter() {
if let Entry::Vacant(entry) = headers.entry(key) {
entry.insert(value.clone());
}
}
}
pub(super) fn execute_request(
&self,
mut req: Request,
) -> impl Future<Output = crate::Result<Response>> {
self.merge_headers(&mut req);
fetch(req)
}
}
impl Default for Client {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("Client");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
impl fmt::Debug for ClientBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("ClientBuilder");
self.config.fmt_fields(&mut builder);
builder.finish()
}
}
// Can use new methods in web-sys when requiring v0.2.93.
// > `init.method(m)` to `init.set_method(m)`
// For now, ignore their deprecation.
#[allow(deprecated)]
async fn fetch(req: Request) -> crate::Result<Response> {
// Build the js Request
let mut init = web_sys::RequestInit::new();
init.method(req.method().as_str());
// convert HeaderMap to Headers
let js_headers = web_sys::Headers::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
for (name, value) in req.headers() {
js_headers
.append(
name.as_str(),
value.to_str().map_err(crate::error::builder)?,
)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
}
init.headers(&js_headers.into());
// When req.cors is true, do nothing because the default mode is 'cors'
if !req.cors {
init.mode(web_sys::RequestMode::NoCors);
}
if let Some(creds) = req.credentials {
init.credentials(creds);
}
if let Some(cache) = req.cache {
init.set_cache(cache);
}
if let Some(body) = req.body() {
if !body.is_empty() {
init.body(Some(body.to_js_value()?.as_ref()));
}
}
let mut abort = AbortGuard::new()?;
if let Some(timeout) = req.timeout() {
abort.timeout(*timeout);
}
init.signal(Some(&abort.signal()));
let js_req = web_sys::Request::new_with_str_and_init(req.url().as_str(), &init)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
// Await the fetch() promise
let p = js_fetch(&js_req);
let js_resp = super::promise::<web_sys::Response>(p)
.await
.map_err(|error| {
if error.to_string() == "JsValue(\"reqwest::errors::TimedOut\")" {
crate::error::TimedOut.into()
} else {
error
}
})
.map_err(crate::error::request)?;
// Convert from the js Response
let mut resp = http::Response::builder().status(js_resp.status());
let url = Url::parse(&js_resp.url()).expect_throw("url parse");
let js_headers = js_resp.headers();
let js_iter = js_sys::try_iter(&js_headers)
.expect_throw("headers try_iter")
.expect_throw("headers have an iterator");
for item in js_iter {
let item = item.expect_throw("headers iterator doesn't throw");
let serialized_headers: String = JSON::stringify(&item)
.expect_throw("serialized headers")
.into();
let [name, value]: [String; 2] = serde_json::from_str(&serialized_headers)
.expect_throw("deserializable serialized headers");
resp = resp.header(&name, &value);
}
resp.body(js_resp)
.map(|resp| Response::new(resp, url, abort))
.map_err(crate::error::request)
}
// ===== impl ClientBuilder =====
impl ClientBuilder {
/// Constructs a new `ClientBuilder`.
///
/// This is the same as `Client::builder()`.
pub fn new() -> Self {
ClientBuilder {
config: Config::default(),
}
}
/// Returns a 'Client' that uses this ClientBuilder configuration
pub fn build(mut self) -> Result<Client, crate::Error> {
if let Some(err) = self.config.error {
return Err(err);
}
let config = std::mem::take(&mut self.config);
Ok(Client {
config: Arc::new(config),
})
}
/// Sets the `User-Agent` header to be used by this client.
pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
where
V: TryInto<HeaderValue>,
V::Error: Into<http::Error>,
{
match value.try_into() {
Ok(value) => {
self.config.headers.insert(USER_AGENT, value);
}
Err(e) => {
self.config.error = Some(crate::error::builder(e.into()));
}
}
self
}
/// Sets the default headers for every request
pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
for (key, value) in headers.iter() {
self.config.headers.insert(key, value.clone());
}
self
}
}
impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
struct Config {
headers: HeaderMap,
error: Option<crate::Error>,
}
impl Default for Config {
fn default() -> Config {
Config {
headers: HeaderMap::new(),
error: None,
}
}
}
impl Config {
fn fmt_fields(&self, f: &mut fmt::DebugStruct<'_, '_>) {
f.field("default_headers", &self.headers);
}
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn default_headers() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// merge headers as if client were about to issue fetch
client.merge_headers(&mut req);
let test_headers = req.headers();
assert!(test_headers.get(CONTENT_TYPE).is_some(), "content-type");
assert!(test_headers.get("x-custom").is_some(), "custom header");
assert!(test_headers.get("accept").is_none(), "no accept header");
}
#[wasm_bindgen_test]
async fn default_headers_clone() {
use crate::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert("x-custom", HeaderValue::from_static("flibbertigibbet"));
let client = crate::Client::builder()
.default_headers(headers)
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.header(CONTENT_TYPE, "text/plain")
.build()
.expect("request");
client.merge_headers(&mut req);
let headers1 = req.headers();
// confirm that request headers override defaults
assert_eq!(
headers1.get(CONTENT_TYPE).unwrap(),
"text/plain",
"request headers override defaults"
);
// confirm that request headers don't change client defaults
let mut req2 = client
.get("https://www.example.com/x")
.build()
.expect("req 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(CONTENT_TYPE).unwrap(),
"application/json",
"request headers don't change client defaults"
);
}
#[wasm_bindgen_test]
fn user_agent_header() {
use crate::header::USER_AGENT;
let client = crate::Client::builder()
.user_agent("FooBar/1.2.3")
.build()
.expect("client");
let mut req = client
.get("https://www.example.com")
.build()
.expect("request");
// Merge the client headers with the request's one.
client.merge_headers(&mut req);
let headers1 = req.headers();
// Confirm that we have the `User-Agent` header set
assert_eq!(
headers1.get(USER_AGENT).unwrap(),
"FooBar/1.2.3",
"The user-agent header was not set: {req:#?}"
);
// Now we try to overwrite the `User-Agent` value
let mut req2 = client
.get("https://www.example.com")
.header(USER_AGENT, "Another-User-Agent/42")
.build()
.expect("request 2");
client.merge_headers(&mut req2);
let headers2 = req2.headers();
assert_eq!(
headers2.get(USER_AGENT).expect("headers2 user agent"),
"Another-User-Agent/42",
"Was not able to overwrite the User-Agent value on the request-builder"
);
}
}

85
vendor/reqwest/src/wasm/mod.rs vendored Normal file
View File

@@ -0,0 +1,85 @@
use std::convert::TryInto;
use std::time::Duration;
use js_sys::Function;
use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{AbortController, AbortSignal};
mod body;
mod client;
/// TODO
#[cfg(feature = "multipart")]
pub mod multipart;
mod request;
mod response;
pub use self::body::Body;
pub use self::client::{Client, ClientBuilder};
pub use self::request::{Request, RequestBuilder};
pub use self::response::Response;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = "setTimeout")]
fn set_timeout(handler: &Function, timeout: i32) -> JsValue;
#[wasm_bindgen(js_name = "clearTimeout")]
fn clear_timeout(handle: JsValue) -> JsValue;
}
async fn promise<T>(promise: js_sys::Promise) -> Result<T, crate::error::BoxError>
where
T: JsCast,
{
use wasm_bindgen_futures::JsFuture;
let js_val = JsFuture::from(promise).await.map_err(crate::error::wasm)?;
js_val
.dyn_into::<T>()
.map_err(|_js_val| "promise resolved to unexpected type".into())
}
/// A guard that cancels a fetch request when dropped.
struct AbortGuard {
ctrl: AbortController,
timeout: Option<(JsValue, Closure<dyn FnMut()>)>,
}
impl AbortGuard {
fn new() -> crate::Result<Self> {
Ok(AbortGuard {
ctrl: AbortController::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?,
timeout: None,
})
}
fn signal(&self) -> AbortSignal {
self.ctrl.signal()
}
fn timeout(&mut self, timeout: Duration) {
let ctrl = self.ctrl.clone();
let abort =
Closure::once(move || ctrl.abort_with_reason(&"reqwest::errors::TimedOut".into()));
let timeout = set_timeout(
abort.as_ref().unchecked_ref::<js_sys::Function>(),
timeout.as_millis().try_into().expect("timeout"),
);
if let Some((id, _)) = self.timeout.replace((timeout, abort)) {
clear_timeout(id);
}
}
}
impl Drop for AbortGuard {
fn drop(&mut self) {
self.ctrl.abort();
if let Some((id, _)) = self.timeout.take() {
clear_timeout(id);
}
}
}

419
vendor/reqwest/src/wasm/multipart.rs vendored Normal file
View File

@@ -0,0 +1,419 @@
//! multipart/form-data
use std::borrow::Cow;
use std::fmt;
use http::HeaderMap;
use mime_guess::Mime;
use web_sys::FormData;
use super::Body;
/// An async multipart/form-data request.
pub struct Form {
inner: FormParts<Part>,
}
impl Form {
pub(crate) fn is_empty(&self) -> bool {
self.inner.fields.is_empty()
}
}
/// A field in a multipart form.
pub struct Part {
meta: PartMetadata,
value: Body,
}
pub(crate) struct FormParts<P> {
pub(crate) fields: Vec<(Cow<'static, str>, P)>,
}
pub(crate) struct PartMetadata {
mime: Option<Mime>,
file_name: Option<Cow<'static, str>>,
pub(crate) headers: HeaderMap,
}
pub(crate) trait PartProps {
fn metadata(&self) -> &PartMetadata;
}
// ===== impl Form =====
impl Default for Form {
fn default() -> Self {
Self::new()
}
}
impl Form {
/// Creates a new async Form without any content.
pub fn new() -> Form {
Form {
inner: FormParts::new(),
}
}
/// Add a data field with supplied name and value.
///
/// # Examples
///
/// ```
/// let form = reqwest::multipart::Form::new()
/// .text("username", "seanmonstar")
/// .text("password", "secret");
/// ```
pub fn text<T, U>(self, name: T, value: U) -> Form
where
T: Into<Cow<'static, str>>,
U: Into<Cow<'static, str>>,
{
self.part(name, Part::text(value))
}
/// Adds a customized Part.
pub fn part<T>(self, name: T, part: Part) -> Form
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.part(name, part))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(FormParts<Part>) -> FormParts<Part>,
{
Form {
inner: func(self.inner),
}
}
pub(crate) fn to_form_data(&self) -> crate::Result<FormData> {
let form = FormData::new()
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
for (name, part) in self.inner.fields.iter() {
part.append_to_form(name, &form)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)?;
}
Ok(form)
}
}
impl fmt::Debug for Form {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt_fields("Form", f)
}
}
// ===== impl Part =====
impl Part {
/// Makes a text parameter.
pub fn text<T>(value: T) -> Part
where
T: Into<Cow<'static, str>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(string) => Body::from(string),
};
Part::new(body)
}
/// Makes a new parameter from arbitrary bytes.
pub fn bytes<T>(value: T) -> Part
where
T: Into<Cow<'static, [u8]>>,
{
let body = match value.into() {
Cow::Borrowed(slice) => Body::from(slice),
Cow::Owned(vec) => Body::from(vec),
};
Part::new(body)
}
/// Makes a new parameter from an arbitrary stream.
pub fn stream<T: Into<Body>>(value: T) -> Part {
Part::new(value.into())
}
fn new(value: Body) -> Part {
Part {
meta: PartMetadata::new(),
value: value.into_part(),
}
}
/// Tries to set the mime of this part.
pub fn mime_str(self, mime: &str) -> crate::Result<Part> {
Ok(self.mime(mime.parse().map_err(crate::error::builder)?))
}
// Re-export when mime 0.4 is available, with split MediaType/MediaRange.
fn mime(self, mime: Mime) -> Part {
self.with_inner(move |inner| inner.mime(mime))
}
/// Sets the filename, builder style.
pub fn file_name<T>(self, filename: T) -> Part
where
T: Into<Cow<'static, str>>,
{
self.with_inner(move |inner| inner.file_name(filename))
}
/// Sets custom headers for the part.
pub fn headers(self, headers: HeaderMap) -> Part {
self.with_inner(move |inner| inner.headers(headers))
}
fn with_inner<F>(self, func: F) -> Self
where
F: FnOnce(PartMetadata) -> PartMetadata,
{
Part {
meta: func(self.meta),
value: self.value,
}
}
fn append_to_form(
&self,
name: &str,
form: &web_sys::FormData,
) -> Result<(), wasm_bindgen::JsValue> {
let single = self
.value
.as_single()
.expect("A part's body can't be multipart itself");
let mut mime_type = self.metadata().mime.as_ref();
// The JS fetch API doesn't support file names and mime types for strings. So we do our best
// effort to use `append_with_str` and fallback to `append_with_blob_*` if that's not
// possible.
if let super::body::Single::Text(text) = single {
if mime_type.is_none() || mime_type == Some(&mime_guess::mime::TEXT_PLAIN) {
if self.metadata().file_name.is_none() {
return form.append_with_str(name, text);
}
} else {
mime_type = Some(&mime_guess::mime::TEXT_PLAIN);
}
}
let blob = self.blob(mime_type)?;
if let Some(file_name) = &self.metadata().file_name {
form.append_with_blob_and_filename(name, &blob, file_name)
} else {
form.append_with_blob(name, &blob)
}
}
fn blob(&self, mime_type: Option<&Mime>) -> crate::Result<web_sys::Blob> {
use web_sys::Blob;
use web_sys::BlobPropertyBag;
let mut properties = BlobPropertyBag::new();
if let Some(mime) = mime_type {
properties.type_(mime.as_ref());
}
let js_value = self
.value
.as_single()
.expect("A part's body can't be set to a multipart body")
.to_js_value();
let body_array = js_sys::Array::new();
body_array.push(&js_value);
Blob::new_with_u8_array_sequence_and_options(body_array.as_ref(), &properties)
.map_err(crate::error::wasm)
.map_err(crate::error::builder)
}
}
impl fmt::Debug for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut dbg = f.debug_struct("Part");
dbg.field("value", &self.value);
self.meta.fmt_fields(&mut dbg);
dbg.finish()
}
}
impl PartProps for Part {
fn metadata(&self) -> &PartMetadata {
&self.meta
}
}
// ===== impl FormParts =====
impl<P: PartProps> FormParts<P> {
pub(crate) fn new() -> Self {
FormParts { fields: Vec::new() }
}
/// Adds a customized Part.
pub(crate) fn part<T>(mut self, name: T, part: P) -> Self
where
T: Into<Cow<'static, str>>,
{
self.fields.push((name.into(), part));
self
}
}
impl<P: fmt::Debug> FormParts<P> {
pub(crate) fn fmt_fields(&self, ty_name: &'static str, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct(ty_name)
.field("parts", &self.fields)
.finish()
}
}
// ===== impl PartMetadata =====
impl PartMetadata {
pub(crate) fn new() -> Self {
PartMetadata {
mime: None,
file_name: None,
headers: HeaderMap::default(),
}
}
pub(crate) fn mime(mut self, mime: Mime) -> Self {
self.mime = Some(mime);
self
}
pub(crate) fn file_name<T>(mut self, filename: T) -> Self
where
T: Into<Cow<'static, str>>,
{
self.file_name = Some(filename.into());
self
}
pub(crate) fn headers<T>(mut self, headers: T) -> Self
where
T: Into<HeaderMap>,
{
self.headers = headers.into();
self
}
}
impl PartMetadata {
pub(crate) fn fmt_fields<'f, 'fa, 'fb>(
&self,
debug_struct: &'f mut fmt::DebugStruct<'fa, 'fb>,
) -> &'f mut fmt::DebugStruct<'fa, 'fb> {
debug_struct
.field("mime", &self.mime)
.field("file_name", &self.file_name)
.field("headers", &self.headers)
}
}
#[cfg(test)]
mod tests {
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn test_multipart_js() {
use super::{Form, Part};
use js_sys::Uint8Array;
use wasm_bindgen::JsValue;
use web_sys::{File, FormData};
let text_file_name = "test.txt";
let text_file_type = "text/plain";
let text_content = "TEST";
let text_part = Part::text(text_content)
.file_name(text_file_name)
.mime_str(text_file_type)
.expect("invalid mime type");
let binary_file_name = "binary.bin";
let binary_file_type = "application/octet-stream";
let binary_content = vec![0u8, 42];
let binary_part = Part::bytes(binary_content.clone())
.file_name(binary_file_name)
.mime_str(binary_file_type)
.expect("invalid mime type");
let string_name = "string";
let string_content = "CONTENT";
let string_part = Part::text(string_content);
let text_name = "text part";
let binary_name = "binary part";
let form = Form::new()
.part(text_name, text_part)
.part(binary_name, binary_part)
.part(string_name, string_part);
let mut init = web_sys::RequestInit::new();
init.method("POST");
init.body(Some(
form.to_form_data()
.expect("could not convert to FormData")
.as_ref(),
));
let js_req = web_sys::Request::new_with_str_and_init("", &init)
.expect("could not create JS request");
let form_data_promise = js_req.form_data().expect("could not get form_data promise");
let form_data = crate::wasm::promise::<FormData>(form_data_promise)
.await
.expect("could not get body as form data");
// check text part
let text_file = File::from(form_data.get(text_name));
assert_eq!(text_file.name(), text_file_name);
assert_eq!(text_file.type_(), text_file_type);
let text_promise = text_file.text();
let text = crate::wasm::promise::<JsValue>(text_promise)
.await
.expect("could not get text body as text");
assert_eq!(
text.as_string().expect("text is not a string"),
text_content
);
// check binary part
let binary_file = File::from(form_data.get(binary_name));
assert_eq!(binary_file.name(), binary_file_name);
assert_eq!(binary_file.type_(), binary_file_type);
// check string part
let string = form_data
.get(string_name)
.as_string()
.expect("content is not a string");
assert_eq!(string, string_content);
let binary_array_buffer_promise = binary_file.array_buffer();
let array_buffer = crate::wasm::promise::<JsValue>(binary_array_buffer_promise)
.await
.expect("could not get request body as array buffer");
let binary = Uint8Array::new(&array_buffer).to_vec();
assert_eq!(binary, binary_content);
}
}

621
vendor/reqwest/src/wasm/request.rs vendored Normal file
View File

@@ -0,0 +1,621 @@
use std::convert::TryFrom;
use std::fmt;
use std::time::Duration;
use bytes::Bytes;
use http::{request::Parts, Method, Request as HttpRequest};
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use url::Url;
use web_sys::{RequestCache, RequestCredentials};
use super::{Body, Client, Response};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
/// A request which can be executed with `Client::execute()`.
pub struct Request {
method: Method,
url: Url,
headers: HeaderMap,
body: Option<Body>,
timeout: Option<Duration>,
pub(super) cors: bool,
pub(super) credentials: Option<RequestCredentials>,
pub(super) cache: Option<RequestCache>,
}
/// A builder to construct the properties of a `Request`.
pub struct RequestBuilder {
client: Client,
request: crate::Result<Request>,
}
impl Request {
/// Constructs a new request.
#[inline]
pub fn new(method: Method, url: Url) -> Self {
Request {
method,
url,
headers: HeaderMap::new(),
body: None,
timeout: None,
cors: true,
credentials: None,
cache: None,
}
}
/// Get the method.
#[inline]
pub fn method(&self) -> &Method {
&self.method
}
/// Get a mutable reference to the method.
#[inline]
pub fn method_mut(&mut self) -> &mut Method {
&mut self.method
}
/// Get the url.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/// Get a mutable reference to the url.
#[inline]
pub fn url_mut(&mut self) -> &mut Url {
&mut self.url
}
/// Get the headers.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
/// Get a mutable reference to the headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
/// Get the body.
#[inline]
pub fn body(&self) -> Option<&Body> {
self.body.as_ref()
}
/// Get a mutable reference to the body.
#[inline]
pub fn body_mut(&mut self) -> &mut Option<Body> {
&mut self.body
}
/// Get the timeout.
#[inline]
pub fn timeout(&self) -> Option<&Duration> {
self.timeout.as_ref()
}
/// Get a mutable reference to the timeout.
#[inline]
pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
&mut self.timeout
}
/// Attempts to clone the `Request`.
///
/// None is returned if a body is which can not be cloned.
pub fn try_clone(&self) -> Option<Request> {
let body = match self.body.as_ref() {
Some(body) => Some(body.try_clone()?),
None => None,
};
Some(Self {
method: self.method.clone(),
url: self.url.clone(),
headers: self.headers.clone(),
body,
timeout: self.timeout,
cors: self.cors,
credentials: self.credentials,
cache: self.cache,
})
}
}
impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
RequestBuilder { client, request }
}
/// Assemble a builder starting from an existing `Client` and a `Request`.
pub fn from_parts(client: crate::Client, request: crate::Request) -> crate::RequestBuilder {
crate::RequestBuilder {
client,
request: crate::Result::Ok(request),
}
}
/// Modify the query string of the URL.
///
/// Modifies the URL of this request, adding the parameters provided.
/// This method appends and does not overwrite. This means that it can
/// be called multiple times and that existing query parameters are not
/// overwritten if the same key is used. The key will simply show up
/// twice in the query string.
/// Calling `.query([("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
///
/// # Note
/// This method does not support serializing a single key-value
/// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
/// as `.query(&[("key", "val")])`. It's also possible to serialize structs
/// and maps into a key-value pair.
///
/// # Errors
/// This method will fail if the object you provide cannot be serialized
/// into a query string.
pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
let url = req.url_mut();
let mut pairs = url.query_pairs_mut();
let serializer = serde_urlencoded::Serializer::new(&mut pairs);
if let Err(err) = query.serialize(serializer) {
error = Some(crate::error::builder(err));
}
}
if let Ok(ref mut req) = self.request {
if let Some("") = req.url().query() {
req.url_mut().set_query(None);
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Send a form body.
///
/// Sets the body to the url encoded serialization of the passed value,
/// and also sets the `Content-Type: application/x-www-form-urlencoded`
/// header.
///
/// # Errors
///
/// This method fails if the passed value cannot be serialized into
/// url encoded format
pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_urlencoded::to_string(form) {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
/// Set the request json
pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
let mut error = None;
if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) {
Ok(body) => {
req.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
}
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Enable HTTP basic authentication.
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
{
let header_value = crate::util::basic_auth(username, password);
self.header(crate::header::AUTHORIZATION, header_value)
}
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
let header_value = format!("Bearer {token}");
self.header(crate::header::AUTHORIZATION, header_value)
}
/// Set the request body.
pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.body = Some(body.into());
}
self
}
/// Enables a request timeout.
pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.timeout_mut() = Some(timeout);
}
self
}
/// TODO
#[cfg(feature = "multipart")]
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
pub fn multipart(mut self, multipart: super::multipart::Form) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
*req.body_mut() = Some(Body::from_form(multipart))
}
self
}
/// Add a `Header` to this Request.
pub fn header<K, V>(mut self, key: K, value: V) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
let mut error = None;
if let Ok(ref mut req) = self.request {
match <HeaderName as TryFrom<K>>::try_from(key) {
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(value) => {
req.headers_mut().append(key, value);
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
};
}
if let Some(err) = error {
self.request = Err(err);
}
self
}
/// Add a set of Headers to the existing ones on this Request.
///
/// The headers will be merged in to any already set.
pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
crate::util::replace_headers(req.headers_mut(), headers);
}
self
}
/// Disable CORS on fetching the request.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request mode][mdn] will be set to 'no-cors'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
pub fn fetch_mode_no_cors(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cors = false;
}
self
}
/// Set fetch credentials to 'same-origin'
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request credentials][mdn] will be set to 'same-origin'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
pub fn fetch_credentials_same_origin(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::SameOrigin);
}
self
}
/// Set fetch credentials to 'include'
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request credentials][mdn] will be set to 'include'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
pub fn fetch_credentials_include(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::Include);
}
self
}
/// Set fetch credentials to 'omit'
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request credentials][mdn] will be set to 'omit'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
pub fn fetch_credentials_omit(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.credentials = Some(RequestCredentials::Omit);
}
self
}
/// Set fetch cache mode to 'default'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'default'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_default(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::Default);
}
self
}
/// Set fetch cache mode to 'no-store'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'no-store'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_no_store(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::NoStore);
}
self
}
/// Set fetch cache mode to 'reload'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'reload'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_reload(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::Reload);
}
self
}
/// Set fetch cache mode to 'no-cache'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'no-cache'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_no_cache(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::NoCache);
}
self
}
/// Set fetch cache mode to 'force-cache'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'force-cache'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_force_cache(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::ForceCache);
}
self
}
/// Set fetch cache mode to 'only-if-cached'.
///
/// # WASM
///
/// This option is only effective with WebAssembly target.
///
/// The [request cache][mdn] will be set to 'only-if-cached'.
///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/cache
pub fn fetch_cache_only_if_cached(mut self) -> RequestBuilder {
if let Ok(ref mut req) = self.request {
req.cache = Some(RequestCache::OnlyIfCached);
}
self
}
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
pub fn build(self) -> crate::Result<Request> {
self.request
}
/// Build a `Request`, which can be inspected, modified and executed with
/// `Client::execute()`.
///
/// This is similar to [`RequestBuilder::build()`], but also returns the
/// embedded `Client`.
pub fn build_split(self) -> (Client, crate::Result<Request>) {
(self.client, self.request)
}
/// Constructs the Request and sends it to the target URL, returning a
/// future Response.
///
/// # Errors
///
/// This method fails if there was an error while sending request.
///
/// # Example
///
/// ```no_run
/// # use reqwest::Error;
/// #
/// # async fn run() -> Result<(), Error> {
/// let response = reqwest::Client::new()
/// .get("https://hyper.rs")
/// .send()
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn send(self) -> crate::Result<Response> {
let req = self.request?;
self.client.execute_request(req).await
}
/// Attempt to clone the RequestBuilder.
///
/// `None` is returned if the RequestBuilder can not be cloned.
///
/// # Examples
///
/// ```no_run
/// # use reqwest::Error;
/// #
/// # fn run() -> Result<(), Error> {
/// let client = reqwest::Client::new();
/// let builder = client.post("http://httpbin.org/post")
/// .body("from a &str!");
/// let clone = builder.try_clone();
/// assert!(clone.is_some());
/// # Ok(())
/// # }
/// ```
pub fn try_clone(&self) -> Option<RequestBuilder> {
self.request
.as_ref()
.ok()
.and_then(|req| req.try_clone())
.map(|req| RequestBuilder {
client: self.client.clone(),
request: Ok(req),
})
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
}
}
impl fmt::Debug for RequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut builder = f.debug_struct("RequestBuilder");
match self.request {
Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
Err(ref err) => builder.field("error", err).finish(),
}
}
}
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
type Error = crate::Error;
fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
let (parts, body) = req.into_parts();
let Parts {
method,
uri,
headers,
..
} = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
url,
headers,
body: Some(body.into()),
timeout: None,
cors: true,
credentials: None,
cache: None,
})
}
}
impl TryFrom<Request> for HttpRequest<Body> {
type Error = crate::Error;
fn try_from(req: Request) -> crate::Result<Self> {
let Request {
method,
url,
headers,
body,
..
} = req;
let mut req = HttpRequest::builder()
.method(method)
.uri(url.as_str())
.body(body.unwrap_or_else(|| Body::from(Bytes::default())))
.map_err(crate::error::builder)?;
*req.headers_mut() = headers;
Ok(req)
}
}

195
vendor/reqwest/src/wasm/response.rs vendored Normal file
View File

@@ -0,0 +1,195 @@
use std::fmt;
use bytes::Bytes;
use http::{HeaderMap, StatusCode};
use js_sys::Uint8Array;
use url::Url;
use crate::wasm::AbortGuard;
#[cfg(feature = "stream")]
use wasm_bindgen::JsCast;
#[cfg(feature = "stream")]
use futures_util::stream::{self, StreamExt};
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
/// A Response to a submitted `Request`.
pub struct Response {
http: http::Response<web_sys::Response>,
_abort: AbortGuard,
// Boxed to save space (11 words to 1 word), and it's not accessed
// frequently internally.
url: Box<Url>,
}
impl Response {
pub(super) fn new(
res: http::Response<web_sys::Response>,
url: Url,
abort: AbortGuard,
) -> Response {
Response {
http: res,
url: Box::new(url),
_abort: abort,
}
}
/// Get the `StatusCode` of this `Response`.
#[inline]
pub fn status(&self) -> StatusCode {
self.http.status()
}
/// Get the `Headers` of this `Response`.
#[inline]
pub fn headers(&self) -> &HeaderMap {
self.http.headers()
}
/// Get a mutable reference to the `Headers` of this `Response`.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.http.headers_mut()
}
/// Get the content-length of this response, if known.
///
/// Reasons it may not be known:
///
/// - The server didn't send a `content-length` header.
/// - The response is compressed and automatically decoded (thus changing
/// the actual decoded length).
pub fn content_length(&self) -> Option<u64> {
self.headers()
.get(http::header::CONTENT_LENGTH)?
.to_str()
.ok()?
.parse()
.ok()
}
/// Get the final `Url` of this `Response`.
#[inline]
pub fn url(&self) -> &Url {
&self.url
}
/* It might not be possible to detect this in JS?
/// Get the HTTP `Version` of this `Response`.
#[inline]
pub fn version(&self) -> Version {
self.http.version()
}
*/
/// Try to deserialize the response body as JSON.
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn json<T: DeserializeOwned>(self) -> crate::Result<T> {
let full = self.bytes().await?;
serde_json::from_slice(&full).map_err(crate::error::decode)
}
/// Get the response text.
pub async fn text(self) -> crate::Result<String> {
let p = self
.http
.body()
.text()
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?;
let js_val = super::promise::<wasm_bindgen::JsValue>(p)
.await
.map_err(crate::error::decode)?;
if let Some(s) = js_val.as_string() {
Ok(s)
} else {
Err(crate::error::decode("response.text isn't string"))
}
}
/// Get the response as bytes
pub async fn bytes(self) -> crate::Result<Bytes> {
let p = self
.http
.body()
.array_buffer()
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?;
let buf_js = super::promise::<wasm_bindgen::JsValue>(p)
.await
.map_err(crate::error::decode)?;
let buffer = Uint8Array::new(&buf_js);
let mut bytes = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes);
Ok(bytes.into())
}
/// Convert the response into a `Stream` of `Bytes` from the body.
#[cfg(feature = "stream")]
pub fn bytes_stream(self) -> impl futures_core::Stream<Item = crate::Result<Bytes>> {
use futures_core::Stream;
use std::pin::Pin;
let web_response = self.http.into_body();
let abort = self._abort;
if let Some(body) = web_response.body() {
let body = wasm_streams::ReadableStream::from_raw(body.unchecked_into());
Box::pin(body.into_stream().map(move |buf_js| {
// Keep the abort guard alive as long as this stream is.
let _abort = &abort;
let buffer = Uint8Array::new(
&buf_js
.map_err(crate::error::wasm)
.map_err(crate::error::decode)?,
);
let mut bytes = vec![0; buffer.length() as usize];
buffer.copy_to(&mut bytes);
Ok(bytes.into())
})) as Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>
} else {
// If there's no body, return an empty stream.
Box::pin(stream::empty()) as Pin<Box<dyn Stream<Item = crate::Result<Bytes>>>>
}
}
// util methods
/// Turn a response into an error if the server returned an error.
pub fn error_for_status(self) -> crate::Result<Self> {
let status = self.status();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url, status))
} else {
Ok(self)
}
}
/// Turn a reference to a response into an error if the server returned an error.
pub fn error_for_status_ref(&self) -> crate::Result<&Self> {
let status = self.status();
if status.is_client_error() || status.is_server_error() {
Err(crate::error::status_code(*self.url.clone(), status))
} else {
Ok(self)
}
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
//.field("url", self.url())
.field("status", &self.status())
.field("headers", self.headers())
.finish()
}
}