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