Tor support

pull/14/head
dvkt 4 years ago
parent f489f42bf7
commit 17cefcc67f

51
Cargo.lock generated

@ -10,6 +10,11 @@ name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "c2-chacha"
version = "0.2.3"
@ -142,6 +147,7 @@ dependencies = [
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"tor-stream 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -240,6 +246,17 @@ dependencies = [
"core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "socks"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempfile"
version = "3.1.0"
@ -264,6 +281,15 @@ dependencies = [
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tor-stream"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"socks 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vcpkg"
version = "0.2.8"
@ -274,6 +300,11 @@ name = "wasi"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.8"
@ -283,6 +314,11 @@ dependencies = [
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
@ -293,9 +329,19 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
@ -324,10 +370,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021"
"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df"
"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895"
"checksum socks 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e6a64cfa9346d26e836a49fcc1ddfcb4d3df666b6787b6864db61d4918e1cbc2"
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
"checksum termion 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a8fb22f7cde82c8220e5aeacb3258ed7ce996142c77cba193f203515e26c330"
"checksum tor-stream 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5865109fc90e0bc0f8c299f3794ca0fd5771df988aa6b962d4c9129c39674746"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"

@ -34,3 +34,4 @@ dev-version-ext = "dev"
termion = "1.5.3"
native-tls = "0.2"
libc = "0.2.66"
tor-stream = "0.2.0"

@ -31,6 +31,9 @@ the gophersphere.
Options:
-t, --tls Try to open all pages w/ TLS
-T, --tor Try to open all pages w/ Tor
Set the TOR_PROXY env variable to use
an address other than the default :9050
-r, --raw Print raw Gopher response only
-p, --print Print rendered Gopher response only
-l, --local Connect to 127.0.0.1:7070

@ -18,6 +18,9 @@ start gopher://phetch/1/home
# Always use TLS mode. (--tls)
tls no
# Connect using local TOR proxy. (--tor)
tor no
# Always start in wide mode. (--wide)
wide no
";
@ -26,6 +29,7 @@ wide no
pub struct Config {
pub start: String,
pub tls: bool,
pub tor: bool,
pub wide: bool,
}
@ -60,6 +64,7 @@ pub fn parse(text: &str) -> Result<Config> {
let mut cfg = Config {
start: String::new(),
tls: false,
tor: false,
wide: false,
};
@ -89,6 +94,7 @@ pub fn parse(text: &str) -> Result<Config> {
match key {
"start" => cfg.start.push_str(val),
"tls" => cfg.tls = to_bool(val)?,
"tor" => cfg.tor = to_bool(val)?,
"wide" => cfg.wide = to_bool(val)?,
_ => return Err(error!("Unknown key on line {}: {}", linenum, key)),
}
@ -116,6 +122,7 @@ mod tests {
fn test_parse_default() {
let config = parse(DEFAULT_CONFIG).expect("Couldn't parse config");
assert_eq!(config.tls, false);
assert_eq!(config.tor, false);
assert_eq!(config.wide, false);
assert_eq!(config.start, "gopher://phetch/1/home");
}
@ -148,8 +155,9 @@ mod tests {
#[test]
fn test_no_or_false() {
let cfg = parse("tls false\nwide no").unwrap();
let cfg = parse("tls false\nwide no\ntor n").unwrap();
assert_eq!(cfg.tls, false);
assert_eq!(cfg.tor, false);
assert_eq!(cfg.wide, false);
}
#[test]

@ -1,4 +1,5 @@
use std::{
env,
io::{Read, Result, Write},
net::TcpStream,
net::ToSocketAddrs,
@ -6,6 +7,7 @@ use std::{
time::Duration,
};
use termion::input::TermRead;
use tor_stream::TorStream;
#[cfg(not(feature = "disable-tls"))]
use native_tls::TlsConnector;
@ -51,15 +53,21 @@ pub const TCP_TIMEOUT_DURATION: Duration = Duration::from_secs(TCP_TIMEOUT_IN_SE
/// Fetches a gopher URL and returns a tuple of:
/// (did tls work?, raw Gopher response)
pub fn fetch_url(url: &str, try_tls: bool) -> Result<(bool, String)> {
pub fn fetch_url(url: &str, tls: bool, tor: bool) -> Result<(bool, String)> {
let (_, host, port, sel) = parse_url(url);
fetch(host, port, sel, try_tls)
fetch(host, port, sel, tls, tor)
}
/// Fetches a gopher URL by its component parts and returns a tuple of:
/// (did tls work?, raw Gopher response)
pub fn fetch(host: &str, port: &str, selector: &str, try_tls: bool) -> Result<(bool, String)> {
let mut stream = request(host, port, selector, try_tls)?;
pub fn fetch(
host: &str,
port: &str,
selector: &str,
tls: bool,
tor: bool,
) -> Result<(bool, String)> {
let mut stream = request(host, port, selector, tls, tor)?;
let mut body = Vec::new();
stream.read_to_end(&mut body)?;
let out = clean_response(&String::from_utf8_lossy(&body));
@ -81,7 +89,7 @@ fn clean_response(res: &str) -> String {
/// Downloads a binary to disk. Allows canceling with Ctrl-c.
/// Returns a tuple of:
/// (path it was saved to, the size in bytes)
pub fn download_url(url: &str, try_tls: bool) -> Result<(String, usize)> {
pub fn download_url(url: &str, tls: bool, tor: bool) -> Result<(String, usize)> {
let (_, host, port, sel) = parse_url(url);
let filename = sel
.split_terminator('/')
@ -93,7 +101,7 @@ pub fn download_url(url: &str, try_tls: bool) -> Result<(String, usize)> {
let stdin = termion::async_stdin();
let mut keys = stdin.keys();
let mut stream = request(host, port, sel, try_tls)?;
let mut stream = request(host, port, sel, tls, tor)?;
let mut file = std::fs::OpenOptions::new()
.write(true)
.create(true)
@ -119,36 +127,54 @@ pub fn download_url(url: &str, try_tls: bool) -> Result<(String, usize)> {
/// Make a Gopher request and return a TcpStream ready to be read()'d.
/// Will attempt a TLS connection first, then retry a regular
/// connection if it fails.
pub fn request(host: &str, port: &str, selector: &str, try_tls: bool) -> Result<Stream> {
pub fn request(host: &str, port: &str, selector: &str, tls: bool, tor: bool) -> Result<Stream> {
let selector = selector.replace('?', "\t"); // search queries
let sock = format!("{}:{}", host, port)
.to_socket_addrs()
.and_then(|mut socks| socks.next().ok_or_else(|| error!("Can't create socket")))?;
// attempt tls connection
#[cfg(not(feature = "disable-tls"))]
{
if try_tls {
if let Ok(connector) = TlsConnector::new() {
let stream = TcpStream::connect_timeout(&sock, TCP_TIMEOUT_DURATION)?;
stream.set_read_timeout(Some(TCP_TIMEOUT_DURATION))?;
if let Ok(mut stream) = connector.connect(host, stream) {
stream.write(format!("{}\r\n", selector).as_ref())?;
return Ok(Stream {
io: Box::new(stream),
tls: true,
});
if tls {
#[cfg(not(feature = "disable-tls"))]
{
{
if let Ok(connector) = TlsConnector::new() {
let stream = TcpStream::connect_timeout(&sock, TCP_TIMEOUT_DURATION)?;
stream.set_read_timeout(Some(TCP_TIMEOUT_DURATION))?;
if let Ok(mut stream) = connector.connect(host, stream) {
stream.write(format!("{}\r\n", selector).as_ref())?;
return Ok(Stream {
io: Box::new(stream),
tls: true,
});
}
}
}
}
}
let mut stream = TcpStream::connect_timeout(&sock, TCP_TIMEOUT_DURATION)?;
stream.write(format!("{}\r\n", selector).as_ref())?;
Ok(Stream {
io: Box::new(stream),
tls: false,
})
// tls didn't work, try regular
if tor {
let proxy = env::var("TOR_PROXY")
.unwrap_or("127.0.0.1:9050".into())
.to_socket_addrs()?
.nth(0)
.unwrap();
let mut stream = TorStream::connect_with_address(proxy, sock)?;
stream.write(format!("{}\r\n", selector).as_ref())?;
Ok(Stream {
io: Box::new(stream),
tls: false,
})
} else {
let mut stream = TcpStream::connect_timeout(&sock, TCP_TIMEOUT_DURATION)?;
stream.set_read_timeout(Some(TCP_TIMEOUT_DURATION))?;
stream.write(format!("{}\r\n", selector).as_ref())?;
Ok(Stream {
io: Box::new(stream),
tls: false,
})
}
}
/// Parses gopher URL into parts.

@ -58,6 +58,7 @@ fn run() -> i32 {
return 1;
}
}
"-T" | "--tor" | "-tor" => cfg.tor = true,
arg => {
if arg.starts_with('-') {
print_version();
@ -75,13 +76,19 @@ fn run() -> i32 {
}
}
if cfg.tor && cfg.tls {
eprintln!("Can't set both --tor and --tls.");
return 1;
}
if mode == Mode::Raw {
print_raw(&cfg.start, cfg.tls);
print_raw(&cfg.start, cfg.tls, cfg.tor);
return 0;
}
let mut ui = UI::new(cfg.tls);
if let Err(e) = ui.open(&cfg.start, &cfg.start) {
let start = cfg.start.clone();
let mut ui = UI::new(cfg);
if let Err(e) = ui.open(&start, &start) {
eprintln!("{}", e);
return 1;
}
@ -127,6 +134,9 @@ Usage:
Options:
-t, --tls Try to open all pages w/ TLS
-T, --tor Try to open all pages w/ Tor
Set the TOR_PROXY env variable to use
an address other than the default :9050
-r, --raw Print raw Gopher response only
-p, --print Print rendered Gopher response only
-l, --local Connect to 127.0.0.1:7070
@ -138,8 +148,8 @@ Once you've launched phetch, use `ctrl-h` to view the on-line help."
);
}
fn print_raw(url: &str, try_tls: bool) {
match gopher::fetch_url(url, try_tls) {
fn print_raw(url: &str, tls: bool, tor: bool) {
match gopher::fetch_url(url, tls, tor) {
Ok((_, response)) => println!("{}", response),
Err(e) => {
eprintln!("{}", e);

@ -14,6 +14,7 @@ pub struct Menu {
pub scroll: usize, // scrolling offset
pub searching: bool, // search mode?
pub tls: bool, // retrieved via tls?
pub tor: bool, // retrieved via tor?
pub size: (usize, usize), // cols, rows
pub wide: bool, // in wide mode?
}
@ -44,6 +45,10 @@ impl View for Menu {
self.tls
}
fn is_tor(&self) -> bool {
self.tor
}
fn raw(&self) -> String {
self.raw.to_string()
}
@ -66,10 +71,12 @@ impl View for Menu {
}
impl Menu {
pub fn from(url: String, response: String, tls: bool) -> Menu {
let mut menu = Self::parse(url, response);
menu.tls = tls;
menu
pub fn from(url: String, response: String, tls: bool, tor: bool) -> Menu {
Menu {
tls,
tor,
..Self::parse(url, response)
}
}
fn cols(&self) -> usize {
@ -797,6 +804,7 @@ impl Menu {
searching: false,
size: (0, 0),
tls: false,
tor: false,
wide: false,
}
}

@ -10,6 +10,7 @@ pub struct Text {
longest: usize, // longest line
size: (usize, usize), // cols, rows
pub tls: bool, // retrieved via tls?
pub tor: bool, // retrieved via tor?
pub wide: bool, // in wide mode? turns off margins
}
@ -24,6 +25,10 @@ impl View for Text {
self.tls
}
fn is_tor(&self) -> bool {
self.tor
}
fn url(&self) -> String {
self.url.to_string()
}
@ -135,7 +140,7 @@ impl View for Text {
}
impl Text {
pub fn from(url: String, response: String, tls: bool) -> Text {
pub fn from(url: String, response: String, tls: bool, tor: bool) -> Text {
let mut lines = 0;
let mut longest = 0;
for line in response.split_terminator('\n') {
@ -153,6 +158,7 @@ impl Text {
longest,
size: (0, 0),
tls,
tor,
wide: false,
}
}

@ -5,6 +5,7 @@ pub use self::view::View;
use crate::{
bookmarks, color,
config::Config,
gopher::{self, Type},
help, history,
menu::Menu,
@ -39,12 +40,12 @@ pub struct UI {
running: bool, // main ui loop running?
pub size: (usize, usize), // cols, rows
status: String, // status message, if any
tls: bool, // tls mode?
config: Config, // user config
out: RefCell<AlternateScreen<RawTerminal<Stdout>>>,
}
impl UI {
pub fn new(tls: bool) -> UI {
pub fn new(config: Config) -> UI {
let mut size = (0, 0);
if let Ok((cols, rows)) = terminal_size() {
size = (cols as usize, rows as usize);
@ -66,8 +67,8 @@ impl UI {
dirty: true,
running: true,
size,
config,
status: String::new(),
tls,
out: RefCell::new(out),
}
}
@ -163,9 +164,9 @@ impl UI {
fn download(&mut self, url: &str) -> Result<()> {
let url = url.to_string();
let tls = self.tls;
let (tls, tor) = (self.config.tls, self.config.tor);
self.spinner(&format!("Downloading {}", url), move || {
gopher::download_url(&url, tls)
gopher::download_url(&url, tls, tor)
})
.and_then(|res| res)
.and_then(|(path, bytes)| {
@ -189,17 +190,17 @@ impl UI {
thread::spawn(move || history::save(&hname, &hurl));
// request thread
let thread_url = url.to_string();
let try_tls = self.tls;
let (tls, tor) = (self.config.tls, self.config.tor);
// don't spin on first ever request
let (tls, res) = if self.views.is_empty() {
gopher::fetch_url(&thread_url, try_tls)?
gopher::fetch_url(&thread_url, tls, tor)?
} else {
self.spinner("", move || gopher::fetch_url(&thread_url, try_tls))??
self.spinner("", move || gopher::fetch_url(&thread_url, tls, tor))??
};
let (typ, _, _, _) = gopher::parse_url(&url);
match typ {
Type::Menu | Type::Search => Ok(Box::new(Menu::from(url.to_string(), res, tls))),
Type::Text | Type::HTML => Ok(Box::new(Text::from(url.to_string(), res, tls))),
Type::Menu | Type::Search => Ok(Box::new(Menu::from(url.to_string(), res, tls, tor))),
Type::Text | Type::HTML => Ok(Box::new(Text::from(url.to_string(), res, tls, tor))),
_ => Err(error!("Unsupported Gopher Response: {:?}", typ)),
}
}
@ -210,7 +211,7 @@ impl UI {
&url.trim_start_matches("gopher://phetch/")
.trim_start_matches("1/"),
) {
Ok(Box::new(Menu::from(url.to_string(), source, false)))
Ok(Box::new(Menu::from(url.to_string(), source, false, false)))
} else {
Err(error!("phetch URL not found: {}", url))
}
@ -296,12 +297,15 @@ impl UI {
let page = self.views.get(self.focused)?;
if page.is_tls() {
return Some(format!(
"{}{}{}{}{}",
"{}{}",
termion::cursor::Goto(self.cols() - 3, self.rows()),
color::Black,
color::GreenBG,
"TLS",
"\x1b[0m"
color!("TLS", Black, GreenBG),
));
} else if page.is_tor() {
return Some(format!(
"{}{}",
termion::cursor::Goto(self.cols() - 3, self.rows()),
color!("TOR", Bold, White, MagentaBG),
));
}
None
@ -521,7 +525,7 @@ impl UI {
if let Some(page) = self.views.get(self.focused) {
let url = page.url();
let raw = page.raw();
let mut text = Text::from(url, raw, page.is_tls());
let mut text = Text::from(url, raw, page.is_tls(), page.is_tor());
text.wide = true;
self.add_page(Box::new(text));
}
@ -561,12 +565,6 @@ impl UI {
}
}
impl Default for UI {
fn default() -> Self {
UI::new(false)
}
}
impl Drop for UI {
fn drop(&mut self) {
let mut out = self.out.borrow_mut();

@ -5,6 +5,7 @@ pub trait View: fmt::Display {
fn respond(&mut self, key: ui::Key) -> ui::Action;
fn render(&self) -> String;
fn is_tls(&self) -> bool;
fn is_tor(&self) -> bool;
fn url(&self) -> String;
fn raw(&self) -> String;
fn term_size(&mut self, cols: usize, rows: usize);

Loading…
Cancel
Save