-- Leo's gemini proxy
-- Connecting to typed-hole.org:1965...
-- Connected
-- Sending request
-- Meta line: 20 text/gemini
commit 32d9aece6178edb2d8a49f1c63a8245546773eea
Author: Julien Blanchard <julien@sideburns.eu>
Date: Wed Oct 2 14:38:39 2019 +0200
Open any mime type.
Reworked parser to split header from content directly at the bytes
level.
Open files by downloading them to a temp file before opening them.
Refactor non-Cursive functions into other modules.
diff --git a/Cargo.lock b/Cargo.lock
index c6f7894..bd28038 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -20,12 +20,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "asuka"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"cursive 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "json 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -109,7 +112,7 @@ dependencies = [
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"enum-map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "enumset 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enumset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -187,16 +190,16 @@ dependencies = [
[[package]]
name = "enumset"
-version = "0.4.0"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
- "enumset_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "enumset_derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "enumset_derive"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"darling 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -254,6 +257,11 @@ dependencies = [
]
[[package]]
+name = "json"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -381,6 +389,14 @@ dependencies = [
]
[[package]]
+name = "open"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "openssl"
version = "0.10.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -777,8 +793,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum enum-map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bc0515b284e6ce2cbacd123b339d9c5a0ce49059baa4d9e584ab3803b3dc973"
"checksum enum-map-derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e57001dfb2532f5a103ff869656887fae9a8defa7d236f3e39d2ee86ed629ad7"
"checksum enum-map-internals 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8d7f81655c75281b36ddb9c2a1502afcac9db780859cc5b2eba08efcccb4c510"
-"checksum enumset 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac0a22e173f6570a7d69a2ab9e3fe79cf0dcdd0fdb162bfc932b97158f2b2a7"
-"checksum enumset_derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01d93b926a992a4a526c2a14e2faf734fdef5bf9d0a52ba69a2ca7d4494c284b"
+"checksum enumset 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4293261d4f3472132ffdeb1c97be5f5de5267c4a764c6cc10066aeff35a54c"
+"checksum enumset_derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aeece157d0a6cda3f6015d7f16c570d4ba958161477448a9a6ec49851ccd8ee0"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
@@ -786,6 +802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353"
"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+"checksum json 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3ca41abbeb7615d56322a984e63be5e5d0a117dfaca86c14393e32a762ccac1"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
@@ -801,6 +818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
+"checksum open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "94b424e1086328b0df10235c6ff47be63708071881bead9e76997d9291c0134b"
"checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
"checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884"
diff --git a/Cargo.toml b/Cargo.toml
index 552214d..a6559dc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "asuka"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Julien Blanchard <julien@sideburns.eu>"]
edition = "2018"
@@ -10,3 +10,6 @@ native-tls = "*"
url = "*"
regex = "*"
lazy_static = "*"
+open = "*"
+json = "*"
+tempfile = "*"
diff --git a/src/absolute.rs b/src/absolute.rs
new file mode 100644
index 0000000..7e5bfcf
--- /dev/null
+++ b/src/absolute.rs
@@ -0,0 +1,89 @@
+use url::Url;
+
+pub fn make(url: &str) -> Result<url::Url, url::ParseError> {
+ // Creates an absolute link if needed
+ match super::history::get_current_host() {
+ Some(host) => {
+ if url.starts_with("gemini://") {
+ Url::parse(url)
+ } else if url.starts_with("//") {
+ Url::parse(&format!("gemini:{}", url))
+ } else if url.starts_with('/') {
+ Url::parse(&format!("gemini://{}{}", host, url))
+ } else {
+ let current_host_path = super::history::get_current_url().unwrap();
+ Url::parse(&format!("{}{}", current_host_path, url))
+ }
+ }
+ None => {
+ if url.starts_with("gemini://") {
+ Url::parse(url)
+ } else if url.starts_with("//") {
+ Url::parse(&format!("gemini:{}", url))
+ } else {
+ Url::parse(&format!("gemini://{}", url))
+ }
+ }
+ }
+}
+
+#[test]
+fn test_make_absolute_full_url() {
+ super::history::append("gemini://typed-hole.org");
+ let url = "gemini://typed-hole.org/foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_full_url_no_protocol() {
+ super::history::append("gemini://typed-hole.org");
+ let url = "//typed-hole.org/foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_slash_path() {
+ super::history::append("gemini://typed-hole.org");
+ let url = "/foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_just_path() {
+ super::history::append("gemini://typed-hole.org");
+ let url = "foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_full_url_no_current_host() {
+ let url = "gemini://typed-hole.org/foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_full_url_no_protocol_no_current_host() {
+ let url = "//typed-hole.org/foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_slash_path_no_current_host() {
+ let url = "/foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
+#[test]
+fn test_make_absolute_just_path_no_current_host() {
+ let url = "foo";
+ let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
+ let absolute_url = make(&url).unwrap();
+ assert_eq!(expected_url, absolute_url);
+}
diff --git a/src/content.rs b/src/content.rs
index a7234ce..1606dab 100644
--- a/src/content.rs
+++ b/src/content.rs
@@ -1,10 +1,11 @@
+use tempfile::NamedTempFile;
use std::io::{Read, Write};
use native_tls::TlsConnector;
use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;
-pub fn get_data(url: &url::Url) -> Result<String, String> {
+pub fn get_data(url: &url::Url) -> Result<(Vec<u8>, Vec<u8>), String> {
let host = url.host_str().unwrap();
let urlf = format!("{}:1965", host);
@@ -28,7 +29,11 @@ pub fn get_data(url: &url::Url) -> Result<String, String> {
stream.write_all(url.as_bytes()).unwrap();
let mut res = vec![];
stream.read_to_end(&mut res).unwrap();
- Ok(String::from_utf8_lossy(&res).to_string())
+
+ let clrf_idx = find_subsequence(&res, b"\r\n");
+ let content = res.split_off(clrf_idx.unwrap() + 2);
+
+ Ok((res, content))
}
Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
}
@@ -41,3 +46,19 @@ pub fn get_data(url: &url::Url) -> Result<String, String> {
Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e))
}
}
+
+pub fn download(content: Vec<u8>) {
+ let path = write_tmp_file(content);
+ open::that(path).unwrap();
+}
+
+fn write_tmp_file(content: Vec<u8>) -> std::path::PathBuf {
+ let mut tmp_file = NamedTempFile::new().unwrap();
+ tmp_file.write_all(&content).unwrap();
+ let (_file, path) = tmp_file.keep().unwrap();
+ path
+}
+
+fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
+ haystack.windows(needle.len()).position(|window| window == needle)
+}
diff --git a/src/link.rs b/src/link.rs
index 35dfb9d..a1aa1ff 100644
--- a/src/link.rs
+++ b/src/link.rs
@@ -2,6 +2,7 @@ extern crate regex;
use regex::Regex;
use url::Url;
use std::str::FromStr;
+use json::JsonValue;
pub enum Link {
@@ -64,3 +65,7 @@ fn make_link(url: String, label: String) -> Option<Link> {
_ => None
}
}
+
+pub fn is_gemini(line: &JsonValue) -> bool {
+ line["type"] == "gemini"
+}
diff --git a/src/main.rs b/src/main.rs
index fc42187..4ee3a51 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,13 +5,15 @@ extern crate native_tls;
extern crate regex;
use cursive::align::HAlign;
-use cursive::theme::Effect;
+use cursive::theme::{BaseColor, Color, Effect, PaletteColor, Style, Theme};
use cursive::traits::*;
use cursive::utils::markup::StyledString;
use cursive::view::Scrollable;
use cursive::views::{Dialog, EditView, Panel, SelectView};
use cursive::Cursive;
+use json::object;
+
use std::str::FromStr;
use url::Url;
@@ -23,12 +25,13 @@ use link::Link;
mod content;
mod history;
+mod absolute;
const HELP: &str = "Welcome to Asuka Gemini browser!
-Press g to visit an URL
-Press h to show/hide history
-Press q to exit
+ Press g to visit an URL
+ Press h to show/hide history
+ Press q to exit
";
fn main() {
@@ -36,11 +39,14 @@ fn main() {
let mut siv = Cursive::default();
+ let theme = custom_colors(&siv);
+ siv.set_theme(theme);
+
let mut select = SelectView::new();
select.add_all_str(HELP.lines());
- select.set_on_submit(|s, link| {
- follow_link(s, link);
+ select.set_on_submit(|s, line| {
+ follow_line(s, line);
});
siv.add_fullscreen_layer(
@@ -49,7 +55,9 @@ fn main() {
))
.title("Asuka Browser")
.h_align(HAlign::Center)
- .button("Quit", |s| s.quit())
+ .button("Go To URL (g)", |s| prompt_for_url(s))
+ .button("History (h)", |s| show_history(s))
+ .button("Quit (q)", |s| s.quit())
.with_id("container"),
);
@@ -63,6 +71,13 @@ fn main() {
siv.run();
}
+fn custom_colors(s: &Cursive) -> Theme {
+ // We'll return the current theme with a small modification.
+ let mut theme = s.current_theme().clone();
+ theme.palette[PaletteColor::Highlight] = Color::Rgb(120, 120, 120);
+ theme
+}
+
fn prompt_for_url(s: &mut Cursive) {
s.add_layer(
Dialog::new()
@@ -83,10 +98,11 @@ fn prompt_for_answer(s: &mut Cursive, url: Url, message: String) {
.content(
EditView::new()
.on_submit(move |s, response| {
- let link = format!("{}?query={}", url.to_string(), response);
+ let link = format!("{}?{}", url.to_string(), response);
s.pop_layer();
follow_link(s, &link);
- }).fixed_width(60)
+ })
+ .fixed_width(60),
)
.with_id("url_query"),
);
@@ -134,14 +150,16 @@ fn visit_url(s: &mut Cursive, url: &Url) {
s.pop_layer();
}
- match make_absolute(url.as_str()) {
- Ok(url) => match content::get_data(&url) {
- Ok(new_content) => {
- history::append(url.as_str());
- draw_content(s, url, new_content);
- }
- Err(msg) => {
- s.add_layer(Dialog::info(msg));
+ match absolute::make(url.as_str()) {
+ Ok(url) => {
+ match content::get_data(&url) {
+ Ok((meta, new_content)) => {
+ history::append(url.as_str());
+ draw_content(s, &url, meta, new_content);
+ }
+ Err(msg) => {
+ s.add_layer(Dialog::info(msg));
+ }
}
},
Err(_) => {
@@ -150,81 +168,78 @@ fn visit_url(s: &mut Cursive, url: &Url) {
}
}
-fn draw_content(s: &mut Cursive, url: Url, content: String) {
- let url_copy = url.clone();
+fn draw_content(s: &mut Cursive, url: &Url, meta: Vec<u8>, content: Vec<u8>) {
+ let content_str = String::from_utf8_lossy(&content).to_string();
- // handle response status
- if let Some(status_line) = content.lines().next() {
- if let Ok(status) = Status::from_str(status_line) {
- match status {
- Status::Success(_meta) => {}
- Status::Gone(_meta) => {
- s.add_layer(Dialog::info("Sorry page is gone."));
- return;
- }
- Status::RedirectTemporary(new_url) | Status::RedirectPermanent(new_url) => {
- return follow_link(s, &new_url)
- }
- Status::TransientCertificateRequired(_meta)
- | Status::AuthorisedCertificatedRequired(_meta) => {
- s.add_layer(Dialog::info(
- "You need a valid certificate to access this page.",
- ));
- return;
- }
- Status::Input(message) => {
- prompt_for_answer(s, url_copy, message);
- }
- other_status => {
- s.add_layer(Dialog::info(format!("ERROR: {:?}", other_status)));
- return;
- }
- }
- }
- }
+ // handle meta header
+ handle_response_status(s, url, meta, content);
let mut main_view = match s.find_id::<SelectView>("main") {
Some(view) => view,
- None => panic!("Can't find main view.")
- };
- let mut container = match s.find_id::<Dialog>("container") {
- Some(view) => view,
- None => panic!("Can't find container view.")
+ None => panic!("Can't find main view."),
};
// set title and clear old content
- container.set_title(url.as_str());
+ set_title(s, url.as_str());
main_view.clear();
// draw new content lines
- for line in content.lines().skip(1) {
+ for line in content_str.lines() {
match Link::from_str(line) {
Ok(link) => match link {
- Link::Http(_url, label) => {
+ Link::Http(url, label) => {
let mut formatted = StyledString::new();
- let www_label = format!("[WWW] {}", label);
- formatted.append(StyledString::styled(www_label, Effect::Italic));
+ let www_label = format!("{} [WWW]", label);
+ formatted.append(StyledString::styled(
+ www_label,
+ Style::from(Color::Dark(BaseColor::Green)).combine(Effect::Bold),
+ ));
- main_view.add_item(formatted, String::from("0"))
+ let data = object! {
+ "type" => "www",
+ "url" => url.to_string()
+ };
+ main_view.add_item(formatted, json::stringify(data))
}
- Link::Gopher(_url, label) => {
+ Link::Gopher(url, label) => {
let mut formatted = StyledString::new();
- let gopher_label = format!("[Gopher] {}", label);
- formatted.append(StyledString::styled(gopher_label, Effect::Italic));
+ let gopher_label = format!("{} [Gopher]", label);
+ formatted.append(StyledString::styled(
+ gopher_label,
+ Style::from(Color::Light(BaseColor::Magenta)).combine(Effect::Bold),
+ ));
- main_view.add_item(formatted, String::from("0"))
+ let data = object! {
+ "type" => "gopher",
+ "url" => url.to_string()
+ };
+ main_view.add_item(formatted, json::stringify(data))
}
Link::Gemini(url, label) => {
let mut formatted = StyledString::new();
- formatted.append(StyledString::styled(label, Effect::Underline));
+ formatted.append(StyledString::styled(
+ label,
+ Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
+ ));
- main_view.add_item(formatted, url.to_string())
+ let data = object! {
+ "type" => "gemini",
+ "url" => url.to_string()
+ };
+ main_view.add_item(formatted, json::stringify(data))
}
Link::Relative(url, label) => {
let mut formatted = StyledString::new();
- formatted.append(StyledString::styled(label, Effect::Underline));
+ formatted.append(StyledString::styled(
+ label,
+ Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
+ ));
- main_view.add_item(formatted, url.to_string())
+ let data = object! {
+ "type" => "gemini",
+ "url" => url.to_string()
+ };
+ main_view.add_item(formatted, json::stringify(data))
}
Link::Unknown(_, _) => (),
},
@@ -233,101 +248,72 @@ fn draw_content(s: &mut Cursive, url: Url, content: String) {
}
}
-fn is_gemini_link(line: &str) -> bool {
- line != "0"
+fn handle_response_status(s: &mut Cursive, url: &Url, meta: Vec<u8>, content: Vec<u8>) {
+ let url_copy = url.clone();
+ let meta_str = String::from_utf8_lossy(&meta).to_string();
+
+ if let Ok(status) = Status::from_str(&meta_str) {
+ match status {
+ Status::Success(meta) => {
+ if meta.starts_with("text/") {
+ // display text files.
+ {}
+ } else {
+ // download and try to open the rest.
+ content::download(content);
+ return;
+ }
+ }
+ Status::Gone(_meta) => {
+ s.add_layer(Dialog::info("Sorry page is gone."));
+ return;
+ }
+ Status::RedirectTemporary(new_url) | Status::RedirectPermanent(new_url) => {
+ return follow_link(s, &new_url)
+ }
+ Status::TransientCertificateRequired(_meta)
+ | Status::AuthorisedCertificatedRequired(_meta) => {
+ s.add_layer(Dialog::info(
+ "You need a valid certificate to access this page.",
+ ));
+ return;
+ }
+ Status::Input(message) => {
+ prompt_for_answer(s, url_copy, message);
+ }
+ other_status => {
+ s.add_layer(Dialog::info(format!("ERROR: {:?}", other_status)));
+ return;
+ }
+ }
+ }
}
-fn follow_link(s: &mut Cursive, line: &str) {
- if is_gemini_link(line) {
- let next_url = make_absolute(line).expect("Not an URL");
- visit_url(s, &next_url)
- }
+fn set_title(s: &mut Cursive, text: &str) {
+ let mut container = match s.find_id::<Dialog>("container") {
+ Some(view) => view,
+ None => panic!("Can't find container view."),
+ };
+ container.set_title(text);
}
-fn make_absolute(url: &str) -> Result<url::Url, url::ParseError> {
- // Creates an absolute link if needed
- match history::get_current_host() {
- Some(host) => {
- if url.starts_with("gemini://") {
- Url::parse(url)
- } else if url.starts_with("//") {
- Url::parse(&format!("gemini:{}", url))
- } else if url.starts_with('/') {
- Url::parse(&format!("gemini://{}{}", host, url))
- } else {
- let current_host_path = history::get_current_url().unwrap();
- Url::parse(&format!("{}{}", current_host_path, url))
- }
- }
- None => {
- if url.starts_with("gemini://") {
- Url::parse(url)
- } else if url.starts_with("//") {
- Url::parse(&format!("gemini:{}", url))
+fn follow_line(s: &mut Cursive, line: &str) {
+ let parsed = json::parse(line);
+
+ match parsed {
+ Ok(data) => {
+ if link::is_gemini(&data) {
+ let next_url = absolute::make(&data["url"].to_string()).expect("Not an URL");
+ visit_url(s, &next_url)
} else {
- Url::parse(&format!("gemini://{}", url))
+ open::that(data["url"].to_string()).unwrap();
}
}
+ Err(_) => (),
}
}
-#[test]
-fn test_make_absolute_full_url() {
- history::append("gemini://typed-hole.org");
- let url = "gemini://typed-hole.org/foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_full_url_no_protocol() {
- history::append("gemini://typed-hole.org");
- let url = "//typed-hole.org/foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_slash_path() {
- history::append("gemini://typed-hole.org");
- let url = "/foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_just_path() {
- history::append("gemini://typed-hole.org");
- let url = "foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_full_url_no_current_host() {
- let url = "gemini://typed-hole.org/foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_full_url_no_protocol_no_current_host() {
- let url = "//typed-hole.org/foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_slash_path_no_current_host() {
- let url = "/foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
-}
-#[test]
-fn test_make_absolute_just_path_no_current_host() {
- let url = "foo";
- let expected_url = Url::parse("gemini://typed-hole.org/foo").unwrap();
- let absolute_url = make_absolute(&url).unwrap();
- assert_eq!(expected_url, absolute_url);
+fn follow_link(s: &mut Cursive, link: &str) {
+ let next_url = absolute::make(link).expect("Not an URL");
+ visit_url(s, &next_url)
}
diff --git a/src/status.rs b/src/status.rs
index 11fa614..93d69d5 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -32,7 +32,7 @@ pub enum Status {
pub struct ParseError;
-const STATUS_REGEX: &str = r"^(\d{1,3})[ \t](.*)$";
+const STATUS_REGEX: &str = r"^(\d{1,3})[ \t](.*)\r\n$";
impl FromStr for Status {
type Err = ParseError;
---
Served by Pollux Gemini Server.
-- Response ended
-- Page fetched on Sun May 19 12:18:14 2024