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