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