1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4use crate::request::{RequestPayload};
5use crate::types::{AeadId, ClientIdentifier, DelegatedCredential, H2Setting, KdfId, KeyShareCurve, SignatureAlgorithm, SupportedVersion};
6
7
8#[derive(Debug, Serialize)]
9#[serde(rename_all = "camelCase")]
10pub struct TlsClient {
11 session_id: String,
14 insecure_skip_verify: bool,
15 timeout_seconds: u32,
16
17 follow_redirects: bool,
19 proxy_url: Option<String>,
20
21 tls_client_identifier: Option<ClientIdentifier>,
23 with_random_tls_extension_order: Option<bool>,
24 custom_tls_client: Option<CustomClient>,
25
26 request_url: Option<String>,
28 request_method: Option<String>,
29}
30
31
32
33impl TlsClient {
34 fn build_for_request(&self, method: String, url: String) -> RequestPayload {
35 let tc = TlsClient {
36 session_id: self.session_id.clone(),
37 insecure_skip_verify: self.insecure_skip_verify,
38 timeout_seconds: self.timeout_seconds,
39 follow_redirects: self.follow_redirects,
40 proxy_url: self.proxy_url.clone(),
41 tls_client_identifier: self.tls_client_identifier.clone(),
42 with_random_tls_extension_order: self.with_random_tls_extension_order,
43 custom_tls_client: self.custom_tls_client.clone(),
44 request_url: Some(url),
45 request_method: Some(method),
46 };
47 serde_json::from_value(serde_json::to_value(tc).unwrap()).unwrap()
48 }
49
50 pub fn new(client_identifier: ClientIdentifier, random_tls_order: bool) -> TlsClient {
51 TlsClient {
52 session_id: Uuid::new_v4().to_string(),
53 insecure_skip_verify: false,
54 timeout_seconds: 30,
55 follow_redirects: true,
56 tls_client_identifier: Some(client_identifier),
57 with_random_tls_extension_order: Some(random_tls_order),
58 custom_tls_client: None,
59 proxy_url: None,
60 request_url: None,
61 request_method: None,
62 }
63 }
64
65 pub fn new_custom(custom_profile: CustomClient) -> TlsClient {
66 TlsClient {
67 session_id: Uuid::new_v4().to_string(),
68 insecure_skip_verify: false,
69 timeout_seconds: 30,
70 follow_redirects: true,
71 tls_client_identifier: None,
72 with_random_tls_extension_order: Some(false),
73 custom_tls_client: Some(custom_profile),
74 proxy_url: None,
75 request_url: None,
76 request_method: None,
77 }
78 }
79
80 pub fn set_insecure_skip_verify(mut self, skip: bool) -> Self {
81 self.insecure_skip_verify = skip;
82 self
83 }
84
85 pub fn set_timeout_seconds(mut self, seconds: u32) -> Self {
86 self.timeout_seconds = seconds;
87 self
88 }
89
90 pub fn set_follow_redirects(mut self, follow: bool) -> Self {
91 self.follow_redirects = follow;
92 self
93 }
94
95 pub fn set_proxy_url(mut self, url: String) -> Self {
96 self.proxy_url = Some(url);
97 self
98 }
99
100 pub fn remove_proxy_url(mut self) -> Self {
101 self.proxy_url = None;
102 self
103 }
104
105 pub fn get(&self, url: &str) -> RequestPayload {
106 self.build_for_request("GET".to_string(), url.to_string())
107 }
108
109 pub fn post(&self, url: &str) -> RequestPayload {
110 self.build_for_request("POST".to_string(), url.to_string())
111 }
112
113}
114
115impl Default for TlsClient {
116 fn default() -> Self {
117 Self::new(ClientIdentifier::Chrome120, false)
118 }
119}
120
121#[derive(Default, Debug, Serialize, Deserialize, Clone)]
124#[serde(rename_all = "camelCase")]
125pub struct CustomClient {
126 cert_compression_algo: Option<String>,
127 connection_flow: Option<u32>,
128 h2_settings: Option<HashMap<H2Setting, u32>>,
129 h2_settings_order: Option<Vec<H2Setting>>,
130 header_priority: Option<PriorityParam>,
131 ja3_string: Option<String>,
132 key_share_curves: Option<Vec<KeyShareCurve>>,
133 priority_frames: Option<Vec<PriorityFrames>>,
134 alpn_protocols: Option<Vec<String>>,
135 alps_protocols: Option<Vec<String>>,
136 #[serde(rename = "ECHCandidateCipherSuites")]
137 ech_candidate_cipher_suites: Option<Vec<CandidateCipherSuite>>,
138 #[serde(rename = "ECHCandidatePayloads")]
139 ech_candidate_payloads: Option<u16>,
140 pseudo_header_order: Option<Vec<String>>,
141 supported_delegated_credentials_algorithms: Option<Vec<DelegatedCredential>>,
142 supported_signature_algorithms: Option<Vec<SignatureAlgorithm>>,
143 supported_versions: Option<Vec<SupportedVersion>>,
144}
145
146impl CustomClient {
147 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn with_cert_compression_algo(mut self, algo: String) -> Self {
152 self.cert_compression_algo = Some(algo);
153 self
154 }
155
156 pub fn with_connection_flow(mut self, flow: u32) -> Self {
157 self.connection_flow = Some(flow);
158 self
159 }
160
161 pub fn with_h2_settings(mut self, settings: HashMap<H2Setting, u32>) -> Self {
162 self.h2_settings = Some(settings);
163 self
164 }
165
166 pub fn with_h2_settings_order(mut self, order: Vec<H2Setting>) -> Self {
167 self.h2_settings_order = Some(order);
168 self
169 }
170
171 pub fn with_header_priority(mut self, priority: PriorityParam) -> Self {
172 self.header_priority = Some(priority);
173 self
174 }
175
176 pub fn with_ja3_string(mut self, ja3: String) -> Self {
177 self.ja3_string = Some(ja3);
178 self
179 }
180
181 pub fn with_key_share_curves(mut self, curves: Vec<KeyShareCurve>) -> Self {
182 self.key_share_curves = Some(curves);
183 self
184 }
185
186 pub fn with_priority_frames(mut self, frames: Vec<PriorityFrames>) -> Self {
187 self.priority_frames = Some(frames);
188 self
189 }
190
191 pub fn with_alpn_protocols(mut self, protocols: Vec<String>) -> Self {
192 self.alpn_protocols = Some(protocols);
193 self
194 }
195
196 pub fn with_alps_protocols(mut self, protocols: Vec<String>) -> Self {
197 self.alps_protocols = Some(protocols);
198 self
199 }
200
201 pub fn with_ech_candidate_cipher_suites(mut self, suites: Vec<CandidateCipherSuite>) -> Self {
202 self.ech_candidate_cipher_suites = Some(suites);
203 self
204 }
205
206 pub fn with_ech_candidate_payloads(mut self, payloads: u16) -> Self {
207 self.ech_candidate_payloads = Some(payloads);
208 self
209 }
210
211 pub fn with_pseudo_header_order(mut self, order: Vec<String>) -> Self {
212 self.pseudo_header_order = Some(order);
213 self
214 }
215
216 pub fn with_supported_delegated_credentials_algorithms(mut self, algorithms: Vec<DelegatedCredential>) -> Self {
217 self.supported_delegated_credentials_algorithms = Some(algorithms);
218 self
219 }
220
221 pub fn with_supported_signature_algorithms(mut self, algorithms: Vec<SignatureAlgorithm>) -> Self {
222 self.supported_signature_algorithms = Some(algorithms);
223 self
224 }
225
226 pub fn with_supported_versions(mut self, versions: Vec<SupportedVersion>) -> Self {
227 self.supported_versions = Some(versions);
228 self
229 }
230}
231
232#[derive(Debug, Serialize, Deserialize, Clone)]
233#[serde(rename_all = "camelCase")]
234pub struct CandidateCipherSuite {
235 kdf_id: KdfId,
236 aead_id: AeadId,
237}
238
239#[derive(Debug, Serialize, Deserialize, Clone)]
240#[serde(rename_all = "camelCase")]
241pub struct PriorityFrames {
242 #[serde(rename = "streamID")]
243 stream_id: u32,
244 frame_payload: PriorityParam,
245}
246
247#[derive(Debug, Serialize, Deserialize, Clone)]
248#[serde(rename_all = "camelCase")]
249pub struct PriorityParam {
250 stream_dep: u32,
251 exclusive: bool,
252 weight: u32,
253}
254
255
256mod tests {
257 #[cfg(test)]
258 use std::io::Read;
259 #[cfg(test)]
260 use reqwest::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CONTENT_TYPE, USER_AGENT, HeaderMap, HeaderValue};
261 #[cfg(test)]
262 use super::*;
263
264 #[test]
265 fn test_get() {
266 let mut hm = HeaderMap::new();
267 hm.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
268 hm.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"));
269 hm.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
270 hm.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"));
271
272 let client = TlsClient::new(ClientIdentifier::Chrome105, false);
273
274 let req = &client.get("https://microsoft.com")
275 .headers(hm)
276 .send()
277 .unwrap();
278
279 assert_eq!(req.get_status(), 200)
280 }
281
282 #[test]
283 fn test_post() {
284 let mut hm = HeaderMap::new();
285 hm.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
286 hm.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"));
287 hm.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
288 hm.insert(CONTENT_TYPE, HeaderValue::from_static("application/x-www-form-urlencoded"));
289 hm.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"));
290
291 let client = TlsClient::new(ClientIdentifier::Chrome105, false);
292
293 let req = client.post("https://www.toptal.com/developers/postbin/1711461823032-1978890220634")
294 .headers(hm)
295 .body("foo=bar&baz=foo")
296 .send()
297 .unwrap();
298
299 assert_eq!(req.get_status(), 200)
300 }
301
302 #[test]
303 fn test_get_image() {
304 let mut hm = HeaderMap::new();
305 hm.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
306 hm.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"));
307 hm.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
308 hm.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"));
309
310 let client = TlsClient::new(ClientIdentifier::Chrome105, false);
311
312 let req = client.get("https://avatars.githubusercontent.com/u/17678241?v=4")
313 .headers(hm)
314 .byte_response() .send()
316 .unwrap();
317
318
319
320 assert_eq!(req.get_status(), 200)
321 }
322
323 #[test]
324 fn test_post_image() {
325 let mut hm = HeaderMap::new();
326 hm.insert(ACCEPT, HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
327 hm.insert(USER_AGENT, HeaderValue::from_static("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"));
328 hm.insert(ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
329 hm.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"));
330
331 let client = TlsClient::new(ClientIdentifier::Chrome105, false);
332
333 let mut img_file = std::fs::File::open("src/cb_example.png").unwrap();
335 let mut buffer = Vec::new();
336 img_file.read_to_end(&mut buffer).unwrap();
337 let b64_file = base64::encode(&buffer);
338 println!("{}", b64_file);
339
340 let req = client.post("https://www.toptal.com/developers/postbin/1711492583368-7330834681633")
341 .headers(hm)
342 .body(b64_file)
343 .byte_request()
344 .send()
345 .unwrap();
346
347 assert_eq!(serde_json::to_string(&req).unwrap(), "xx".to_string())
348
349 }
350
351 #[test]
352 fn make_custom_client() {
353
354 }
355
356}
357