1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
#![warn(clippy::pedantic)] use std::io::prelude::*; use std::net::TcpStream; #[derive(Debug)] pub struct Response { headers: Vec<Header>, body: String, } #[derive(Debug)] pub struct Header { name: String, value: String, } /// Sends XML to an rTorrent XML RPC server /// # Arguments /// * `TcpStream` - The TCP stream used to make a request /// * `xml` - The XML to send to rTorrent /// * `buf` - The vector to read the response from rTorrent into /// /// # Example /// ``` /// let mut stream = TcpStream::connect("127.0.0.1:16891").unwrap(); /// /// let mut res: Vec<u8> = Vec::new(); /// let mut xml_res: Vec<char> = Vec::new(); /// /// let xml = String::from("<?xml version=\"1.0\"?><methodCall><methodName>d.multicall2</methodName><params><param><value><string/></value></param><param><value><string>main</string></value></param><param><value><string>d.name=</string></value></param><param><value><string>d.hash=</string></value></param><param><value><string>d.message=</string></value></param><param><value><string>d.state=</string></value></param><param><value><string>d.priority=</string></value></param><param><value><string>d.state_changed=</string></value></param><param><value><string>d.base_path=</string></value></param><param><value><string>d.directory_base=</string></value></param><param><value><string>d.base_filename=</string></value></param><param><value><string>d.directory=</string></value></param><param><value><string>d.directory.set=</string></value></param><param><value><string>d.completed_bytes=</string></value></param><param><value><string>d.size_bytes=</string></value></param><param><value><string>d.down.total=</string></value></param><param><value><string>d.up.total=</string></value></param><param><value><string>d.down.rate=</string></value></param><param><value><string>d.up.rate=</string></value></param><param><value><string>d.custom=seedingtime=</string></value></param><param><value><string>d.custom=addtime=</string></value></param><param><value><string>d.creation_date==</string></value></param><param><value><string>d.is_private==</string></value></param><param><value><string>d.ratio=</string></value></param><param><value><string>d.peers_connected=</string></value></param><param><value><string>d.bytes_done=</string></value></param><param><value><string>d.is_active=</string></value></param><param><value><string>d.complete=</string></value></param><param><value><string>d.hashing=</string></value></param><param><value><string>d.is_hash_checking=</string></value></param><param><value><string>d.is_open=</string></value></param></params></methodCall>"); /// rtorrent_xml_rpc::make_request(&mut stream, &xml, &mut res); /// for ch in res { /// xml_res.push(ch as char); /// } /// println!("{:?}", xml_res); /// ``` /// /// # Errors /// When it fails to write or read from the `TcpStream` pub fn make_request( stream: &mut TcpStream, xml: &str, buf: &mut Vec<u8>, ) -> std::io::Result<usize> { let headers = generate_headers(xml); let header_length = generate_header_length(&headers); let request = generate_request(header_length, &headers, xml).into_bytes(); let request_array: &[u8] = &request; stream.write_all(request_array)?; stream.read_to_end(buf) } /// Parses response from `make_request` to a `Response` struct /// # Arguments /// * `raw_response` - The raw response from `make_request` #[must_use] pub fn parse_response(raw_response: &[u8]) -> Option<Response> { // TODO Stop this from allocating so many strings let mut response = String::with_capacity(raw_response.len()); for ch in raw_response { response.push(*ch as char) } // Split headers and body let split_response: Vec<&str> = response.split("\r\n\r\n").collect(); if split_response.len() != 2 { return None; } let headers = split_response.get(0)?; let body = split_response.get(1)?; let headers = headers.split("\r\n"); let mut headers_vec: Vec<Header> = Vec::new(); for header in headers { let split: Vec<&str> = header.split(":").collect(); headers_vec.push(Header { name: String::from(*split.get(0)?), value: String::from(*split.get(1)?), }); } Some(Response { headers: headers_vec, body: String::from(*body), }) } fn generate_headers(xml: &str) -> Vec<String> { const NULL_CHAR: char = '\0'; let xml_length = xml.len(); let content_length = format!( "CONTENT_LENGTH{null_char}{xml_length}{null_char}", null_char = NULL_CHAR, xml_length = xml_length ); let scgi = format!("SCGI{null_char}1{null_char}", null_char = NULL_CHAR); let array: Vec<String> = vec![content_length, scgi]; array } fn generate_header_length(headers: &[String]) -> usize { let mut length = 0; for header in headers { length += header.len(); } length } fn generate_request(header_length: usize, headers: &[String], xml: &str) -> String { let b = headers.join(","); format!("{}:{}{}", header_length, b, xml) }