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