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