cloudscraper_rs/challenges/core/
reqwest_client.rs1use std::sync::Arc;
8
9use async_trait::async_trait;
10use http::{
11 HeaderMap as HttpHeaderMap, HeaderName as HttpHeaderName, HeaderValue as HttpHeaderValue,
12 Method as HttpMethod,
13};
14use reqwest::{Client, Method, header::HeaderMap, redirect::Policy};
15use url::Url;
16
17use super::{
18 ChallengeExecutionError, ChallengeHttpClient, ChallengeHttpClientError, ChallengeHttpResponse,
19};
20
21pub struct ReqwestChallengeHttpClient {
23 client: Client,
24}
25
26impl ReqwestChallengeHttpClient {
27 pub fn new() -> Result<Self, ChallengeExecutionError> {
30 let client = Client::builder()
31 .redirect(Policy::none())
32 .cookie_store(true)
33 .build()
34 .map_err(|err| {
35 ChallengeExecutionError::Client(ChallengeHttpClientError::Transport(
36 err.to_string(),
37 ))
38 })?;
39
40 Ok(Self { client })
41 }
42
43 pub fn from_client(client: Client) -> Self {
47 Self { client }
48 }
49}
50
51impl Default for ReqwestChallengeHttpClient {
52 fn default() -> Self {
53 Self::new().expect("failed to create reqwest challenge client")
54 }
55}
56
57#[async_trait]
58impl ChallengeHttpClient for ReqwestChallengeHttpClient {
59 async fn send_form(
60 &self,
61 method: &HttpMethod,
62 url: &Url,
63 headers: &HttpHeaderMap,
64 form_fields: &std::collections::HashMap<String, String>,
65 _allow_redirects: bool,
66 ) -> Result<ChallengeHttpResponse, ChallengeHttpClientError> {
67 let req_method = map_method(method)?;
68 let req_headers = convert_headers(headers)?;
69
70 let response = self
71 .client
72 .request(req_method, url.as_str())
73 .headers(req_headers)
74 .form(form_fields)
75 .send()
76 .await
77 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?;
78
79 Ok(to_challenge_response(response).await?)
80 }
81
82 async fn send_with_body(
83 &self,
84 method: &HttpMethod,
85 url: &Url,
86 headers: &HttpHeaderMap,
87 body: Option<&[u8]>,
88 _allow_redirects: bool,
89 ) -> Result<ChallengeHttpResponse, ChallengeHttpClientError> {
90 let req_method = map_method(method)?;
91 let req_headers = convert_headers(headers)?;
92
93 let mut builder = self
94 .client
95 .request(req_method, url.as_str())
96 .headers(req_headers);
97
98 if let Some(data) = body {
99 builder = builder.body(data.to_vec());
100 }
101
102 let response = builder
103 .send()
104 .await
105 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?;
106
107 Ok(to_challenge_response(response).await?)
108 }
109}
110
111fn map_method(method: &HttpMethod) -> Result<Method, ChallengeHttpClientError> {
112 Method::from_bytes(method.as_str().as_bytes())
113 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))
114}
115
116fn convert_headers(headers: &HttpHeaderMap) -> Result<HeaderMap, ChallengeHttpClientError> {
117 let mut map = HeaderMap::new();
118 for (name, value) in headers.iter() {
119 let name = reqwest::header::HeaderName::from_bytes(name.as_str().as_bytes())
120 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?;
121 let value = reqwest::header::HeaderValue::from_bytes(value.as_bytes())
122 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?;
123 map.insert(name, value);
124 }
125 Ok(map)
126}
127
128async fn to_challenge_response(
129 response: reqwest::Response,
130) -> Result<ChallengeHttpResponse, ChallengeHttpClientError> {
131 let status = response.status().as_u16();
132 let headers = convert_back_headers(response.headers())?;
133 let url = response.url().clone();
134 let is_redirect = response.status().is_redirection();
135 let body = response
136 .bytes()
137 .await
138 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?
139 .to_vec();
140
141 Ok(ChallengeHttpResponse {
142 status,
143 headers,
144 body,
145 url,
146 is_redirect,
147 })
148}
149
150fn convert_back_headers(map: &HeaderMap) -> Result<HttpHeaderMap, ChallengeHttpClientError> {
151 let mut headers = HttpHeaderMap::new();
152 for (name, value) in map.iter() {
153 let http_name = HttpHeaderName::from_bytes(name.as_str().as_bytes())
154 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?;
155 let http_value = HttpHeaderValue::from_bytes(value.as_bytes())
156 .map_err(|err| ChallengeHttpClientError::Transport(err.to_string()))?;
157 headers.insert(http_name, http_value);
158 }
159 Ok(headers)
160}
161
162type _AssertSync = Arc<ReqwestChallengeHttpClient>;