1use json::{array, object, JsonValue};
2use log::{debug, error};
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::{env, fs, str};
6use std::io::Write;
7use std::path::Path;
8use std::process::Command;
9use std::str::Lines;
10use xmltree::Element;
11
12pub struct Client {
13 pub url: String,
14 debug: bool,
15 pub method: Method,
16 pub header: HashMap<String, String>,
17 body_data: JsonValue,
18 pub content_type: ContentType,
19 proxy: ProxyInfo,
20 openssl_legacy_provider:bool,
21}
22
23impl Default for Client {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl Client {
30 pub fn new() -> Self {
31 Self {
32 url: "".to_string(),
33 debug: false,
34 method: Method::NONE,
35 header: Default::default(),
36 body_data: object! {},
37 content_type: ContentType::Text,
38 proxy: ProxyInfo::None,
39 openssl_legacy_provider: false
40 }
41 }
42 pub fn debug(&mut self) {
43 self.debug = true;
44 }
45 pub fn post(&mut self, url: &str) -> &mut Self {
46 self.url = url.to_string();
47 self.method = Method::POST;
48 self
49 }
50 pub fn get(&mut self, url: &str) -> &mut Self {
51 self.url = url.to_string();
52 self.method = Method::GET;
53 self
54 }
55 pub fn put(&mut self, url: &str) -> &mut Self {
56 self.url = url.to_string();
57 self.method = Method::PUT;
58 self
59 }
60 pub fn patch(&mut self, url: &str) -> &mut Self {
61 self.url = url.to_string();
62 self.method = Method::PATCH;
63 self
64 }
65 pub fn delete(&mut self, url: &str) -> &mut Self {
66 self.url = url.to_string();
67 self.method = Method::DELETE;
68 self
69 }
70 pub fn head(&mut self, url: &str) -> &mut Self {
71 self.url = url.to_string();
72 self.method = Method::HEAD;
73 self
74 }
75 pub fn trace(&mut self, url: &str) -> &mut Self {
76 self.url = url.to_string();
77 self.method = Method::TRACE;
78 self
79 }
80 pub fn header(&mut self, key: &str, value: &str) -> &mut Self {
81 self.header.insert(key.to_string(), value.to_string());
82 self
83 }
84
85 pub fn query(&mut self, params: JsonValue) -> &mut Self {
86 let mut txt = vec![];
87 for (key, value) in params.entries() {
88 txt.push(format!("{key}={value}"));
89 }
90 if self.url.contains('?') {
91 if !txt.is_empty() {
92 self.url = format!("{}&{}", self.url, txt.join("&"));
93 }
94 } else if !txt.is_empty() {
95 self.url = format!("{}?{}", self.url, txt.join("&"));
96 }
97 self
98 }
99
100
101 pub fn raw_json(&mut self, data: JsonValue) -> &mut Self {
103 self.header("Content-Type", ContentType::Json.str().as_str());
104 self.content_type = ContentType::Json;
105 self.body_data = data;
106 self
107 }
108 pub fn raw_xml(&mut self, data: JsonValue) -> &mut Self {
109 self.header("Content-Type", ContentType::Xml.str().as_str());
110 self.content_type = ContentType::Xml;
111 self.body_data = data;
112 self
113 }
114 pub fn raw_stream(&mut self, filename: &str) -> &mut Self {
115 self.content_type = ContentType::Stream;
116 self.body_data = filename.into();
117 self
118 }
119 pub fn form_data(&mut self, data: JsonValue) -> &mut Self {
121 self.header("Content-Type", ContentType::FormData.str().as_str());
122 self.content_type = ContentType::FormData;
123 self.body_data = data;
124 self
125 }
126 pub fn form_urlencoded(&mut self, data: JsonValue) -> &mut Self {
128 self.header("Content-Type", ContentType::FormUrlencoded.str().as_str());
129 self.content_type = ContentType::FormUrlencoded;
130 self.body_data = data;
131 self
132 }
133 pub fn set_openssl_legacy_provider(&mut self) -> &mut Self{
135 self.openssl_legacy_provider=true;
136 self
137 }
138 pub fn add_proxy(&mut self, proxy_addr: String) {
141 self.proxy = ProxyInfo::Addr(proxy_addr);
142 }
143
144 pub fn add_proxy_with_account(&mut self, proxy_addr: String, user: String, password: String) {
147 self.proxy = ProxyInfo::AddrWithAccount(proxy_addr, user, password);
148 }
149
150 pub fn send(&mut self) -> Result<Response, String> {
151 let mut output = Command::new("curl");
152
153 if self.openssl_legacy_provider{
154 output.args(["--ciphers","ALL"]);
155 }
156
157 match &self.proxy {
158 ProxyInfo::None => {
159 output.args(["--noproxy", "*"]);
160 }
161 ProxyInfo::Addr(proxy) => {
162 output.args(["-x", proxy]);
163 }
164 ProxyInfo::AddrWithAccount(proxy, user, pwd) => {
165 output.args(["-x", proxy, "-U", format!("{user}:{pwd}").as_str()]);
166 }
167 }
168
169 match self.method {
170 Method::HEAD => {
171 output.arg("-I");
172 }
173 _ => {
174 output.arg("-i");
175 output.arg("-X");
176 output.arg(self.method.to_str().to_uppercase());
177 }
178 }
179 output.arg(self.url.as_str());
180
181 if !self.header.is_empty() {
182 for (key, value) in self.header.iter() {
183 output.arg("-H");
184 output.arg(format!("{key}: {value}"));
185 }
186 }
187
188 match self.content_type {
189 ContentType::FormData => {
190 for (_, value) in self.body_data.entries() {
191 output.arg("-F");
192 if value[2].is_empty() {
193 output.arg(format!("{}={}", value[0], value[1]));
194 } else {
195 output.arg(format!("{}={};{}", value[0], value[1], value[2]));
196 }
197 }
198 }
199 ContentType::FormUrlencoded => {
200 output.arg("-d");
201 let mut d = vec![];
202 for (key, value) in self.body_data.entries() {
203 d.push(format!("{key}={value}"))
204 }
205 output.arg(d.join("&"));
206 }
207 ContentType::Json | ContentType::Xml => {
208 output.arg("-d");
209 output.arg(format!("{}", self.body_data));
210 }
211 ContentType::Javascript => {}
212 ContentType::Text => {}
213 ContentType::Html => {}
214 ContentType::Other(_) => {}
215 ContentType::Stream => {
216 output.arg("--data-binary");
222 output.arg(format!("@{}", self.body_data));
223 }
224 }
225
226 if self.debug {
227 println!("{}", self.url);
228 println!("{output:?}");
229 }
230 let req = match output.output() {
231 Ok(e) => e,
232 Err(e) => {
233 return Err(e.to_string());
234 }
235 };
236
237 if req.status.success() {
238 let body = req.stdout.clone();
239 if self.debug {
240 let text = String::from_utf8_lossy(&req.stdout);
241 println!("响应内容:\n{text}");
242 }
243 Ok(Response::new(self.debug, body)?)
244 } else {
245 let err = String::from_utf8_lossy(&req.stderr).to_string();
246 let txt = match err.find("ms:") {
247 None => err,
248 Some(e) => {
249 err[e + 3..].trim().to_string()
250 }
251 };
252 Err(txt)
253 }
254 }
255}
256
257enum ProxyInfo {
258 None,
259 Addr(String),
260 AddrWithAccount(String, String, String),
261}
262
263
264#[derive(Debug)]
265pub struct Response {
266 debug: bool,
267 status: i32,
268 method: Method,
269 headers: JsonValue,
270 cookies: JsonValue,
271 body: Body,
273}
274impl Response {
275 const CRLF2: [u8; 4] = [13, 10, 13, 10];
276
277 fn new(debug: bool, body: Vec<u8>) -> Result<Self, String> {
278 let mut that = Self {
279 debug,
280 status: 0,
281 method: Method::NONE,
282 headers: object! {},
283 cookies: object! {},
284 body: Default::default(),
285 };
286
287 let (header, body) = match body.windows(Self::CRLF2.len()).position(|window| window == Self::CRLF2) {
288 None => (vec![], vec![]),
289 Some(index) => {
290 let header = body[..index].to_vec();
291 let body = Vec::from(&body[index + Self::CRLF2.len()..]);
292 (header, body)
293 }
294 };
295
296 let text = String::from_utf8_lossy(header.as_slice());
297 let lines = text.lines();
298
299 that.get_request_line(lines.clone().next().expect("get next line in response err"))?;
300 that.get_header(lines.clone())?;
302 that.body.stream = body.clone();
303 that.body.set_content(body);
304 Ok(that)
305 }
306
307 fn get_request_line(&mut self, line: &str) -> Result<(), String> {
309 let lines = line.split_whitespace().collect::<Vec<&str>>();
310 if lines.len() < 2 {
311 return Err("请求行错误".to_string());
312 }
313 self.method = Method::from(lines[0]);
314 self.status = lines[1].parse::<i32>().unwrap();
315 Ok(())
316 }
317
318 fn get_header(&mut self, data: Lines) -> Result<(), String> {
320 let mut header = object! {};
321 let mut cookie = object! {};
322 let mut body = Body::default();
323
324 for text in data {
325 let (key, value) = match text.trim().find(":") {
326 None => continue,
327 Some(e) => {
328 let key = text[..e].trim().to_lowercase().clone();
329 let value = text[e + 1..].trim().to_string();
330 (key, value)
331 }
332 };
333 match key.as_str() {
334 "content-type" => match value {
335 _ if value.contains("multipart/form-data") => {
336 let boundarys = value.split("boundary=").collect::<Vec<&str>>();
337 body.boundary = boundarys[1..].join("");
338 body.content_type = ContentType::from("multipart/form-data");
339 let _ = header.insert(key.as_str(), "multipart/form-data");
340 }
341 _ => {
342 let value = match value.find(";") {
343 None => value,
344 Some(e) => value[..e].trim().to_string(),
345 };
346 body.content_type = ContentType::from(value.as_str());
347 let _ = header.insert(key.as_str(), body.content_type.str());
348 }
349 },
350 "content-length" => {
351 body.content_length = value.to_string().parse::<usize>().unwrap_or(0);
352 }
353 "cookie" => {
354 let _ = value.split(";").collect::<Vec<&str>>().iter().map(|&x| {
355 match x.find("=") {
356 None => {}
357 Some(index) => {
358 let key = x[..index].trim().to_string();
359 let val = x[index + 1..].trim().to_string();
360 let _ = cookie.insert(key.as_str(), val);
361 }
362 };
363 ""
364 }).collect::<Vec<&str>>();
365 }
366 _ => {
367 if self.debug {
368 debug!("header: {key} = {value}");
369 }
370 let _ = header.insert(key.as_str(), value);
371 }
372 };
373 }
374 self.headers = header.clone();
375 self.cookies = cookie.clone();
376 self.body = body.clone();
377 Ok(())
378 }
379 pub fn status(&self) -> i32 {
380 self.status
381 }
382 pub fn headers(&self) -> JsonValue {
383 self.headers.clone()
384 }
385 pub fn cookies(&self) -> JsonValue {
386 self.cookies.clone()
387 }
388 pub fn content_type(&self) -> String {
389 self.body.content_type.str().clone()
390 }
391 pub fn json(&self) -> Result<JsonValue, String> {
392 if self.body.content.is_empty() {
393 Ok(object! {})
394 } else {
395 match json::parse(self.body.content.to_string().as_str()) {
396 Ok(e) => Ok(e),
397 Err(e) => Err(e.to_string())
398 }
399 }
400 }
401 pub fn xml(&self) -> Result<JsonValue, String> {
402 if self.body.content.is_empty() {
403 Ok(object! {})
404 } else {
405 let json = match Element::parse(self.body.content.to_string().as_bytes()) {
406 Ok(e) => xml_element_to_json(&e),
407 Err(e) => {
408 if self.debug {
409 error!("{:?} {}", e.to_string(),self.body.content.clone());
410 }
411 self.body.content.clone()
412 }
413 };
414 Ok(json)
415 }
416 }
417 pub fn body(&self) -> JsonValue {
418 self.body.content.clone()
419 }
420 pub fn stream(&self) -> Vec<u8> {
421 self.body.stream.clone()
422 }
423}
424fn xml_element_to_json(elem: &Element) -> JsonValue {
425 let mut obj = object! {};
426
427 for child in &elem.children {
428 if let xmltree::XMLNode::Element(e) = child {
429 obj[e.name.clone()] = xml_element_to_json(e);
430 }
431 }
432
433 match elem.get_text() {
434 None => obj,
435 Some(text) => JsonValue::from(text.to_string()),
436 }
437}
438
439#[derive(Clone, Debug)]
440pub enum Method {
441 GET,
442 POST,
443 OPTIONS,
444 PATCH,
445 HEAD,
446 DELETE,
447 TRACE,
448 PUT,
449 NONE,
450}
451
452impl Method {
453 pub fn to_str(&self) -> String {
454 match self {
455 Method::GET => "GET",
456 Method::POST => "POST",
457 Method::OPTIONS => "OPTIONS",
458 Method::PATCH => "PATCH",
459 Method::HEAD => "HEAD",
460 Method::DELETE => "DELETE",
461 Method::TRACE => "TRACE",
462 Method::PUT => "PUT",
463 Method::NONE => "NONE",
464 }.to_string()
465 }
466 pub fn from(name: &str) -> Self {
467 match name.to_lowercase().as_str() {
468 "post" => Self::POST,
469 "get" => Self::GET,
470 "head" => Self::HEAD,
471 "put" => Self::PUT,
472 "delete" => Self::DELETE,
473 "options" => Self::OPTIONS,
474 "patch" => Self::PATCH,
475 "trace" => Self::TRACE,
476 _ => Self::NONE,
477 }
478 }
479}
480#[derive(Clone, Debug)]
481pub enum Version {
482 Http09,
483 Http10,
484 Http11,
485 H2,
486 H3,
487 None,
488}
489
490impl Version {
491 pub fn str(&mut self) -> String {
492 match self {
493 Version::Http09 => "HTTP/0.9",
494 Version::Http10 => "HTTP/1.0",
495 Version::Http11 => "HTTP/1.1",
496 Version::H2 => "HTTP/2.0",
497 Version::H3 => "HTTP/3.0",
498 Version::None => "",
499 }.to_string()
500 }
501 pub fn from(name: &str) -> Version {
502 match name {
503 "HTTP/0.9" => Self::Http09,
504 "HTTP/1.0" => Self::Http10,
505 "HTTP/1.1" => Self::Http11,
506 "HTTP/2.0" => Self::H2,
507 "HTTP/3.0" => Self::H3,
508 _ => Self::None,
509 }
510 }
511 pub fn set_version(name: &str) -> Version {
512 match name {
513 "0.9" => Self::Http09,
514 "1.0" => Self::Http10,
515 "1.1" => Self::Http11,
516 "2.0" => Self::H2,
517 "3.0" => Self::H3,
518 _ => Self::None,
519 }
520 }
521}
522
523
524#[derive(Clone, Debug)]
525pub enum FormData {
526 Text(String, JsonValue, String),
530 File(String, String, String),
534 None,
535}
536
537
538#[derive(Debug, Clone)]
539pub struct Body {
540 pub content_type: ContentType,
541 pub boundary: String,
542 pub content_length: usize,
543 pub content: JsonValue,
544 pub stream: Vec<u8>,
545}
546impl Body {
547 pub fn decode(input: &str) -> Result<String, String> {
549 let mut decoded = String::new();
550 let bytes = input.as_bytes();
551 let mut i = 0;
552
553 while i < bytes.len() {
554 if bytes[i] == b'%' {
555 if i + 2 >= bytes.len() {
556 return Err("Incomplete percent-encoding".into());
557 }
558 let hex = &input[i + 1..i + 3];
559 match u8::from_str_radix(hex, 16) {
560 Ok(byte) => decoded.push(byte as char),
561 Err(_) => return Err(format!("Invalid percent-encoding: %{hex}")),
562 }
563 i += 3;
564 } else if bytes[i] == b'+' {
565 decoded.push(' ');
566 i += 1;
567 } else {
568 decoded.push(bytes[i] as char);
569 i += 1;
570 }
571 }
572
573 Ok(decoded)
574 }
575 pub fn set_content(&mut self, data: Vec<u8>) {
576 match self.content_type.clone() {
577 ContentType::FormData | ContentType::Stream => {
578 let mut fields = object! {};
579 let boundary_marker = format!("--{}", self.boundary);
580 let text = unsafe { String::from_utf8_unchecked(data) };
581 let parts = text.split(&boundary_marker).collect::<Vec<&str>>();
582 for part in parts {
583 let part = part.trim();
584 if part.is_empty() || part == "--" {
585 continue; }
587
588 let mut headers_and_body = part.splitn(2, "\r\n\r\n");
589 if let (Some(headers), Some(body)) = (headers_and_body.next(), headers_and_body.next())
590 {
591 let headers = headers.split("\r\n");
593
594 let mut field_name = "";
595 let mut filename = "";
596 let mut content_type = ContentType::Text;
597
598 for header in headers {
599 if header.to_lowercase().starts_with("content-disposition:") {
600 match header.find("filename=\"") {
601 None => {}
602 Some(filename_start) => {
603 let filename_len = filename_start + 10;
604 let filename_end = header[filename_len..].find('"').unwrap() + filename_len;
605 filename = &header[filename_len..filename_end];
606 }
607 }
608
609 match header.find("name=\"") {
610 None => {}
611 Some(name_start) => {
612 let name_start = name_start + 6;
613 let name_end = header[name_start..].find('"').unwrap() + name_start;
614 field_name = &header[name_start..name_end];
615 }
616 }
617 }
618 if header.to_lowercase().starts_with("content-type:") {
619 content_type = ContentType::from(
620 header.to_lowercase().trim_start_matches("content-type:").trim(),
621 );
622 }
623 }
624
625 if filename.is_empty() {
626 fields[field_name.to_string()] = JsonValue::from(body);
627 } else {
628 let mut temp_dir = env::temp_dir();
630 temp_dir.push(filename);
632 let mut temp_file = match fs::File::create(&temp_dir) {
634 Ok(e) => e,
635 Err(_) => continue,
636 };
637 if temp_file.write(body.as_bytes()).is_ok() {
638 if fields[field_name.to_string()].is_empty() {
639 fields[field_name.to_string()] = array![]
640 }
641
642 let extension = Path::new(filename).extension() .and_then(|ext| ext.to_str()); let suffix = extension.unwrap_or("txt");
646
647 fields[field_name.to_string()].push(object! {
648 name:filename,
650 suffix:suffix,
651 size:body.len(),
652 "type":content_type.str(),
653 file:temp_dir.to_str()
654 }).unwrap();
655 };
656 }
657 }
658 }
659 self.content = fields;
660 }
661 ContentType::FormUrlencoded => {
662 let text = unsafe { String::from_utf8_unchecked(data) };
663 let params = text.split("&").collect::<Vec<&str>>();
664 let mut list = object! {};
665 for param in params.iter() {
666 let t = param.split("=").collect::<Vec<&str>>().iter().map(|&x| Body::decode(x).unwrap_or(x.to_string())).collect::<Vec<String>>();
667 list[t[0].to_string()] = t[1].clone().into();
668 }
669 self.content = list;
670 }
671 ContentType::Json => {
672 let text = unsafe { String::from_utf8_unchecked(data) };
673 self.content = json::parse(text.as_str()).unwrap_or(object! {});
674 }
675 ContentType::Xml => {
676 let text = unsafe { String::from_utf8_unchecked(data) };
677 self.content = text.into();
678 }
679 ContentType::Html | ContentType::Text | ContentType::Javascript => {
680 let text = unsafe { String::from_utf8_unchecked(data) };
681 self.content = text.into();
682 }
683 ContentType::Other(_) => self.content = data.into()
684 }
685 }
686}
687
688impl Default for Body {
689 fn default() -> Self {
690 Self {
691 content_type: ContentType::Other("text/plain".to_string()),
692 boundary: "".to_string(),
693 content_length: 0,
694 content: object! {},
695 stream: vec![],
696 }
697 }
698}
699
700#[derive(Debug, Clone)]
702pub enum ContentType {
703 FormData,
704 FormUrlencoded,
705 Json,
706 Xml,
707 Javascript,
708 Text,
709 Html,
710 Stream,
711 Other(String),
712}
713impl ContentType {
714 pub fn from(name: &str) -> Self {
715 match name {
716 "multipart/form-data" => Self::FormData,
717 "application/x-www-form-urlencoded" => Self::FormUrlencoded,
718 "application/json" => Self::Json,
719 "application/xml" | "text/xml" => Self::Xml,
720 "application/javascript" => Self::Javascript,
721 "text/html" => Self::Html,
722 "text/plain" => Self::Text,
723 "application/octet-stream" => Self::Stream,
724 _ => Self::Other(name.to_string()),
725 }
726 }
727 pub fn str(&self) -> String {
728 match self {
729 Self::FormData => "multipart/form-data",
730 Self::FormUrlencoded => "application/x-www-form-urlencoded",
731 Self::Json => "application/json",
732 Self::Xml => "application/xml",
733 Self::Javascript => "application/javascript",
734 Self::Text => "text/plain",
735 Self::Html => "text/html",
736 Self::Other(name) => name,
737 Self::Stream => "application/octet-stream",
738 }.to_string()
739 }
740}