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