Compare commits

...

10 Commits

Author SHA1 Message Date
Matthew Ransley
bb34f72c2f Chrome 129
Some checks failed
CI / CI is green (push) Has been cancelled
CI / Check Style (push) Has been cancelled
CI / feat.: blocking (push) Has been cancelled
CI / windows / stable-i686-gnu (push) Has been cancelled
CI / windows / stable-i686-msvc (push) Has been cancelled
CI / windows / stable-x86_64-gnu (push) Has been cancelled
CI / windows / stable-x86_64-msvc (push) Has been cancelled
CI / feat.: brotli (push) Has been cancelled
CI / feat.: cookies (push) Has been cancelled
CI / feat.: deflate (push) Has been cancelled
CI / feat.: gzip (push) Has been cancelled
CI / feat.: json (push) Has been cancelled
CI / feat.: multipart (push) Has been cancelled
CI / feat.: native-tls (push) Has been cancelled
CI / feat.: default-tls and rustls-tls (push) Has been cancelled
CI / feat.: socks/default-tls (push) Has been cancelled
CI / feat.: socks/rustls-tls (push) Has been cancelled
CI / feat.: stream (push) Has been cancelled
CI / feat.: trust-dns (push) Has been cancelled
CI / feat.: rustls-tls (push) Has been cancelled
CI / feat.: rustls-tls-manual-roots (push) Has been cancelled
CI / feat.: default-tls disabled (push) Has been cancelled
CI / linux / beta (push) Has been cancelled
CI / linux / stable (push) Has been cancelled
CI / macOS / stable (push) Has been cancelled
CI / Docs (push) Has been cancelled
CI / linux / nightly (push) Has been cancelled
CI / MSRV 1.57.0 (push) Has been cancelled
CI / Android (push) Has been cancelled
CI / WASM (push) Has been cancelled
Signed-off-by: Matthew Ransley <matthewransley5@gmail.com>
2024-10-24 19:35:59 +01:00
Matthew Ransley
08d5048e5b Merge branch 'master' of https://github.com/epicmatthew23/reqwest-impersonate 2023-07-05 14:19:26 +01:00
Matthew Ransley
4ee6bdec9a UTF encoded form 2023-07-05 14:17:53 +01:00
Matthew Ransley
3bd1d5d1b3 Update README.md 2023-07-04 20:12:52 +01:00
Matthew Ransley
d3f3a5fde3 Update README.md 2023-07-04 20:10:51 +01:00
Matthew Ransley
0437b3fccf Update README.md 2023-07-04 20:10:17 +01:00
Matthew Ransley
d51035a0f2 Ignore SSL 2023-07-04 20:02:09 +01:00
Matthew Ransley
97d5b067c8 v114 2023-07-03 19:23:16 +01:00
4JX
815e8695ad Fmt 2023-01-05 02:40:04 +01:00
4JX
fa96a507f4 Chrome V108 2023-01-05 02:25:20 +01:00
14 changed files with 576 additions and 422 deletions

105
README.md
View File

