1use json::{array, object, JsonValue};
2use log::debug;
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 elem = Element::parse(self.body.content.to_string().as_bytes()).unwrap();
355 let json = xml_element_to_json(&elem);
356 Ok(json)
357 }
358 }
359 pub fn body(&self) -> JsonValue {
360 self.body.content.clone()
361 }
362}
363fn xml_element_to_json(elem: &Element) -> JsonValue {
364 let mut obj = object! {};
365
366 for child in &elem.children {
367 if let xmltree::XMLNode::Element(e) = child {
368 obj[e.name.clone()] = xml_element_to_json(e);
369 }
370 }
371
372 match elem.get_text() {
373 None => obj,
374 Some(text) => JsonValue::from(text.to_string()),
375 }
376}
377
378#[derive(Clone, Debug)]
379pub enum Method {
380 GET,
381 POST,
382 OPTIONS,
383 PATCH,
384 HEAD,
385 DELETE,
386 TRACE,
387 PUT,
388 NONE,
389}
390
391impl Method {
392 pub fn to_str(&self) -> String {
393 match self {
394 Method::GET => "GET",
395 Method::POST => "POST",
396 Method::OPTIONS => "OPTIONS",
397 Method::PATCH => "PATCH",
398 Method::HEAD => "HEAD",
399 Method::DELETE => "DELETE",
400 Method::TRACE => "TRACE",
401 Method::PUT => "PUT",
402 Method::NONE => "NONE",
403 }.to_string()
404 }
405 pub fn from(name: &str) -> Self {
406 match name.to_lowercase().as_str() {
407 "post" => Self::POST,
408 "get" => Self::GET,
409 "head" => Self::HEAD,
410 "put" => Self::PUT,
411 "delete" => Self::DELETE,
412 "options" => Self::OPTIONS,
413 "patch" => Self::PATCH,
414 "trace" => Self::TRACE,
415 _ => Self::NONE,
416 }
417 }
418}
419#[derive(Clone, Debug)]
420pub enum Version {
421 Http09,
422 Http10,
423 Http11,
424 H2,
425 H3,
426 None,
427}
428
429impl Version {
430 pub fn str(&mut self) -> String {
431 match self {
432 Version::Http09 => "HTTP/0.9",
433 Version::Http10 => "HTTP/1.0",
434 Version::Http11 => "HTTP/1.1",
435 Version::H2 => "HTTP/2.0",
436 Version::H3 => "HTTP/3.0",
437 Version::None => "",
438 }.to_string()
439 }
440 pub fn from(name: &str) -> Version {
441 match name {
442 "HTTP/0.9" => Self::Http09,
443 "HTTP/1.0" => Self::Http10,
444 "HTTP/1.1" => Self::Http11,
445 "HTTP/2.0" => Self::H2,
446 "HTTP/3.0" => Self::H3,
447 _ => Self::None,
448 }
449 }
450 pub fn set_version(name: &str) -> Version {
451 match name {
452 "0.9" => Self::Http09,
453 "1.0" => Self::Http10,
454 "1.1" => Self::Http11,
455 "2.0" => Self::H2,
456 "3.0" => Self::H3,
457 _ => Self::None,
458 }
459 }
460}
461
462
463#[derive(Clone, Debug)]
464pub enum FormData {
465 Text(String, JsonValue, String),
469 File(String, String, String),
473 None,
474}
475
476
477#[derive(Debug, Clone)]
478pub struct Body {
479 pub content_type: ContentType,
480 pub boundary: String,
481 pub content_length: usize,
482 pub content: JsonValue,
483}
484impl Body {
485 pub fn decode(input: &str) -> Result<String, String> {
487 let mut decoded = String::new();
488 let bytes = input.as_bytes();
489 let mut i = 0;
490
491 while i < bytes.len() {
492 if bytes[i] == b'%' {
493 if i + 2 >= bytes.len() {
494 return Err("Incomplete percent-encoding".into());
495 }
496 let hex = &input[i + 1..i + 3];
497 match u8::from_str_radix(hex, 16) {
498 Ok(byte) => decoded.push(byte as char),
499 Err(_) => return Err(format!("Invalid percent-encoding: %{}", hex)),
500 }
501 i += 3;
502 } else if bytes[i] == b'+' {
503 decoded.push(' ');
504 i += 1;
505 } else {
506 decoded.push(bytes[i] as char);
507 i += 1;
508 }
509 }
510
511 Ok(decoded)
512 }
513 pub fn set_content(&mut self, data: Vec<u8>) {
514 match self.content_type.clone() {
515 ContentType::FormData => {
516 let mut fields = object! {};
517 let boundary_marker = format!("--{}", self.boundary);
518 let text = unsafe { String::from_utf8_unchecked(data) };
519 let parts = text.split(&boundary_marker).collect::<Vec<&str>>();
520 for part in parts {
521 let part = part.trim();
522 if part.is_empty() || part == "--" {
523 continue; }
525
526 let mut headers_and_body = part.splitn(2, "\r\n\r\n");
527 if let (Some(headers), Some(body)) = (headers_and_body.next(), headers_and_body.next())
528 {
529 let headers = headers.split("\r\n");
531
532 let mut field_name = "";
533 let mut filename = "";
534 let mut content_type = ContentType::Text;
535
536 for header in headers {
537 if header.to_lowercase().starts_with("content-disposition:") {
538 match header.find("filename=\"") {
539 None => {}
540 Some(filename_start) => {
541 let filename_len = filename_start + 10;
542 let filename_end = header[filename_len..].find('"').unwrap() + filename_len;
543 filename = &header[filename_len..filename_end];
544 }
545 }
546
547 match header.find("name=\"") {
548 None => {}
549 Some(name_start) => {
550 let name_start = name_start + 6;
551 let name_end = header[name_start..].find('"').unwrap() + name_start;
552 field_name = &header[name_start..name_end];
553 }
554 }
555 }
556 if header.to_lowercase().starts_with("content-type:") {
557 content_type = ContentType::from(
558 header.to_lowercase().trim_start_matches("content-type:").trim(),
559 );
560 }
561 }
562
563 if filename.is_empty() {
564 fields[field_name.to_string()] = JsonValue::from(body);
565 } else {
566 let mut temp_dir = env::temp_dir();
568 temp_dir.push(filename);
570 let mut temp_file = match fs::File::create(&temp_dir) {
572 Ok(e) => e,
573 Err(_) => continue,
574 };
575 if temp_file.write(body.as_bytes()).is_ok() {
576 if fields[field_name.to_string()].is_empty() {
577 fields[field_name.to_string()] = array![]
578 }
579
580 let extension = Path::new(filename).extension() .and_then(|ext| ext.to_str()); let suffix = extension.unwrap_or("txt");
584
585 fields[field_name.to_string()].push(object! {
586 name:filename,
588 suffix:suffix,
589 size:body.len(),
590 "type":content_type.str(),
591 file:temp_dir.to_str()
592 }).unwrap();
593 };
594 }
595 }
596 }
597 self.content = fields;
598 }
599 ContentType::FormUrlencoded => {
600 let text = unsafe { String::from_utf8_unchecked(data) };
601 let params = text.split("&").collect::<Vec<&str>>();
602 let mut list = object! {};
603 for param in params.iter() {
604 let t = param.split("=").collect::<Vec<&str>>().iter().map(|&x| Body::decode(x).unwrap_or(x.to_string())).collect::<Vec<String>>();
605 list[t[0].to_string()] = t[1].clone().into();
606 }
607 self.content = list;
608 }
609 ContentType::Json => {
610 let text = unsafe { String::from_utf8_unchecked(data) };
611 self.content = json::parse(text.as_str()).unwrap_or(object! {});
612 }
613 ContentType::Xml => {
614 let text = unsafe { String::from_utf8_unchecked(data) };
615 self.content = text.into();
616 }
617 ContentType::Html | ContentType::Text | ContentType::Javascript => {
618 let text = unsafe { String::from_utf8_unchecked(data) };
619 self.content = text.into();
620 }
621 ContentType::Other(name) => match name.as_str() {
622 "application/pdf" => {
623 let text = unsafe { String::from_utf8_unchecked(data) };
624 self.content = text.into();
625 }
626 _ => {
627 let text = unsafe { String::from_utf8_unchecked(data) };
628 self.content = text.into();
629 }
630 },
631 ContentType::Stream => {}
632 }
633 }
634}
635
636impl Default for Body {
637 fn default() -> Self {
638 Self {
639 content_type: ContentType::Other("text/plain".to_string()),
640 boundary: "".to_string(),
641 content_length: 0,
642 content: object! {},
643 }
644 }
645}
646
647#[derive(Debug, Clone)]
649pub enum ContentType {
650 FormData,
651 FormUrlencoded,
652 Json,
653 Xml,
654 Javascript,
655 Text,
656 Html,
657 Stream,
658 Other(String),
659}
660impl ContentType {
661 pub fn from(name: &str) -> Self {
662 match name {
663 "multipart/form-data" => Self::FormData,
664 "application/x-www-form-urlencoded" => Self::FormUrlencoded,
665 "application/json" => Self::Json,
666 "application/xml" | "text/xml" => Self::Xml,
667 "application/javascript" => Self::Javascript,
668 "text/html" => Self::Html,
669 "text/plain" => Self::Text,
670 "application/octet-stream" => Self::Stream,
671 _ => Self::Other(name.to_string()),
672 }
673 }
674 pub fn str(&self) -> String {
675 match self {
676 Self::FormData => "multipart/form-data",
677 Self::FormUrlencoded => "application/x-www-form-urlencoded",
678 Self::Json => "application/json",
679 Self::Xml => "application/xml",
680 Self::Javascript => "application/javascript",
681 Self::Text => "text/plain",
682 Self::Html => "text/html",
683 Self::Other(name) => name,
684 Self::Stream => "application/octet-stream",
685 }.to_string()
686 }
687}