diff --git a/src/dns.rs b/src/dns.rs index f3c2edc..5b1750f 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -7,7 +7,7 @@ const RECURSION_DESIRED: u16 = 1 << 8; const RECORD_TYPE_SRV: u16 = 33; const RECORD_CLASS_IN: u16 = 1; -pub fn create_dns_query(qname: &String, qtype: u16, qclass: u16) -> Result> { +pub fn create_dns_query(qname: &str, qtype: u16, qclass: u16) -> Result> { let qname = match qname.is_ascii() { true => qname, false => return Err(Error::new(ErrorKind::InvalidInput, "domain is not valid ASCII")), @@ -110,7 +110,7 @@ pub fn parse_srv_response(mut ptr: usize, recv: &[u8]) -> Option pub fn resolve_srv_port(domain: &str) -> Option { let request = create_dns_query( - &("_minecraft._tcp.".to_string() + domain), + &format!("_minecraft._tcp.{}", domain), RECORD_TYPE_SRV, RECORD_CLASS_IN).unwrap(); diff --git a/src/http.rs b/src/http.rs index 3ad56ba..1379016 100644 --- a/src/http.rs +++ b/src/http.rs @@ -41,7 +41,7 @@ impl StatusCode { } } -type HttpHandlerFunc = fn(&Request, Response) -> Result; +type HttpHandlerFunc = fn(&Request, Response, bool) -> Result; pub struct Request<'a> { stream: &'a TcpStream, @@ -213,20 +213,21 @@ impl<'a> Response<'a> { } } -pub struct HttpServer { - address: String, +pub struct HttpServer<'a> { + address: &'a str, port: u16, trusted_proxies: Arc>, max_connections: usize, + verbose: bool, } -impl HttpServer { - pub fn new(address: String, max_connections: usize, trusted_proxies: Vec) -> HttpServer { - let mut _address = address.clone(); +impl HttpServer <'_> { + pub fn new(address: &'_ str, max_connections: usize, trusted_proxies: Vec, verbose: bool) -> HttpServer<'_> { + let mut _address = address; let mut _port: u16 = 8080; match address.split_once(":") { Some((ip, port)) => { - _address = ip.to_string(); + _address = ip; _port = port.parse::().expect(format!("Invalid port {}", port).as_str()); } None => {} @@ -236,12 +237,14 @@ impl HttpServer { port: _port, trusted_proxies: Arc::new(trusted_proxies), max_connections, + verbose, } } pub fn start(&self, handler: HttpHandlerFunc) -> Result<()> { let pool = ThreadPool::new(self.max_connections); let listener = TcpListener::bind(format!("{}:{}", self.address, self.port)).expect("Failed to bind to port"); + let verbose = self.verbose; println!("Now listening on {}:{}", self.address, self.port); @@ -250,7 +253,7 @@ impl HttpServer { Ok(stream) => { let trusted_proxies = self.trusted_proxies.clone(); pool.execute(move || { - HttpServer::handle_client(&stream, handler, trusted_proxies); + HttpServer::handle_client(&stream, handler, trusted_proxies, verbose); }); } Err(e) => { @@ -262,7 +265,7 @@ impl HttpServer { Ok(()) } - fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc, trusted_proxies: Arc>) { + fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc, trusted_proxies: Arc>, verbose: bool) { let buf_reader = BufReader::new(stream); let http_request: Vec = buf_reader .lines() @@ -280,7 +283,7 @@ impl HttpServer { let response = Response::new(stream); let start_date = Local::now(); - match handler(&request, response) { + match handler(&request, response, verbose) { Ok(status) => { let end_date = Local::now(); diff --git a/src/main.rs b/src/main.rs index 615da37..5bd96c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ fn parse_address(address: &str) -> Result { } } -fn handle_html_request(request: &Request, response: &mut Response) -> Result { +fn handle_html_request(request: &Request, response: &mut Response, verbose: bool) -> Result { let content = include_str!("views/index.html"); response.set_header("Content-Type", "text/html".to_string()); response.status(StatusCode::OK); @@ -72,7 +72,7 @@ fn handle_html_request(request: &Request, response: &mut Response) -> Result { - match MinecraftStatus::fetch(&address) { + match MinecraftStatus::fetch(&address, verbose) { Err(err) => { println!( "Failed to connect to {} ({}): {}", @@ -136,7 +136,7 @@ fn handle_html_request(request: &Request, response: &mut Response) -> Result Result { +fn handle_json_request(request: &Request, response: &mut Response, verbose: bool) -> Result { response.set_header("Content-Type", "text/plain".to_string()); response.status(StatusCode::OK); @@ -149,7 +149,7 @@ fn handle_json_request(request: &Request, response: &mut Response) -> Result { - match MinecraftStatus::fetch(&address) { + match MinecraftStatus::fetch(&address, verbose) { Err(err) => { println!( "Failed to connect to {} ({}): {}", @@ -194,11 +194,9 @@ fn handle_json_request(request: &Request, response: &mut Response) -> Result Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 2 { - println!( - r#"Crafty McStatusFace, v{} - made with <3 by ari melody +fn print_help() { + println!( + r#"Crafty McStatusFace, v{} - made with <3 by ari melody Host a web API: $ mcstatusface serve [address[:port]] @@ -206,27 +204,53 @@ $ mcstatusface serve [address[:port]] Query a server: $ mcstatusface "#, env!("CARGO_PKG_VERSION")); +} + +fn main() -> Result<()> { + let args: Vec = env::args().collect(); + if args.len() < 2 { + print_help(); std::process::exit(0); } - if args[1] != "serve" { - let address = parse_address(&args[1]).expect("Failed to parse address"); - let status = MinecraftStatus::fetch(&address).unwrap(); + let verbose = args.contains(&String::from("-v")); - println!("Version: {} ({})", status.version.name, status.version.protocol); - println!("Players: {}/{}", status.players.online, status.players.max); - println!( - "Enforces Secure Chat: {}", - if status.enforces_secure_chat() { "true" } else { "false" }, - ); - println!("MOTD:"); - println!("{}", status.parse_description()); + if !args[1..].contains(&String::from("serve")) { + let address_arg = match args[1..].iter().find(|arg| !arg.starts_with("-")) { + Some(arg) => arg, + None => { + print_help(); + std::process::exit(0); + } + }; + let address = parse_address(&address_arg).expect("Failed to parse address"); - return Ok(()); + match MinecraftStatus::fetch(&address, verbose) { + Ok(status) => { + println!("Version: {} ({})", status.version.name, status.version.protocol); + println!("Players: {}/{}", status.players.online, status.players.max); + println!( + "Enforces Secure Chat: {}", + if status.enforces_secure_chat() { "true" } else { "false" }, + ); + println!("MOTD:"); + println!("{}", status.parse_description()); + + return Ok(()); + }, + Err (e) => { + return Err(Error::new( + ErrorKind::Other, + format!("Failed to fetch status: {}", e))); + } + }; } - let mut address = "0.0.0.0:8080".to_string(); - if args.len() > 2 { address = args[2].to_string() } + let mut address = "0.0.0.0:8080"; + let address_args = args[1..].iter() + .filter(|arg| *arg != "-v" && *arg != "serve") + .collect::>(); + if !address_args.is_empty() { address = address_args[0] } let trusted_proxies: Vec = match env::var("MCSTATUSFACE_TRUSTED_PROXIES") { Err(_) => { vec![] } @@ -242,7 +266,7 @@ env!("CARGO_PKG_VERSION")); } }; - HttpServer::new(address, 16, trusted_proxies).start(|request, mut response| { + HttpServer::new(address, 16, trusted_proxies, verbose).start(|request, mut response, verbose| { response.status(StatusCode::OK); response.set_header("Content-Type", "text/plain".to_string()); response.set_header("x-powered-by", "GIRL FUEL".to_string()); @@ -264,11 +288,11 @@ env!("CARGO_PKG_VERSION")); if request.headers().get("accept").is_some_and( |accept| accept.contains("text/html") ) { - return handle_html_request(request, &mut response); + return handle_html_request(request, &mut response, verbose); } // JSON response - return handle_json_request(request, &mut response); + return handle_json_request(request, &mut response, verbose); } response.status(StatusCode::NotFound); diff --git a/src/status.rs b/src/status.rs index 28bbba5..8e1185d 100644 --- a/src/status.rs +++ b/src/status.rs @@ -4,6 +4,8 @@ use std::time::Duration; use crate::leb128::{read_leb128, write_leb128}; +const TIMEOUT_SECS: u64 = 5; + #[derive(serde::Serialize, serde::Deserialize)] pub struct MinecraftVersion { pub name: String, @@ -56,20 +58,21 @@ pub struct MinecraftStatus { } impl MinecraftStatus { - pub fn fetch(address: &SocketAddr) -> Result { - //println!("Connecting to {address}..."); + pub fn fetch(address: &SocketAddr, verbose: bool) -> Result { + if verbose { println!("Connecting to {address}..."); } - let stream = TcpStream::connect_timeout(address, Duration::new(5, 0)); - if stream.is_err() { return Err(stream.unwrap_err()); } + let mut stream = match TcpStream::connect_timeout( + address, Duration::from_secs(TIMEOUT_SECS)) { + Ok(stream) => stream, + Err(e) => { + return Err(e); + } + }; + if verbose { println!("Connected!"); } - //println!("Connected!"); - - let mut stream = stream.unwrap(); + if verbose { println!("Sending payload..."); } let mut send_buffer: Vec = Vec::new(); - - //println!("Sending payload..."); - send_buffer.push(0x00); // packet ID write_leb128(&mut send_buffer, 769); // "i am 1.21.4" write_leb128(&mut send_buffer, // upcoming address length @@ -83,7 +86,7 @@ impl MinecraftStatus { send_packet(&mut stream, &[0x00]).unwrap(); - //println!("Payload sent, receiving...\n"); + if verbose { println!("Awaiting response..."); } let mut data: Vec = Vec::new(); let mut len: usize = 0;