@@ -1,10 +1,6 @@
# reqwest-impersonate
A fork of reqwest used to impersonate the Chrome browser. Inspired by [curl-impersonate](https://github.com/lwthiker/curl-impersonate).
This crate was intended to be an experiment to learn more about TLS and HTTP2 fingerprinting. Some parts of reqwest may not have the code needed to work when used to copy Chrome.
It is currently missing HTTP/2 `PRIORITY` support. (PRs to [h2](https://github.com/hyperium/h2) are welcome)
A fork of [reqwest-impersonate](https://github.com/4JX/reqwest-impersonate), designed to provide more functionality and stability
**Notice:** This crate depends on patched dependencies. To use it, please add the following to your `Cargo.toml`.
@@ -14,14 +10,12 @@ hyper = { git = "https://github.com/4JX/hyper.git", branch = "v0.14.18-patched"
h2 = { git = "https://github.com/4JX/h2.git", branch = "imp" }
```
These patches were made specifically for `reqwest-impersonate` to work, but I would appreciate if someone took the time to PR more "proper" versions to the parent projects.
## Example
`Cargo.toml`
```toml
reqwest-impersonate = { git = "https://github.com/4JX/reqwest-impersonate.git", default-features = false, features = [
reqwest-impersonate = { git = "https://github.com/epicmatthew23/reqwest-impersonate.git", default-features = false, features = [
"chrome",
"blocking",
] }
@@ -33,9 +27,9 @@ reqwest-impersonate = { git = "https://github.com/4JX/reqwest-impersonate.git",
use reqwest_impersonate::browser::ChromeVersion;
fn main() {
// Build a client to mimic Chrome 104
// Build a client to mimic Chrome 114
let client = reqwest_impersonate::blocking::Client::builder()
.chrome_builder(ChromeVersion::V104)
.chrome_builder(ChromeVersion::V114)
.build()
.unwrap();
@@ -50,94 +44,3 @@ fn main() {
};
}
```
## Original readme
[![crates.io](https://img.shields.io/crates/v/reqwest.svg)](https://crates.io/crates/reqwest)
[![Documentation](https://docs.rs/reqwest/badge.svg)](https://docs.rs/reqwest)
[![MIT/Apache-2 licensed](https://img.shields.io/crates/l/reqwest.svg)](./LICENSE-APACHE)
[![CI](https://github.com/seanmonstar/reqwest/workflows/CI/badge.svg)](https://github.com/seanmonstar/reqwest/actions?query=workflow%3ACI)
An ergonomic, batteries-included HTTP Client for Rust.
- Plain bodies, JSON, urlencoded, multipart
- Customizable redirect policy
- HTTP Proxies
- HTTPS via system-native TLS (or optionally, rustls)
- Cookie Store
- WASM
- [Changelog](CHANGELOG.md)
## Example
This asynchronous example uses [Tokio](https://tokio.rs) and enables some
optional features, so your `Cargo.toml` could look like this:
```toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
```
And then the code:
```rust,no_run
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::get("https://httpbin.org/ip")
.await?
.json::<HashMap<String, String>>()
.await?;
println!("{:#?}", resp);
Ok(())
}
```
## Blocking Client
There is an optional "blocking" client API that can be enabled:
```toml
[dependencies]
reqwest = { version = "0.11", features = ["blocking", "json"] }
```
```rust,no_run
use std::collections::HashMap;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let resp = reqwest::blocking::get("https://httpbin.org/ip")?
.json::<HashMap<String, String>>()?;
println!("{:#?}", resp);
Ok(())
}
```
## Requirements
On Linux:
- OpenSSL 1.0.1, 1.0.2, 1.1.0, or 1.1.1 with headers (see <https://github.com/sfackler/rust-openssl>)
On Windows and macOS:
- Nothing.
Reqwest uses [rust-native-tls](https://github.com/sfackler/rust-native-tls),
which will use the operating system TLS framework if available, meaning Windows
and macOS. On Linux, it will use OpenSSL 1.1.
## 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.

37
examples/tls.rs Normal file
View File

@@ -0,0 +1,37 @@
//! `cargo run --example tls --features=blocking,chrome`
#![deny(warnings)]
use reqwest_impersonate::browser::ChromeVersion;
// This is using the `tokio` runtime. You'll need the following dependency:
//
// `tokio = { version = "1", features = ["full"] }`
#[cfg(not(target_arch = "wasm32"))]
fn main() -> Result<(), reqwest_impersonate::Error> {
// Build a client to mimic Chrome 104
let client = reqwest_impersonate::blocking::Client::builder()
.chrome_builder(ChromeVersion::V108)
.build()
.unwrap();
// Use the API you're already familiar with
match client.get("https://tls.peet.ws/api/all").send() {
Ok(res) => {
println!("{}", res.text().unwrap());
}
Err(err) => {
dbg!(err);
}
};
Ok(())
}
// The [cfg(not(target_arch = "wasm32"))] above prevent building the tokio::main function
// for wasm32 target, because tokio isn't compatible with wasm32.
// If you aren't building for wasm32, you don't need that line.
// The two lines below avoid the "'main' function not found" error when building for wasm32 target.
#[cfg(target_arch = "wasm32")]
fn main() {}

View File

@@ -56,7 +56,7 @@
# The main application derivation
reqwest-impersonate = craneLib.buildPackage
({
(rec {
src = nixLib.cleanSourceWith
{
src = workspaceSrc;
@@ -68,6 +68,7 @@
buildInputs = with pkgs;
[
openssl
boringssl
]
++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ ];
@@ -76,6 +77,8 @@
clang
pkg-config
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ ];
LD_LIBRARY_PATH = nixLib.makeLibraryPath buildInputs;
} // envVars);
in
{

View File

@@ -30,7 +30,7 @@ use super::response::Response;
use super::Body;
#[cfg(feature = "__chrome")]
use crate::browser::{configure_chrome, ChromeVersion};
use crate::connect::{Connector};
use crate::connect::Connector;
#[cfg(feature = "cookies")]
use crate::cookie;
#[cfg(feature = "trust-dns")]

View File

@@ -10,15 +10,15 @@ use serde::Serialize;
use serde_json;
use super::body::Body;
use super::client::{Client, Pending};
use super::client::{ Client, Pending };
#[cfg(feature = "multipart")]
use super::multipart;
use super::response::Response;
#[cfg(feature = "multipart")]
use crate::header::CONTENT_LENGTH;
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
use crate::{Method, Url};
use http::{request::Parts, Request as HttpRequest, Version};
use crate::header::{ HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE };
use crate::{ Method, Url };
use http::{ request::Parts, Request as HttpRequest, Version };
/// A request which can be executed with `Client::execute()`.
pub struct Request {
@@ -142,23 +142,9 @@ impl Request {
}
pub(super) fn pieces(
self,
) -> (
Method,
Url,
HeaderMap,
Option<Body>,
Option<Duration>,
Version,
) {
(
self.method,
self.url,
self.headers,
self.body,
self.timeout,
self.version,
)
self
) -> (Method, Url, HeaderMap, Option<Body>, Option<Duration>, Version) {
(self.method, self.url, self.headers, self.body, self.timeout, self.version)
}
}
@@ -166,8 +152,7 @@ impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
let mut builder = RequestBuilder { client, request };
let auth = builder
.request
let auth = builder.request
.as_mut()
.ok()
.and_then(|req| extract_authority(&mut req.url));
@@ -181,39 +166,44 @@ impl RequestBuilder {
/// Add a `Header` to this Request.
pub fn header<K, V>(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>,
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>
{
self.header_sensitive(key, value, false)
}
/// Add a `Header` to this Request with ability to define if header_value is sensitive.
fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
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(mut value) => {
// We want to potentially make an unsensitive header
// to be sensitive, not the reverse. So, don't turn off
// a previously sensitive header.
if sensitive {
value.set_sensitive(true);
Ok(key) =>
match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(mut value) => {
// We want to potentially make an unsensitive header
// to be sensitive, not the reverse. So, don't turn off
// a previously sensitive header.
if sensitive {
value.set_sensitive(true);
}
req.headers_mut().append(key, value);
}
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
req.headers_mut().append(key, value);
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
};
}
if let Some(err) = error {
@@ -247,14 +237,14 @@ impl RequestBuilder {
/// # }
/// ```
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
where U: fmt::Display, P: fmt::Display
{
let mut header_value = b"Basic ".to_vec();
{
let mut encoder =
Base64Encoder::from(&mut header_value, &base64::engine::DEFAULT_ENGINE);
let mut encoder = Base64Encoder::from(
&mut header_value,
&base64::engine::DEFAULT_ENGINE
);
// The unwraps here are fine because Vec::write* is infallible.
write!(encoder, "{}:", username).unwrap();
if let Some(password) = password {
@@ -266,10 +256,7 @@ impl RequestBuilder {
}
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder where T: fmt::Display {
let header_value = format!("Bearer {}", token);
self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
}
@@ -318,7 +305,7 @@ impl RequestBuilder {
pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
let mut builder = self.header(
CONTENT_TYPE,
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str()
);
builder = match multipart.compute_length() {
@@ -327,7 +314,7 @@ impl RequestBuilder {
};
if let Ok(ref mut req) = builder.request {
*req.body_mut() = Some(multipart.stream())
*req.body_mut() = Some(multipart.stream());
}
builder
}
@@ -414,11 +401,13 @@ impl RequestBuilder {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -444,11 +433,15 @@ impl RequestBuilder {
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.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/json")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -553,11 +546,9 @@ impl fmt::Debug for RequestBuilder {
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
req: &Request
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
f.field("method", &req.method).field("url", &req.url).field("headers", &req.headers)
}
/// Check the request URL for a "username:password" type authority, and if
@@ -566,21 +557,15 @@ pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>
use percent_encoding::percent_decode;
if url.has_authority() {
let username: String = percent_decode(url.username().as_bytes())
.decode_utf8()
.ok()?
.into();
let password = url.password().and_then(|pass| {
percent_decode(pass.as_bytes())
.decode_utf8()
.ok()
.map(String::from)
});
let username: String = percent_decode(url.username().as_bytes()).decode_utf8().ok()?.into();
let password = url
.password()
.and_then(|pass| {
percent_decode(pass.as_bytes()).decode_utf8().ok().map(String::from)
});
if !username.is_empty() || password.is_some() {
url.set_username("")
.expect("has_authority means set_username shouldn't fail");
url.set_password(None)
.expect("has_authority means set_password shouldn't fail");
url.set_username("").expect("has_authority means set_username shouldn't fail");
url.set_password(None).expect("has_authority means set_password shouldn't fail");
return Some((username, password));
}
}
@@ -588,21 +573,12 @@ pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>
None
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
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,
version,
..
} = parts;
let Parts { method, uri, headers, version, .. } = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
@@ -619,14 +595,7 @@ impl TryFrom<Request> for HttpRequest<Body> {
type Error = crate::Error;
fn try_from(req: Request) -> crate::Result<Self> {
let Request {
method,
url,
headers,
body,
version,
..
} = req;
let Request { method, url, headers, body, version, .. } = req;
let mut req = HttpRequest::builder()
.version(version)
@@ -642,7 +611,7 @@ impl TryFrom<Request> for HttpRequest<Body> {
#[cfg(test)]
mod tests {
use super::{Client, HttpRequest, Request, Version};
use super::{ Client, HttpRequest, Request, Version };
use crate::Method;
use serde::Serialize;
use std::collections::BTreeMap;
@@ -667,7 +636,12 @@ mod tests {
let some_url = "https://google.com/";
let r = client.get(some_url);
let r = r.query(&[("foo", "a"), ("foo", "b")]);
let r = r.query(
&[
("foo", "a"),
("foo", "b"),
]
);
let req = r.build().expect("request is valid");
assert_eq!(req.url().query(), Some("foo=a&foo=b"));
@@ -743,11 +717,7 @@ mod tests {
let some_url = "https://google.com/";
let empty_query: &[(&str, &str)] = &[];
let req = client
.get(some_url)
.query(empty_query)
.build()
.expect("request build");
let req = client.get(some_url).query(empty_query).build().expect("request build");
assert_eq!(req.url().query(), None);
assert_eq!(req.url().as_str(), "https://google.com/");
@@ -760,11 +730,7 @@ mod tests {
.post("http://httpbin.org/post")
.header("foo", "bar")
.body("from a &str!");
let req = builder
.try_clone()
.expect("clone successful")
.build()
.expect("request is valid");
let req = builder.try_clone().expect("clone successful").build().expect("request is valid");
assert_eq!(req.url().as_str(), "http://httpbin.org/post");
assert_eq!(req.method(), Method::POST);
assert_eq!(req.headers()["foo"], "bar");
@@ -774,11 +740,7 @@ mod tests {
fn try_clone_no_body() {
let client = Client::new();
let builder = client.get("http://httpbin.org/get");
let req = builder
.try_clone()
.expect("clone successful")
.build()
.expect("request is valid");
let req = builder.try_clone().expect("clone successful").build().expect("request is valid");
assert_eq!(req.url().as_str(), "http://httpbin.org/get");
assert_eq!(req.method(), Method::GET);
assert!(req.body().is_none());
@@ -790,9 +752,7 @@ mod tests {
let chunks: Vec<Result<_, ::std::io::Error>> = vec![Ok("hello"), Ok(" "), Ok("world")];
let stream = futures_util::stream::iter(chunks);
let client = Client::new();
let builder = client
.get("http://httpbin.org/get")
.body(super::Body::wrap_stream(stream));
let builder = client.get("http://httpbin.org/get").body(super::Body::wrap_stream(stream));
let clone = builder.try_clone();
assert!(clone.is_none());
}
@@ -805,10 +765,7 @@ mod tests {
let req = client.get(some_url).build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(
req.headers()["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
assert_eq!(req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
}
#[test]
@@ -823,10 +780,7 @@ mod tests {
.expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(
req.headers()["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
assert_eq!(req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
assert!(req.headers()["authorization"].is_sensitive());
}
@@ -835,11 +789,7 @@ mod tests {
let client = Client::new();
let some_url = "https://localhost/";
let req = client
.get(some_url)
.bearer_auth("Hold my bear")
.build()
.expect("request build");
let req = client.get(some_url).bearer_auth("Hold my bear").build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
@@ -854,11 +804,7 @@ mod tests {
let mut header = http::HeaderValue::from_static("in plain sight");
header.set_sensitive(true);
let req = client
.get(some_url)
.header("hiding", header)
.build()
.expect("request build");
let req = client.get(some_url).header("hiding", header).build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(req.headers()["hiding"], "in plain sight");

View File

@@ -3,18 +3,18 @@ use std::fmt;
use std::time::Duration;
use base64::encode;
use http::{request::Parts, Request as HttpRequest, Version};
use http::{ request::Parts, Request as HttpRequest, Version };
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use serde_urlencoded;
use super::body::{self, Body};
use super::body::{ self, Body };
#[cfg(feature = "multipart")]
use super::multipart;
use super::Client;
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
use crate::{async_impl, Method, Url};
use crate::header::{ HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE };
use crate::{ async_impl, Method, Url };
/// A request which can be executed with `Client::execute()`.
pub struct Request {
@@ -155,8 +155,7 @@ impl RequestBuilder {
pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
let mut builder = RequestBuilder { client, request };
let auth = builder
.request
let auth = builder.request
.as_mut()
.ok()
.and_then(|req| async_impl::request::extract_authority(req.url_mut()));
@@ -182,34 +181,39 @@ impl RequestBuilder {
/// # }
/// ```
pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>
{
self.header_sensitive(key, value, false)
}
/// Add a `Header` to this Request with ability to define if header_value is sensitive.
fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
where
HeaderName: TryFrom<K>,
HeaderValue: TryFrom<V>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
<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(mut value) => {
value.set_sensitive(sensitive);
req.headers_mut().append(key, value);
Ok(key) =>
match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(mut value) => {
value.set_sensitive(sensitive);
req.headers_mut().append(key, value);
}
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
};
}
if let Some(err) = error {
@@ -262,9 +266,7 @@ impl RequestBuilder {
/// # }
/// ```
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
where U: fmt::Display, P: fmt::Display
{
let auth = match password {
Some(password) => format!("{}:{}", username, password),
@@ -285,10 +287,7 @@ impl RequestBuilder {
/// # Ok(())
/// # }
/// ```
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder where T: fmt::Display {
let header_value = format!("Bearer {}", token);
self.header_sensitive(crate::header::AUTHORIZATION, &*header_value, true)
}
@@ -448,11 +447,13 @@ impl RequestBuilder {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -499,11 +500,15 @@ impl RequestBuilder {
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.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/json")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -536,13 +541,13 @@ impl RequestBuilder {
pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
let mut builder = self.header(
CONTENT_TYPE,
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str()
);
if let Ok(ref mut req) = builder.request {
*req.body_mut() = Some(match multipart.compute_length() {
Some(length) => Body::sized(multipart.reader(), length),
None => Body::new(multipart.reader()),
})
});
}
builder
}
@@ -619,20 +624,12 @@ impl RequestBuilder {
}
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
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 Parts { method, uri, headers, .. } = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
let mut inner = async_impl::Request::new(method, url);
crate::util::replace_headers(inner.headers_mut(), headers);
@@ -651,24 +648,22 @@ impl fmt::Debug for Request {
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
req: &Request
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", req.method())
.field("url", req.url())
.field("headers", req.headers())
f.field("method", req.method()).field("url", req.url()).field("headers", req.headers())
}
#[cfg(test)]
mod tests {
use super::super::{body, Client};
use super::{HttpRequest, Request, Version};
use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST};
use super::super::{ body, Client };
use super::{ HttpRequest, Request, Version };
use crate::header::{ HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST };
use crate::Method;
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use serde_urlencoded;
use std::collections::{BTreeMap, HashMap};
use std::collections::{ BTreeMap, HashMap };
use std::convert::TryFrom;
#[test]
@@ -822,7 +817,12 @@ mod tests {
let some_url = "https://google.com/";
let mut r = client.get(some_url);
r = r.query(&[("foo", "a"), ("foo", "b")]);
r = r.query(
&[
("foo", "a"),
("foo", "b"),
]
);
let req = r.build().expect("request is valid");
assert_eq!(req.url().query(), Some("foo=a&foo=b"));
@@ -881,7 +881,7 @@ mod tests {
// Make sure the content type was set
assert_eq!(
r.headers().get(CONTENT_TYPE).unwrap(),
&"application/x-www-form-urlencoded"
&"application/x-www-form-urlencoded; charset=UTF-8"
);
let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap();
@@ -915,14 +915,11 @@ mod tests {
#[cfg(feature = "json")]
fn add_json_fail() {
use serde::ser::Error as _;
use serde::{Serialize, Serializer};
use serde::{ Serialize, Serializer };
use std::error::Error as _;
struct MyStruct;
impl Serialize for MyStruct {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
Err(S::Error::custom("nope"))
}
}
@@ -967,11 +964,7 @@ mod tests {
let some_url = "https://google.com/";
let empty_query: &[(&str, &str)] = &[];
let req = client
.get(some_url)
.query(empty_query)
.build()
.expect("request build");
let req = client.get(some_url).query(empty_query).build().expect("request build");
assert_eq!(req.url().query(), None);
assert_eq!(req.url().as_str(), "https://google.com/");
@@ -985,10 +978,7 @@ mod tests {
let req = client.get(some_url).build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(
req.headers()["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
assert_eq!(req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
}
#[test]
@@ -1041,10 +1031,7 @@ mod tests {
.expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(
req.headers()["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
assert_eq!(req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
assert_eq!(req.headers()["authorization"].is_sensitive(), true);
}
@@ -1053,11 +1040,7 @@ mod tests {
let client = Client::new();
let some_url = "https://localhost/";
let req = client
.get(some_url)
.bearer_auth("Hold my bear")
.build()
.expect("request build");
let req = client.get(some_url).bearer_auth("Hold my bear").build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");

View File

@@ -27,4 +27,7 @@ pub enum ChromeVersion {
V104,
V105,
V106,
V108,
V114,
V129
}

View File

@@ -5,11 +5,17 @@ use super::ChromeVersion;
mod v104;
mod v105;
mod v106;
mod v108;
mod v114;
mod v129;
pub(super) fn get_config_from_ver(ver: ChromeVersion) -> BrowserSettings {
match ver {
ChromeVersion::V104 => v104::get_settings(),
ChromeVersion::V105 => v105::get_settings(),
ChromeVersion::V106 => v106::get_settings(),
ChromeVersion::V108 => v108::get_settings(),
ChromeVersion::V114 => v114::get_settings(),
ChromeVersion::V129 => v129::get_settings(),
}
}

View File

@@ -0,0 +1,106 @@
use boring::ssl::{
CertCompressionAlgorithm,
SslConnector,
SslConnectorBuilder,
SslMethod,
SslVersion,
};
use http::{
header::{ ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, UPGRADE_INSECURE_REQUESTS, USER_AGENT },
HeaderMap,
HeaderValue,
};
use std::sync::Arc;
use crate::browser::{ BrowserSettings, Http2Data };
pub(super) fn get_settings() -> BrowserSettings {
BrowserSettings {
tls_builder_func: Arc::new(create_ssl_connector),
http2: Http2Data {
initial_stream_window_size: 6291456,
initial_connection_window_size: 15728640,
max_concurrent_streams: 1000,
max_header_list_size: 262144,
header_table_size: 65536,
enable_push: Some(false),
},
headers: create_headers(),
gzip: true,
brotli: true,
}
}
fn create_ssl_connector() -> SslConnectorBuilder {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_grease_enabled(true);
builder.enable_ocsp_stapling();
let cipher_list = [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
];
builder.set_cipher_list(&cipher_list.join(":")).unwrap();
let sigalgs_list = [
"ecdsa_secp256r1_sha256",
"rsa_pss_rsae_sha256",
"rsa_pkcs1_sha256",
"ecdsa_secp384r1_sha384",
"rsa_pss_rsae_sha384",
"rsa_pkcs1_sha384",
"rsa_pss_rsae_sha512",
"rsa_pkcs1_sha512",
];
builder.set_sigalgs_list(&sigalgs_list.join(":")).unwrap();
builder.enable_signed_cert_timestamps();
builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap();
builder.add_cert_compression_alg(CertCompressionAlgorithm::Brotli).unwrap();
builder.set_min_proto_version(Some(SslVersion::TLS1_2)).unwrap();
builder.set_max_proto_version(Some(SslVersion::TLS1_3)).unwrap();
builder
}
fn create_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(
"sec-ch-ua",
HeaderValue::from_static(
"\"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"108\", \"Google Chrome\";v=\"108\""
)
);
headers.insert(
USER_AGENT,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
.parse()
.unwrap()
);
headers.insert(ACCEPT_ENCODING, "gzip, deflate, br".parse().unwrap());
headers.insert(ACCEPT_LANGUAGE, "en-US,en;q=0.9".parse().unwrap());
headers
}

View File

@@ -0,0 +1,109 @@
use boring::ssl::{
CertCompressionAlgorithm,
SslConnector,
SslConnectorBuilder,
SslMethod,
SslVersion,
SslVerifyMode,
};
use http::{
header::{ ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, UPGRADE_INSECURE_REQUESTS, USER_AGENT },
HeaderMap,
HeaderValue,
};
use std::sync::Arc;
use crate::browser::{ BrowserSettings, Http2Data };
pub(super) fn get_settings() -> BrowserSettings {
BrowserSettings {
tls_builder_func: Arc::new(create_ssl_connector),
http2: Http2Data {
initial_stream_window_size: 6291456,
initial_connection_window_size: 15728640,
max_concurrent_streams: 1000,
max_header_list_size: 262144,
header_table_size: 65536,
enable_push: Some(false),
},
headers: create_headers(),
gzip: true,
brotli: true,
}
}
fn create_ssl_connector() -> SslConnectorBuilder {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_grease_enabled(true);
builder.enable_ocsp_stapling();
builder.set_verify(SslVerifyMode::NONE);
let cipher_list = [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
];
builder.set_cipher_list(&cipher_list.join(":")).unwrap();
let sigalgs_list = [
"ecdsa_secp256r1_sha256",
"rsa_pss_rsae_sha256",
"rsa_pkcs1_sha256",
"ecdsa_secp384r1_sha384",
"rsa_pss_rsae_sha384",
"rsa_pkcs1_sha384",
"rsa_pss_rsae_sha512",
"rsa_pkcs1_sha512",
];
builder.set_sigalgs_list(&sigalgs_list.join(":")).unwrap();
builder.enable_signed_cert_timestamps();
builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap();
builder.add_cert_compression_alg(CertCompressionAlgorithm::Brotli).unwrap();
builder.set_min_proto_version(Some(SslVersion::TLS1_2)).unwrap();
builder.set_max_proto_version(Some(SslVersion::TLS1_3)).unwrap();
builder
}
fn create_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(
"sec-ch-ua",
HeaderValue::from_static(
"\"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"114\", \"Google Chrome\";v=\"114\""
)
);
headers.insert(
USER_AGENT,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
.parse()
.unwrap()
);
headers.insert(ACCEPT_ENCODING, "gzip, deflate, br".parse().unwrap());
headers.insert(ACCEPT_LANGUAGE, "en-US,en;q=0.9".parse().unwrap());
headers
}

View File

@@ -0,0 +1,85 @@
use boring::ssl::{
CertCompressionAlgorithm, SslConnector, SslConnectorBuilder, SslMethod, SslVersion,
SslVerifyMode,
};
use http::{
header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, UPGRADE_INSECURE_REQUESTS, USER_AGENT},
HeaderMap, HeaderValue,
};
use std::sync::Arc;
use crate::browser::{BrowserSettings, Http2Data};
pub(super) fn get_settings() -> BrowserSettings {
BrowserSettings {
tls_builder_func: Arc::new(create_ssl_connector),
http2: Http2Data {
initial_stream_window_size: 6291456,
initial_connection_window_size: 15728640,
max_concurrent_streams: 1000,
max_header_list_size: 262144,
header_table_size: 65536,
enable_push: Some(false),
},
headers: create_headers(),
gzip: true,
brotli: true,
}
}
fn create_ssl_connector() -> SslConnectorBuilder {
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_grease_enabled(true);
builder.enable_ocsp_stapling();
builder.set_verify(SslVerifyMode::NONE);
let cipher_list = [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
];
builder.set_cipher_list(&cipher_list.join(":")).unwrap();
let sigalgs_list = [
"ecdsa_secp256r1_sha256",
"rsa_pss_rsae_sha256",
"rsa_pkcs1_sha256",
"ecdsa_secp384r1_sha384",
"rsa_pss_rsae_sha384",
"rsa_pkcs1_sha384",
"rsa_pss_rsae_sha512",
"rsa_pkcs1_sha512",
];
builder.set_sigalgs_list(&sigalgs_list.join(":")).unwrap();
builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap();
builder.add_cert_compression_alg(CertCompressionAlgorithm::Brotli).unwrap();
builder.enable_signed_cert_timestamps();
builder.set_min_proto_version(Some(SslVersion::TLS1_2)).unwrap();
builder.set_max_proto_version(Some(SslVersion::TLS1_3)).unwrap();
builder
}
fn create_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers
}

View File

@@ -72,7 +72,11 @@ impl Certificate {
/// # Ok(())
/// # }
/// ```
#[cfg(any(not(feature = "__boring"), feature = "native-tls-crate", feature = "__rustls"))]
#[cfg(any(
not(feature = "__boring"),
feature = "native-tls-crate",
feature = "__rustls"
))]
pub fn from_der(der: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "native-tls-crate")]
@@ -98,7 +102,11 @@ impl Certificate {
/// # Ok(())
/// # }
/// ```
#[cfg(any(not(feature = "__boring"), feature = "native-tls-crate", feature = "__rustls"))]
#[cfg(any(
not(feature = "__boring"),
feature = "native-tls-crate",
feature = "__rustls"
))]
pub fn from_pem(pem: &[u8]) -> crate::Result<Certificate> {
Ok(Certificate {
#[cfg(feature = "native-tls-crate")]

View File

@@ -4,15 +4,15 @@ use std::io::Write;
use base64::write::EncoderWriter as Base64Encoder;
use bytes::Bytes;
use http::{request::Parts, Method, Request as HttpRequest};
use http::{ request::Parts, Method, Request as HttpRequest };
use serde::Serialize;
#[cfg(feature = "json")]
use serde_json;
use url::Url;
use web_sys::RequestCredentials;
use super::{Body, Client, Response};
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
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 {
@@ -174,11 +174,13 @@ impl RequestBuilder {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -195,11 +197,15 @@ impl RequestBuilder {
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.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/json")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -210,14 +216,14 @@ impl RequestBuilder {
/// Enable HTTP basic authentication.
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
where U: fmt::Display, P: fmt::Display
{
let mut header_value = b"Basic ".to_vec();
{
let mut encoder =
Base64Encoder::from(&mut header_value, &base64::engine::DEFAULT_ENGINE);
let mut encoder = Base64Encoder::from(
&mut header_value,
&base64::engine::DEFAULT_ENGINE
);
// The unwraps here are fine because Vec::write* is infallible.
write!(encoder, "{}:", username).unwrap();
if let Some(password) = password {
@@ -229,10 +235,7 @@ impl RequestBuilder {
}
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
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)
}
@@ -250,29 +253,34 @@ impl RequestBuilder {
#[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))
*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>,
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);
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())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
};
}
if let Some(err) = error {
@@ -434,27 +442,17 @@ impl fmt::Debug for RequestBuilder {
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
req: &Request
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
f.field("method", &req.method).field("url", &req.url).field("headers", &req.headers)
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
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 Parts { method, uri, headers, .. } = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
@@ -471,13 +469,7 @@ 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 Request { method, url, headers, body, .. } = req;
let mut req = HttpRequest::builder()
.method(method)

View File

@@ -18,7 +18,8 @@ fn test_response_text() {
#[test]
fn test_response_non_utf_8_text() {
let server = server::http(move |_req| async {
http::Response::builder()
http::Response
::builder()
.header("content-type", "text/plain; charset=gbk")
.body(b"\xc4\xe3\xba\xc3"[..].into())
.unwrap()
@@ -91,11 +92,7 @@ fn test_post() {
});
let url = format!("http://{}/2", server.addr());
let res = reqwest::blocking::Client::new()
.post(&url)
.body("Hello")
.send()
.unwrap();
let res = reqwest::blocking::Client::new().post(&url).body("Hello").send().unwrap();
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
@@ -108,7 +105,7 @@ fn test_post_form() {
assert_eq!(req.headers()["content-length"], "24");
assert_eq!(
req.headers()["content-type"],
"application/x-www-form-urlencoded"
"application/x-www-form-urlencoded; charset=UTF-8"
);
let data = hyper::body::to_bytes(req.into_body()).await.unwrap();
@@ -117,14 +114,13 @@ fn test_post_form() {
http::Response::default()
});
let form = &[("hello", "world"), ("sean", "monstar")];
let form = &[
("hello", "world"),
("sean", "monstar"),
];
let url = format!("http://{}/form", server.addr());
let res = reqwest::blocking::Client::new()
.post(&url)
.form(form)
.send()
.expect("request send");
let res = reqwest::blocking::Client::new().post(&url).form(form).send().expect("request send");
assert_eq!(res.url().as_str(), &url);
assert_eq!(res.status(), reqwest::StatusCode::OK);
@@ -135,10 +131,7 @@ fn test_post_form() {
#[test]
fn test_error_for_status_4xx() {
let server = server::http(move |_req| async {
http::Response::builder()
.status(400)
.body(Default::default())
.unwrap()
http::Response::builder().status(400).body(Default::default()).unwrap()
});
let url = format!("http://{}/1", server.addr());
@@ -154,10 +147,7 @@ fn test_error_for_status_4xx() {
#[test]
fn test_error_for_status_5xx() {
let server = server::http(move |_req| async {
http::Response::builder()
.status(500)
.body(Default::default())
.unwrap()
http::Response::builder().status(500).body(Default::default()).unwrap()
});
let url = format!("http://{}/1", server.addr());
@@ -165,10 +155,7 @@ fn test_error_for_status_5xx() {
let err = res.error_for_status().unwrap_err();
assert!(err.is_status());
assert_eq!(
err.status(),
Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR)
);
assert_eq!(err.status(), Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR));
}
#[test]
@@ -180,10 +167,7 @@ fn test_default_headers() {
let mut headers = http::HeaderMap::with_capacity(1);
headers.insert("reqwest-test", "orly".parse().unwrap());
let client = reqwest::blocking::Client::builder()
.default_headers(headers)
.build()
.unwrap();
let client = reqwest::blocking::Client::builder().default_headers(headers).build().unwrap();
let url = format!("http://{}/1", server.addr());
let res = client.get(&url).send().unwrap();
@@ -205,20 +189,14 @@ fn test_override_default_headers() {
let mut headers = http::HeaderMap::with_capacity(1);
headers.insert(
http::header::AUTHORIZATION,
http::header::HeaderValue::from_static("iamatoken"),
http::header::HeaderValue::from_static("iamatoken")
);
let client = reqwest::blocking::Client::builder()
.default_headers(headers)
.build()
.unwrap();
let client = reqwest::blocking::Client::builder().default_headers(headers).build().unwrap();
let url = format!("http://{}/3", server.addr());
let res = client
.get(&url)
.header(
http::header::AUTHORIZATION,
http::header::HeaderValue::from_static("secret"),
)
.header(http::header::AUTHORIZATION, http::header::HeaderValue::from_static("secret"))
.send()
.unwrap();
@@ -253,14 +231,8 @@ fn test_appended_headers_not_overwritten() {
// make sure this also works with default headers
use reqwest::header;
let mut headers = header::HeaderMap::with_capacity(1);
headers.insert(
header::ACCEPT,
header::HeaderValue::from_static("text/html"),
);
let client = reqwest::blocking::Client::builder()
.default_headers(headers)
.build()
.unwrap();
headers.insert(header::ACCEPT, header::HeaderValue::from_static("text/html"));
let client = reqwest::blocking::Client::builder().default_headers(headers).build().unwrap();
let url = format!("http://{}/4", server.addr());
let res = client
@@ -282,9 +254,7 @@ fn test_blocking_inside_a_runtime() {
let url = format!("http://{}/text", server.addr());
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.expect("new rt");
let rt = tokio::runtime::Builder::new_current_thread().build().expect("new rt");
rt.block_on(async move {
let _should_panic = reqwest::blocking::get(&url);
@@ -294,7 +264,8 @@ fn test_blocking_inside_a_runtime() {
#[cfg(feature = "default-tls")]
#[test]
fn test_allowed_methods_blocking() {
let resp = reqwest::blocking::Client::builder()
let resp = reqwest::blocking::Client
::builder()
.https_only(true)
.build()
.expect("client builder")
@@ -303,7 +274,8 @@ fn test_allowed_methods_blocking() {
assert_eq!(resp.is_err(), false);
let resp = reqwest::blocking::Client::builder()
let resp = reqwest::blocking::Client
::builder()
.https_only(true)
.build()
.expect("client builder")
@@ -318,7 +290,8 @@ fn test_allowed_methods_blocking() {
fn test_body_from_bytes() {
let body = "abc";
// No external calls are needed. Only the request building is tested.
let request = reqwest::blocking::Client::builder()
let request = reqwest::blocking::Client
::builder()
.build()
.expect("Could not build the client")
.put("https://google.com")