diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9b1b6..2cda643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.3.0 + +### maj + +- `maj::get` unfortunately had to become async in order for fetching `gus.guru` to work + +### majc + +- keep track of loaded pages as history +- `l` to view/navigate to links +- use [smol](https://github.com/stjepang/smol) for doing the async operations synchronously + ## 0.2.0 ### maj diff --git a/Cargo.toml b/Cargo.toml index 1444137..98025f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "maj" -version = "0.2.0" +version = "0.3.0" authors = ["Christine Dodrill "] edition = "2018" license = "0BSD" @@ -18,6 +18,7 @@ rustls = { version = "0.18", optional = true, features = ["dangerous_configurati webpki = { version = "0.21.0", optional = true } webpki-roots = { version = "0.20", optional = true } tokio-rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true } +tokio-io-timeout = "0.4" log = "0.4" url = "2" thiserror = "1" @@ -42,7 +43,7 @@ optional = true [features] default = ["client", "server"] -client = ["rustls", "webpki", "webpki-roots"] +client = ["rustls", "webpki", "webpki-roots", "tokio", "tokio-rustls"] server = ["rustls", "webpki", "webpki-roots", "tokio", "async-trait", "tokio-rustls"] [workspace] diff --git a/VERSION b/VERSION index 0ea3a94..0d91a54 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 +0.3.0 diff --git a/majc/Cargo.toml b/majc/Cargo.toml index 5d19534..ae85c61 100644 --- a/majc/Cargo.toml +++ b/majc/Cargo.toml @@ -10,5 +10,10 @@ edition = "2018" cursive = "0.15" log = "0.4" url = "2" +webpki = "0.21.0" +tokio-rustls = { version = "0.14", features = ["dangerous_configuration"] } +rustls = { version = "0.18", features = ["dangerous_configuration"] } +smol = { version = "0.3", features = ["tokio02"] } maj = { path = ".." } + diff --git a/majc/src/gemini.rs b/majc/src/gemini.rs index 2c527dd..7ccb3ac 100644 --- a/majc/src/gemini.rs +++ b/majc/src/gemini.rs @@ -7,13 +7,26 @@ use cursive::{ }; use maj::{self, Response}; use std::str; +use tokio_rustls::rustls::ClientConfig; /// The state of the browser. -#[derive(Default, Clone, Debug)] +#[derive(Clone)] pub struct State { url: Option, history: Vec, links: Vec<(url::Url, Option)>, + cfg: ClientConfig, +} + +impl Default for State { + fn default() -> Self { + State { + url: None, + history: vec![], + links: vec![], + cfg: crate::tls::config(), + } + } } pub fn history(siv: &mut Cursive) { @@ -100,7 +113,7 @@ pub fn open(siv: &mut Cursive, url: &str) { st.url = Some(url.clone()); - match maj::get(url.to_string()) { + match smol::run(maj::get(url.to_string(), st.cfg.clone())) { Ok(resp) => { st.history.push(url.clone()); show(siv, url.to_string().as_str(), resp); @@ -200,14 +213,12 @@ pub fn render(body: &str) -> StyledString { for node in doc { match node { Text(line) => styled.append(StyledString::plain(line)), - Link { to, name } => { - match name { - None => styled.append(StyledString::styled(to, Style::from(Effect::Underline))), - Some(name) => { - styled.append(StyledString::styled(name, Style::from(Effect::Underline))) - } + Link { to, name } => match name { + None => styled.append(StyledString::styled(to, Style::from(Effect::Underline))), + Some(name) => { + styled.append(StyledString::styled(name, Style::from(Effect::Underline))) } - } + }, Preformatted(data) => styled.append(StyledString::plain(data)), Heading { level, body } => styled.append(StyledString::styled( format!("{} {}", "#".repeat(level as usize), body), diff --git a/majc/src/main.rs b/majc/src/main.rs index b3bd9d2..53ae9a3 100644 --- a/majc/src/main.rs +++ b/majc/src/main.rs @@ -3,6 +3,7 @@ use cursive::{event::Key, menu::MenuTree}; pub(crate) mod commands; pub(crate) mod gemini; pub(crate) mod theme; +pub(crate) mod tls; fn main() { cursive::logger::init(); diff --git a/majc/src/tls.rs b/majc/src/tls.rs new file mode 100644 index 0000000..7a6e853 --- /dev/null +++ b/majc/src/tls.rs @@ -0,0 +1,25 @@ +use tokio_rustls::rustls; +use std::sync::Arc; + +pub fn config() -> rustls::ClientConfig { + let mut config = rustls::ClientConfig::new(); + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification {})); + + config +} + +struct NoCertificateVerification {} + +impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } +} diff --git a/src/client.rs b/src/client.rs index 107a764..e1db0a6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,27 +1,15 @@ use crate::Response; -use rustls::{ClientConfig, ClientSession, Stream, TLSError}; -use std::{io::prelude::*, net::TcpStream, sync::Arc}; +use std::{io::Cursor, sync::Arc}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; +use tokio_rustls::{ + rustls::{TLSError}, + TlsConnector, +}; use url::Url; -fn config() -> ClientConfig { - let mut config = ClientConfig::new(); - config.dangerous().set_certificate_verifier(Arc::new(NoCertificateVerification{})); - - config -} - -struct NoCertificateVerification {} - -impl rustls::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert(&self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: webpki::DNSNameRef<'_>, - _ocsp: &[u8]) -> Result { - Ok(rustls::ServerCertVerified::assertion()) - } -} - #[derive(thiserror::Error, Debug)] pub enum Error { #[error("TLS error: {0:?}")] @@ -43,7 +31,7 @@ pub enum Error { InvalidScheme(String), } -pub fn get(u: T) -> Result +pub async fn get(u: T, cfg: tokio_rustls::rustls::ClientConfig) -> Result where T: Into, { @@ -53,38 +41,73 @@ where ur.set_port(Some(1965)).unwrap(); } - if ur.scheme() == "" { - let _ = ur.set_scheme("gemini"); - } - if ur.scheme() != "gemini" { return Err(Error::InvalidScheme(ur.scheme().to_string())); } - let cfg = Arc::new(config()); + let cfg = Arc::new(cfg); let host = ur.host_str().unwrap(); - let mut sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap()))?; let name_ref = webpki::DNSNameRef::try_from_ascii_str(host)?; - let mut client = ClientSession::new(&cfg, name_ref); - let mut tls = Stream::new(&mut client, &mut sock); + let config = TlsConnector::from(cfg); + + let sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap())).await?; + let mut tls = config.connect(name_ref, sock).await?; let req = format!("{}\r\n", u); log::trace!("writing request {:?}", req); - tls.write(req.as_bytes())?; - Ok(Response::parse(&mut tls)?) + tls.write(req.as_bytes()).await?; + let mut buf: Vec = vec![]; + tls.read_to_end(&mut buf).await?; + Ok(Response::parse(&mut Cursor::new(buf))?) } #[cfg(test)] mod tests { + use tokio_rustls::rustls; + + fn config() -> rustls::ClientConfig { + let mut config = rustls::ClientConfig::new(); + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification {})); + + config + } + + struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } + use super::*; - use crate::*; - #[test] - fn gemini_homepage() -> Result<(), Error> { + #[tokio::test] + async fn gemini_homepage() -> Result<(), Error> { let _ = pretty_env_logger::try_init(); - let resp = get("gemini://gemini.circumlunar.space/".to_string())?; + let resp = get("gemini://gemini.circumlunar.space/".to_string(), config()).await?; - assert_eq!(resp.status, StatusCode::Success); + assert_eq!(resp.status, crate::StatusCode::Success); + assert_eq!(resp.meta, "text/gemini"); + assert_ne!(resp.body.len(), 0); + + Ok(()) + } + + #[tokio::test] + async fn gus() -> Result<(), Error> { + let _ = pretty_env_logger::try_init(); + let resp = get("gemini://gus.guru/".to_string(), config()).await?; + + assert_eq!(resp.status, crate::StatusCode::Success); assert_eq!(resp.meta, "text/gemini"); assert_ne!(resp.body.len(), 0); diff --git a/src/response.rs b/src/response.rs index 1858060..46b0cf2 100644 --- a/src/response.rs +++ b/src/response.rs @@ -58,6 +58,7 @@ impl Response { result.body = data; return Ok(result); } + panic!("got here: {}, {:?}", n, state); } }