diff --git a/src/main.rs b/src/main.rs index 8451c62..a5ca58d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,75 +22,20 @@ use std::time::Duration; use std::ffi::{CStr, CString}; use std::fs::{self, File}; use std::mem; -use std::io::{Write, BufReader, BufRead}; +use std::io::{Write, BufReader, BufRead, Read, self}; +use std::net::TcpStream; use simple_input::input; use tempfile::NamedTempFile; extern "C" { - #[no_mangle] - fn socket( - __domain: libc::c_int, - __type: libc::c_int, - __protocol: libc::c_int, - ) -> libc::c_int; - #[no_mangle] - fn connect( - __fd: libc::c_int, - __addr: *const sockaddr, - __len: socklen_t, - ) -> libc::c_int; - #[no_mangle] - fn getaddrinfo( - __name: *const libc::c_char, - __service: *const libc::c_char, - __req: *const addrinfo, - __pai: *mut *mut addrinfo, - ) -> libc::c_int; - #[no_mangle] - fn open(__file: *const libc::c_char, __oflag: libc::c_int, _: ...) -> libc::c_int; - #[no_mangle] - fn close(__fd: libc::c_int) -> libc::c_int; #[no_mangle] fn read(__fd: libc::c_int, __buf: *mut libc::c_void, __nbytes: size_t) -> ssize_t; #[no_mangle] - fn write(__fd: libc::c_int, __buf: *const libc::c_void, __n: size_t) -> ssize_t; - #[no_mangle] - fn __errno_location() -> *mut libc::c_int; - #[no_mangle] - fn snprintf( - _: *mut libc::c_char, - _: libc::c_ulong, - _: *const libc::c_char, - _: ... - ) -> libc::c_int; - #[no_mangle] - fn memset( - _: *mut libc::c_void, - _: libc::c_int, - _: libc::c_ulong, - ) -> *mut libc::c_void; - #[no_mangle] - fn strcpy(_: *mut libc::c_char, _: *const libc::c_char) -> *mut libc::c_char; - #[no_mangle] - fn strcmp(_: *const libc::c_char, _: *const libc::c_char) -> libc::c_int; - #[no_mangle] - fn strncmp( - _: *const libc::c_char, - _: *const libc::c_char, - _: libc::c_ulong, - ) -> libc::c_int; - #[no_mangle] fn strlen(_: *const libc::c_char) -> libc::c_ulong; - #[no_mangle] - fn strerror(_: libc::c_int) -> *mut libc::c_char; } -pub type __off_t = libc::c_long; -pub type __off64_t = libc::c_long; -pub type __pid_t = libc::c_int; pub type __ssize_t = libc::c_long; pub type __socklen_t = libc::c_uint; -pub type pid_t = __pid_t; pub type ssize_t = __ssize_t; pub type size_t = libc::c_ulong; pub type socklen_t = __socklen_t; @@ -131,7 +76,7 @@ pub struct Link { pub which: Option, pub key: usize, pub host: String, - pub port: usize, + pub port: u16, pub selector: String, } #[derive(Clone, Debug)] @@ -152,11 +97,11 @@ pub struct BrowserState { pub links: Option>, pub history: Option>, pub link_key: usize, - pub current_host: [libc::c_char; 512], - pub current_port: usize, + pub current_host: String, + pub current_port: u16, pub current_selector: String, pub parsed_host: String, - pub parsed_port: usize, + pub parsed_port: u16, pub parsed_selector: String, pub bookmarks: Vec, pub config: Config, @@ -169,7 +114,7 @@ impl BrowserState { links: None, history: None, link_key: 0, - current_host: [0; 512], + current_host: String::new(), current_port: 0, current_selector: String::new(), parsed_host: String::new(), @@ -206,7 +151,7 @@ impl BrowserState { x => { eprintln!("invalid key in config: {}", x); exit(1) - }, + } } } @@ -217,9 +162,10 @@ impl BrowserState { return; } }; - file.lines().filter_map(|x| x.ok()).filter(|x| !x.starts_with('#')).for_each(|line| { - self.parse_config_line(&line) - }) + file.lines() + .filter_map(|x| x.ok()) + .filter(|x| !x.starts_with('#')) + .for_each(|line| self.parse_config_line(&line)) } pub unsafe fn init_config(&mut self) { @@ -233,43 +179,29 @@ impl BrowserState { pub unsafe fn download_file( &mut self, mut host: &str, - mut port: usize, + mut port: u16, mut selector: &str, mut file: &mut File, ) -> bool { - let mut srvfd: libc::c_int = 0; - let mut len: usize = 0; - let mut total: usize = 0; - let mut buffer: [u8; 4096] = [0; 4096]; - if self.config.verbose - { - println!("downloading [{}]...", selector); + if self.config.verbose { + eprintln!("downloading [{}]...", selector); } - srvfd = dial(&host, port, selector); - if srvfd == -(1 as libc::c_int) { - println!("error: downloading [{}] failed", selector); + let mut stream = dial(&host, port, selector); + if stream.is_none() { + eprintln!("error: downloading [{}] failed", selector); return false; } - loop { - len = read( - srvfd, - buffer.as_mut_ptr() as *mut libc::c_void, - mem::size_of::<[libc::c_char; 4096]>() as libc::c_ulong, - ) as usize; - if !(len > 0) { - break; - } - file.write(&buffer[..len]).expect("failed to write to file"); - total += len; - if self.config.verbose - { - println!("downloading [{}] ({} kb)...", selector, total / 1024); + // todo show progress + match io::copy(&mut stream.unwrap(), file) { + Ok(b) => eprintln!("downloaded {} bytes", b), + Err(e) => { + eprintln!("error: failed to download file: {}", e); + return false; } - } - close(srvfd); - if self.config.verbose - { - println!("downloading [{}] complete", selector); + }; + + if self.config.verbose { + eprintln!("downloading [{}] complete", selector); } true } @@ -277,7 +209,7 @@ impl BrowserState { pub unsafe fn download_temp( &mut self, mut host: &str, - mut port: usize, + mut port: u16, mut selector: &str, ) -> bool { let mut tmpfile = match NamedTempFile::new() { @@ -300,7 +232,7 @@ impl BrowserState { mut which: char, mut name: String, mut host: String, - mut port: usize, + mut port: u16, mut selector: String, ) { let mut a: char = '\0'; @@ -310,17 +242,15 @@ impl BrowserState { return; } let mut link = Link { - which: Some(which as u8 as char), - key: self.link_key, + which: Some(which as u8 as char), + key: self.link_key, host, port, selector, - next: if self.links.is_none() { None } else { self.links.clone() }, + next: if self.links.is_none() { None } else { self.links.clone() }, }; - println!("links: {:?}", self.links); self.links = Some(Box::new(link)); - println!("links post: {:?}", self.links); let fresh4 = self.link_key; self.link_key += 1; make_key_str(fresh4, &mut a, &mut b, &mut c); @@ -336,16 +266,7 @@ impl BrowserState { let mut link: Link = Link { which: None, key: 0, - host: CString::new( - self.current_host - .iter() - .take_while(|x| **x != 0) - .map(|x| *x as u8) - .collect::>(), - ) - .unwrap() - .into_string() - .unwrap(), + host: self.current_host.clone(), port: self.current_port, selector: self.current_selector.clone(), next: if self.history.is_none() { None } else { self.history.clone() }, @@ -363,26 +284,42 @@ impl BrowserState { match line.chars().next() { Some('i') | Some('3') => println!(" {}", fields[0]), Some('.') => println!("\0"), // some gopher servers use this - Some(w @ '0') - | Some(w @ '1') - | Some(w @ '5') - | Some(w @ '7') - | Some(w @ '8') - | Some(w @ '9') - | Some(w @ 'g') - | Some(w @ 'I') - | Some(w @ 'p') - | Some(w @ 'h') - | Some(w @ 's') => { - self.add_link( - w, - fields[0].to_string(), - fields[2].to_string(), - fields[3].parse().unwrap_or(70), // todo oof - fields[1].to_string(), - ); - }, - Some(x) => println!("miss [{}]: {}", x, fields[0]), + Some(w @ '0') | Some(w @ '1') | Some(w @ '5') | Some(w @ '7') + | Some(w @ '8') | Some(w @ '9') | Some(w @ 'g') | Some(w @ 'I') + | Some(w @ 'p') | Some(w @ 'h') | Some(w @ 's') => { + match fields.len() { + 1 => self.add_link( + w, + fields[0].to_string(), + self.current_host.clone(), + self.current_port, + fields[0].to_string(), + ), + 2 => self.add_link( + w, + fields[0].to_string(), + self.current_host.clone(), + self.current_port, + fields[1].to_string(), + ), + 3 => self.add_link( + w, + fields[0].to_string(), + fields[2].to_string(), + self.current_port, + fields[1].to_string(), + ), + x if x >= 4 => self.add_link( + w, + fields[0].to_string(), + fields[2].to_string(), + fields[3].parse().unwrap_or(70), // todo oof + fields[1].to_string(), + ), + _ => (), + } + } + Some(x) => eprintln!("miss [{}]: {}", x, fields[0]), None => (), } } @@ -390,119 +327,74 @@ impl BrowserState { pub unsafe fn view_directory( &mut self, mut host: &str, - mut port: usize, + mut port: u16, mut selector: &str, mut make_current: libc::c_int, ) { - let mut is_dir: libc::c_int = 0; - let mut srvfd: libc::c_int = 0; - let mut i: libc::c_int = 0; - let mut head_read: libc::c_int = 0; - let mut line: [libc::c_char; 1024] = [0; 1024]; - let mut head: [[libc::c_char; 1024]; 5] = [[0; 1024]; 5]; - srvfd = dial(&host, port, selector); - if srvfd != -(1 as libc::c_int) { - /* only adapt current prompt when successful */ - /* make history entry */ - if make_current != 0 { - self.add_history(); - } - /* don't overwrite the current_* things... */ - if host - != CString::new( - self.current_host - .iter() - .take_while(|x| **x != 0) - .map(|x| *x as u8) - .collect::>(), - ) - .unwrap() - .into_string() - .unwrap() - { - snprintf( - self.current_host.as_mut_ptr(), - mem::size_of::<[libc::c_char; 512]>() as libc::c_ulong, - b"%s\x00" as *const u8 as *const libc::c_char, - host, - ); /* clear links *AFTER* dialing out!! */ - } /* quit if not successful */ - if port != self.current_port { - self.current_port = port; - } - if selector != self.current_selector { - self.current_selector = selector.to_string(); - } - } + let stream = dial(&host, port, selector); + let mut buffer = String::new(); self.clear_links(); - if srvfd == -(1 as libc::c_int) { - return; + let mut stream = match stream { + Some(s) => s, + None => return, + }; + /* only adapt current prompt when successful */ + /* make history entry */ + if make_current != 0 { + self.add_history(); } - head_read = 0 as libc::c_int; - is_dir = 1 as libc::c_int; - while head_read < 5 as libc::c_int - && read_line( - srvfd, - line.as_mut_ptr(), - mem::size_of::<[libc::c_char; 1024]>() as libc::c_ulong, - ) != 0 - { - strcpy(head[head_read as usize].as_mut_ptr(), line.as_mut_ptr()); - if is_valid_directory_entry(head[head_read as usize].as_mut_ptr()) == 0 { - is_dir = 0 as libc::c_int; - break; - } else { - head_read += 1 - } + /* don't overwrite the current_* things... */ + if host != self.current_host { + self.current_host = host.to_string(); + } /* quit if not successful */ + if port != self.current_port { + self.current_port = port; } - if is_dir == 0 { - println!("error: Not a directory"); - close(srvfd); - return; + if selector != self.current_selector { + self.current_selector = selector.to_string(); } - i = 0 as libc::c_int; - while i < head_read { - self.handle_directory_line(&CString::from_raw(head[i as usize].as_mut_ptr()).into_string().unwrap()); - i += 1; + + if let Err(e) = stream.read_to_string(&mut buffer) { + eprintln!("failed to read response body: {}", e); } - while read_line( - srvfd, - line.as_mut_ptr(), - mem::size_of::<[libc::c_char; 1024]>() as libc::c_ulong, - ) != 0 - { - self.handle_directory_line(&CString::from_raw(line.as_mut_ptr()).into_string().unwrap()); + + for line in buffer.lines() { + if !is_valid_directory_entry(line.chars().next().unwrap_or('\0')) { + println!( + "error: not a directory: [{}] {}", + line.chars().next().unwrap_or('\0'), + line.chars().skip(1).collect::() + ); + return; + } + + self.handle_directory_line(line); } - close(srvfd); } pub unsafe fn view_file( &mut self, mut cmd: &str, mut host: &str, - mut port: usize, + mut port: u16, mut selector: &str, ) { if !self.download_temp(host, port, selector) { return; } - if self.config.verbose - { + if self.config.verbose { println!("h({}) p({}) s({})", host, port, selector); } - if self.config.verbose - { + if self.config.verbose { println!("executing: {} {}", cmd, self.tmpfilename); } /* execute */ - match Command::new(cmd) - .arg(&self.tmpfilename) - .spawn() - { - Ok(mut c) => if let Err(e) = c.wait() { - eprintln!("failed to wait for command to exit: {}", e); - }, + match Command::new(cmd).arg(&self.tmpfilename).spawn() { + Ok(mut c) => + if let Err(e) = c.wait() { + eprintln!("failed to wait for command to exit: {}", e); + }, Err(e) => eprintln!("error: failed to run command: {}", e), } @@ -514,7 +406,7 @@ impl BrowserState { pub unsafe fn view_download( &mut self, mut host: &str, - mut port: usize, + mut port: u16, mut selector: &str, ) { let mut filename: String = @@ -534,14 +426,8 @@ impl BrowserState { filename = line; // TODO something stinky going on here let mut file = match File::create(&filename) { Ok(f) => f, - Err(_) => { - println!( - "error: unable to create file [{}]: {}", - filename, - CString::from_raw(strerror(*__errno_location())) - .into_string() - .unwrap() - ); + Err(e) => { + println!("error: unable to create file [{}]: {}", filename, e,); return; } }; @@ -555,34 +441,17 @@ impl BrowserState { pub unsafe fn view_search( &mut self, mut host: &str, - mut port: usize, + mut port: u16, mut selector: &str, ) { - let mut search_selector: [libc::c_char; 1024] = [0; 1024]; - let mut line: [libc::c_char; 1024] = [0; 1024]; - println!("enter search string: "); - if read_line( - 0 as libc::c_int, - line.as_mut_ptr(), - mem::size_of::<[libc::c_char; 1024]>() as libc::c_ulong, - ) == 0 - { - println!("search aborted"); - return; - } - snprintf( - search_selector.as_mut_ptr(), - mem::size_of::<[libc::c_char; 1024]>() as libc::c_ulong, - b"%s\t%s\x00" as *const u8 as *const libc::c_char, - selector, - line.as_mut_ptr(), - ); - self.view_directory( - host, - port, - &CString::from_raw(search_selector.as_mut_ptr()).into_string().unwrap(), - 1 as libc::c_int, - ); + let search_selector: String = match input("enter search string: ").as_str() { + "" => { + println!("search aborted"); + return; + } + s => format!("{}\t{}", selector, s), + }; + self.view_directory(host, port, &search_selector, 1 as libc::c_int); } pub unsafe fn view_history(&mut self, mut key: Option) { @@ -635,13 +504,7 @@ impl BrowserState { i = 0; for bookmark in &self.bookmarks { make_key_str(i, &mut a, &mut b, &mut c); - println!( - "{}{}{} {}", - a, - b, - c, - bookmark - ); + println!("{}{}{} {}", a, b, c, bookmark); } } else if let Some(key) = key { for (i, bookmark) in self.bookmarks.clone().iter().enumerate() { @@ -654,10 +517,7 @@ impl BrowserState { 0 as libc::c_int, ); } else { - println!( - "invalid gopher URI: {}", - bookmark, - ); + println!("invalid gopher URI: {}", bookmark,); } return; } @@ -854,18 +714,7 @@ impl BrowserState { // todo color prompt println!( "{}:{}{} ", - CString::new( - self.current_host - .iter() - .take_while(|x| **x != 0) - .map(|x| *x as u8) - .collect::>() - ) - .unwrap() - .into_string() - .unwrap(), - self.current_port, - &self.current_selector + self.current_host, self.current_port, &self.current_selector ); if read_line( 0 as libc::c_int, @@ -886,18 +735,8 @@ impl BrowserState { } 42 => { self.view_directory( - &CString::new( - self.current_host - .clone() - .iter() - .take_while(|x| **x != 0) - .map(|x| *x as u8) - .collect::>(), - ) - .unwrap() - .into_string() - .unwrap(), - self.current_port.clone(), + &self.current_host.clone(), + self.current_port, &self.current_selector.clone(), 0 as libc::c_int, ); @@ -968,74 +807,25 @@ pub fn banner(to_error: bool) { } } -pub unsafe fn dial(mut host: &str, mut port: usize, mut selector: &str) -> libc::c_int { - let mut hints: addrinfo = addrinfo { - ai_flags: 0, - ai_family: 0, - ai_socktype: 0, - ai_protocol: 0, - ai_addrlen: 0, - ai_addr: 0 as *mut sockaddr, - ai_canonname: 0 as *mut libc::c_char, - ai_next: 0 as *mut addrinfo, - }; - let mut res: *mut addrinfo = 0 as *mut addrinfo; - let mut r: *mut addrinfo = 0 as *mut addrinfo; - let mut srv: libc::c_int = -(1 as libc::c_int); - let mut l: libc::c_int = 0; - let mut request: [libc::c_char; 512] = [0; 512]; - memset( - &mut hints as *mut addrinfo as *mut libc::c_void, - 0 as libc::c_int, - mem::size_of::() as libc::c_ulong, - ); - hints.ai_family = 0 as libc::c_int; - hints.ai_socktype = SOCK_STREAM as libc::c_int; - if getaddrinfo( - CString::new(host).unwrap().into_raw(), - CString::new(port.to_string()).unwrap().into_raw(), - &mut hints, - &mut res, - ) != 0 as libc::c_int - { - eprintln!( - "error: cannot resolve hostname '{}:{}': {}", - host, - port, - CStr::from_ptr(strerror(*__errno_location())).to_str().unwrap() - ); - return -(1 as libc::c_int); - } - r = res; - while !r.is_null() { - srv = socket((*r).ai_family, (*r).ai_socktype, (*r).ai_protocol); - if !(srv == -(1 as libc::c_int)) { - if connect(srv, (*r).ai_addr, (*r).ai_addrlen) == 0 as libc::c_int { - break; - } - close(srv); +pub unsafe fn dial( + mut host: &str, + mut port: u16, + mut selector: &str, +) -> Option { + let mut stream = match TcpStream::connect((host, port)) { + Ok(s) => s, + Err(e) => { + eprintln!("failed to dial server: {}", e); + return None; } - r = (*r).ai_next - } - if r.is_null() { - eprintln!("error: cannot connect to host '{}:{}'", host, port); - return -(1 as libc::c_int); - } - snprintf( - request.as_mut_ptr(), - mem::size_of::<[libc::c_char; 512]>() as libc::c_ulong, - b"%s\r\n\x00" as *const u8 as *const libc::c_char, - selector, - ); - l = strlen(request.as_mut_ptr()) as libc::c_int; - if write(srv, request.as_mut_ptr() as *const libc::c_void, l as size_t) - != l as libc::c_long - { - eprintln!("error: cannot complete request"); - close(srv); - return -(1 as libc::c_int); - } - return srv; + }; + + if let Err(e) = writeln!(stream, "{}", selector) { + eprintln!("failed to send request to server"); + return None; + }; + + Some(stream) } pub unsafe fn read_line( @@ -1117,28 +907,23 @@ pub unsafe fn make_key_str( }; } -pub unsafe fn is_valid_directory_entry(mut line: *const libc::c_char) -> libc::c_int { - match *line.offset(0 as libc::c_int as isize) as libc::c_int { - 105 | 51 | 46 | 48 | 49 | 53 | 55 | 56 | 57 | 103 | 73 | 112 | 104 | 115 => { - /* some gopher servers use this */ - return 1 as libc::c_int; - } - _ => return 0 as libc::c_int, - }; +pub unsafe fn is_valid_directory_entry(kind: char) -> bool { + match kind { + 'i' | '3' | '.' | '0' | '1' | '5' | '7' | '8' | '9' | 'g' | 'I' | 'p' | 'h' + | 's' => true, + _ => false, + } } -pub unsafe fn view_telnet(mut host: &str, mut port: usize) { +pub unsafe fn view_telnet(mut host: &str, mut port: u16) { println!("executing: telnet {} {}", host, port); // TODO check stdio - match Command::new("telnet") - .arg(host) - .arg(port.to_string()) - .spawn() - { - Ok(mut c) => if let Err(e) = c.wait() { - eprintln!("failed to wait for telnet: {}", e); - }, + match Command::new("telnet").arg(host).arg(port.to_string()).spawn() { + Ok(mut c) => + if let Err(e) = c.wait() { + eprintln!("failed to wait for telnet: {}", e); + }, Err(e) => eprintln!("failed to start telnet: {}", e), } println!("(done)");