-- 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 {

Clone, Copy, PartialEq, Eq, Hash)]

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