majc: support history #3

Merged
cadey merged 4 commits from majc-history into main 2020-07-27 22:16:59 +00:00
9 changed files with 128 additions and 49 deletions
Showing only changes of commit 30afd1126c - Show all commits

View File

@ -1,5 +1,17 @@
# Changelog # 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 ## 0.2.0
### maj ### maj

View File

@ -1,6 +1,6 @@
[package] [package]
name = "maj" name = "maj"
version = "0.2.0" version = "0.3.0"
authors = ["Christine Dodrill <me@christine.website>"] authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018" edition = "2018"
license = "0BSD" license = "0BSD"
@ -18,6 +18,7 @@ rustls = { version = "0.18", optional = true, features = ["dangerous_configurati
webpki = { version = "0.21.0", optional = true } webpki = { version = "0.21.0", optional = true }
webpki-roots = { version = "0.20", optional = true } webpki-roots = { version = "0.20", optional = true }
tokio-rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true } tokio-rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true }
tokio-io-timeout = "0.4"
log = "0.4" log = "0.4"
url = "2" url = "2"
thiserror = "1" thiserror = "1"
@ -42,7 +43,7 @@ optional = true
[features] [features]
default = ["client", "server"] 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"] server = ["rustls", "webpki", "webpki-roots", "tokio", "async-trait", "tokio-rustls"]
[workspace] [workspace]

View File

@ -1 +1 @@
0.2.0 0.3.0

View File

@ -10,5 +10,10 @@ edition = "2018"
cursive = "0.15" cursive = "0.15"
log = "0.4" log = "0.4"
url = "2" 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 = ".." } maj = { path = ".." }

View File

@ -7,13 +7,26 @@ use cursive::{
}; };
use maj::{self, Response}; use maj::{self, Response};
use std::str; use std::str;
use tokio_rustls::rustls::ClientConfig;
/// The state of the browser. /// The state of the browser.
#[derive(Default, Clone, Debug)] #[derive(Clone)]
pub struct State { pub struct State {
url: Option<url::Url>, url: Option<url::Url>,
history: Vec<url::Url>, history: Vec<url::Url>,
links: Vec<(url::Url, Option<String>)>, links: Vec<(url::Url, Option<String>)>,
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) { pub fn history(siv: &mut Cursive) {
@ -100,7 +113,7 @@ pub fn open(siv: &mut Cursive, url: &str) {
st.url = Some(url.clone()); st.url = Some(url.clone());
match maj::get(url.to_string()) { match smol::run(maj::get(url.to_string(), st.cfg.clone())) {
Ok(resp) => { Ok(resp) => {
st.history.push(url.clone()); st.history.push(url.clone());
show(siv, url.to_string().as_str(), resp); show(siv, url.to_string().as_str(), resp);
@ -200,14 +213,12 @@ pub fn render(body: &str) -> StyledString {
for node in doc { for node in doc {
match node { match node {
Text(line) => styled.append(StyledString::plain(line)), Text(line) => styled.append(StyledString::plain(line)),
Link { to, name } => { Link { to, name } => match name {
match name {
None => styled.append(StyledString::styled(to, Style::from(Effect::Underline))), None => styled.append(StyledString::styled(to, Style::from(Effect::Underline))),
Some(name) => { Some(name) => {
styled.append(StyledString::styled(name, Style::from(Effect::Underline))) styled.append(StyledString::styled(name, Style::from(Effect::Underline)))
} }
} },
}
Preformatted(data) => styled.append(StyledString::plain(data)), Preformatted(data) => styled.append(StyledString::plain(data)),
Heading { level, body } => styled.append(StyledString::styled( Heading { level, body } => styled.append(StyledString::styled(
format!("{} {}", "#".repeat(level as usize), body), format!("{} {}", "#".repeat(level as usize), body),

View File

@ -3,6 +3,7 @@ use cursive::{event::Key, menu::MenuTree};
pub(crate) mod commands; pub(crate) mod commands;
pub(crate) mod gemini; pub(crate) mod gemini;
pub(crate) mod theme; pub(crate) mod theme;
pub(crate) mod tls;
fn main() { fn main() {
cursive::logger::init(); cursive::logger::init();

25
majc/src/tls.rs Normal file
View File

@ -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<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}

View File

@ -1,27 +1,15 @@
use crate::Response; use crate::Response;
use rustls::{ClientConfig, ClientSession, Stream, TLSError}; use std::{io::Cursor, sync::Arc};
use std::{io::prelude::*, net::TcpStream, sync::Arc}; use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
use tokio_rustls::{
rustls::{TLSError},
TlsConnector,
};
use url::Url; 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<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
#[error("TLS error: {0:?}")] #[error("TLS error: {0:?}")]
@ -43,7 +31,7 @@ pub enum Error {
InvalidScheme(String), InvalidScheme(String),
} }
pub fn get<T>(u: T) -> Result<crate::Response, Error> pub async fn get<T>(u: T, cfg: tokio_rustls::rustls::ClientConfig) -> Result<crate::Response, Error>
where where
T: Into<String>, T: Into<String>,
{ {
@ -53,38 +41,73 @@ where
ur.set_port(Some(1965)).unwrap(); ur.set_port(Some(1965)).unwrap();
} }
if ur.scheme() == "" {
let _ = ur.set_scheme("gemini");
}
if ur.scheme() != "gemini" { if ur.scheme() != "gemini" {
return Err(Error::InvalidScheme(ur.scheme().to_string())); 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 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 name_ref = webpki::DNSNameRef::try_from_ascii_str(host)?;
let mut client = ClientSession::new(&cfg, name_ref); let config = TlsConnector::from(cfg);
let mut tls = Stream::new(&mut client, &mut sock);
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); let req = format!("{}\r\n", u);
log::trace!("writing request {:?}", req); log::trace!("writing request {:?}", req);
tls.write(req.as_bytes())?; tls.write(req.as_bytes()).await?;
Ok(Response::parse(&mut tls)?) let mut buf: Vec<u8> = vec![];
tls.read_to_end(&mut buf).await?;
Ok(Response::parse(&mut Cursor::new(buf))?)
} }
#[cfg(test)] #[cfg(test)]
mod tests { 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<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
use super::*; use super::*;
use crate::*;
#[test] #[tokio::test]
fn gemini_homepage() -> Result<(), Error> { async fn gemini_homepage() -> Result<(), Error> {
let _ = pretty_env_logger::try_init(); 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_eq!(resp.meta, "text/gemini");
assert_ne!(resp.body.len(), 0); assert_ne!(resp.body.len(), 0);

View File

@ -58,6 +58,7 @@ impl Response {
result.body = data; result.body = data;
return Ok(result); return Ok(result);
} }
panic!("got here: {}, {:?}", n, state);
} }
} }