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