-- Leo's gemini proxy
-- Connecting to typed-hole.org:1965...
-- Connected
-- Sending request
-- Meta line: 20 text/gemini
commit a7872fd21369c10068a09306e65b8930e39ab420
Author: Julien Blanchard <julien@sideburns.eu>
Date: Mon Dec 23 13:03:34 2019 +0100
Handles statuses, open external links
diff --git a/Cargo.lock b/Cargo.lock
index 9dac585..eec952b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -140,6 +140,7 @@ dependencies = [
"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)",
"pango 0.8.0 (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)",
@@ -547,6 +548,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.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -975,6 +984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
+"checksum open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "94b424e1086328b0df10235c6ff47be63708071881bead9e76997d9291c0134b"
"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f"
diff --git a/Cargo.toml b/Cargo.toml
index 3bc3ee8..d07e2e4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ glib = "*"
glib-sys = "*"
pango = "*"
json = "*"
+open = "*"
regex = "*"
native-tls = "*"
url = "*"
diff --git a/src/content.rs b/src/content.rs
index 6e8be18..184d9e4 100644
--- a/src/content.rs
+++ b/src/content.rs
@@ -47,10 +47,10 @@ pub fn get_data(url: &url::Url) -> Result<(Vec<u8>, Vec<u8>), String> {
}
}
-// pub fn download(content: Vec<u8>) {
-// let path = write_tmp_file(content);
-// open::that(path).unwrap();
-// }
+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();
diff --git a/src/main.rs b/src/main.rs
index 5cfaef3..7a012ee 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,8 @@ use glib::clone;
use gtk::prelude::*;
use gtk::TextBuffer;
+use url::Url;
+
mod gui;
use gui::Gui;
mod absolute;
@@ -19,8 +21,10 @@ mod history;
mod link;
use link::Link;
mod parser;
-mod tags;
use parser::{ParseError, TextElement, TextElement::*};
+mod status;
+use status::Status;
+mod tags;
fn main() {
// Start up the GTK3 subsystem.
@@ -80,16 +84,61 @@ fn visit_url(gui: &Arc<Gui>, url: String) {
match absolute::make(url.as_str()) {
Ok(url) => match content::get_data(&url) {
- Ok((_meta, new_content)) => {
- history::append(url.as_str());
- update_url_field(&gui, url.as_str());
- let content_str = String::from_utf8_lossy(&new_content).to_string();
-
- let parsed_content = parser::parse(content_str);
- clear_buffer(&content_view);
- draw_content(&gui, parsed_content);
-
- content_view.show_all();
+ Ok((meta, new_content)) => {
+ 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.
+ history::append(url.as_str());
+ update_url_field(&gui, url.as_str());
+ let content_str =
+ String::from_utf8_lossy(&new_content).to_string();
+
+ let parsed_content = parser::parse(content_str);
+ clear_buffer(&content_view);
+ draw_content(&gui, parsed_content);
+
+ content_view.show_all();
+ } else {
+ // download and try to open the rest.
+ content::download(new_content);
+ }
+ }
+ Status::Gone(_meta) => {
+ clear_buffer(&content_view);
+
+ let buffer = content_view.get_buffer().unwrap();
+ let mut end_iter = buffer.get_end_iter();
+
+ buffer.insert_markup(
+ &mut end_iter,
+ "<span foreground=\"red\" size=\"x-large\">Sorry page is gone.</span>\n",
+ );
+ }
+ Status::RedirectTemporary(new_url)
+ | Status::RedirectPermanent(new_url) => {
+ visit_url(&gui, new_url);
+ }
+ Status::TransientCertificateRequired(_meta)
+ | Status::AuthorisedCertificatedRequired(_meta) => {
+ clear_buffer(&content_view);
+
+ let buffer = content_view.get_buffer().unwrap();
+ let mut end_iter = buffer.get_end_iter();
+
+ buffer.insert_markup(
+ &mut end_iter,
+ "<span foreground=\"red\" size=\"x-large\">You need a valid certificate to access this page.</span>\n",
+ );
+ }
+ // Status::Input(message) => {
+ // prompt_for_answer(s, url_copy, message);
+ // }
+ _ => (),
+ }
+ }
}
Err(_) => {
let buffer = content_view.get_buffer().unwrap();
@@ -167,9 +216,6 @@ fn draw_content(gui: &Arc<Gui>, content: Vec<Result<TextElement, ParseError>>) -
}
fn draw_link(gui: &Arc<Gui>, link_item: String) {
- let content_view = gui.content_view();
- let buffer = content_view.get_buffer().unwrap();
-
match Link::from_str(&link_item) {
Ok(Link::Http(url, label)) => {
let button_label = if label.is_empty() {
@@ -179,19 +225,7 @@ fn draw_link(gui: &Arc<Gui>, link_item: String) {
};
let www_label = format!("{} [WWW]", button_label);
- let button = gtk::Button::new_with_label(&www_label);
- button.set_tooltip_text(Some(&url.to_string()));
-
- button.connect_clicked(clone!(@weak gui => move |_| {
- let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();
- visit_url(&gui, new_url);
- }));
-
- let mut start_iter = buffer.get_end_iter();
- let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
- content_view.add_child_at_anchor(&button, &anchor);
- let mut end_iter = buffer.get_end_iter();
- buffer.insert(&mut end_iter, "\n");
+ insert_external_button(&gui, url, &www_label);
}
Ok(Link::Gopher(url, label)) => {
let button_label = if label.is_empty() {
@@ -200,66 +234,60 @@ fn draw_link(gui: &Arc<Gui>, link_item: String) {
label
};
let gopher_label = format!("{} [Gopher]", button_label);
+ insert_external_button(&gui, url, &gopher_label);
+ }
+ Ok(Link::Gemini(url, label)) => {
+ insert_gemini_button(&gui, url, label);
+ }
+ Ok(Link::Relative(url, label)) => {
+ let new_url = absolute::make(&url.clone().to_string()).unwrap();
+ insert_gemini_button(&gui, new_url, label);
+ }
+ Ok(Link::Unknown(_, _)) => (),
+ Err(_) => (),
+ }
+}
- let button = gtk::Button::new_with_label(&gopher_label);
- button.set_tooltip_text(Some(&url.to_string()));
+fn insert_gemini_button(gui: &Arc<Gui>, url: Url, label: String) {
+ let content_view = gui.content_view();
+ let buffer = content_view.get_buffer().unwrap();
- button.connect_clicked(clone!(@weak gui => move |_| {
- let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();
- visit_url(&gui, new_url);
- }));
+ let button_label = if label.is_empty() {
+ url.clone().to_string()
+ } else {
+ label
+ };
- let mut start_iter = buffer.get_end_iter();
- let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
- content_view.add_child_at_anchor(&button, &anchor);
- let mut end_iter = buffer.get_end_iter();
- buffer.insert(&mut end_iter, "\n");
- }
- Ok(Link::Gemini(url, label)) => {
- let button_label = if label.is_empty() {
- url.clone().to_string()
- } else {
- label
- };
+ let button = gtk::Button::new_with_label(&button_label);
+ button.set_tooltip_text(Some(&url.to_string()));
- let button = gtk::Button::new_with_label(&button_label);
- button.set_tooltip_text(Some(&url.to_string()));
+ button.connect_clicked(clone!(@weak gui => move |_| {
+ visit_url(&gui, url.to_string());
+ }));
- button.connect_clicked(clone!(@weak gui => move |_| {
- let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();
- visit_url(&gui, new_url);
- }));
+ let mut start_iter = buffer.get_end_iter();
+ let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
+ content_view.add_child_at_anchor(&button, &anchor);
+ let mut end_iter = buffer.get_end_iter();
+ buffer.insert(&mut end_iter, "\n");
+}
- let mut start_iter = buffer.get_end_iter();
- let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
- content_view.add_child_at_anchor(&button, &anchor);
- let mut end_iter = buffer.get_end_iter();
- buffer.insert(&mut end_iter, "\n");
- }
- Ok(Link::Relative(url, label)) => {
- let button_label = if label.is_empty() {
- url.clone().to_string()
- } else {
- label
- };
+fn insert_external_button(gui: &Arc<Gui>, url: Url, label: &str) {
+ let content_view = gui.content_view();
+ let buffer = content_view.get_buffer().unwrap();
- let button = gtk::Button::new_with_label(&button_label);
- button.set_tooltip_text(Some(&url.to_string()));
+ let button = gtk::Button::new_with_label(&label);
+ button.set_tooltip_text(Some(&url.to_string()));
- button.connect_clicked(clone!(@weak gui => move |_| {
- let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();
- visit_url(&gui, new_url);
- }));
+ button.connect_clicked(move |_| {
+ open::that(url.to_string()).unwrap();
+ });
- let mut start_iter = buffer.get_end_iter();
- let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
- content_view.add_child_at_anchor(&button, &anchor);
- let mut end_iter = buffer.get_end_iter();
- buffer.insert(&mut end_iter, "\n");
- }
- Ok(Link::Unknown(_, _)) => (),
- Err(_) => (),
- }
+ let mut start_iter = buffer.get_end_iter();
+ let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();
+ content_view.add_child_at_anchor(&button, &anchor);
+ let mut end_iter = buffer.get_end_iter();
+ buffer.insert(&mut end_iter, "\n");
}
fn clear_buffer(view: >k::TextView) {
diff --git a/src/status.rs b/src/status.rs
new file mode 100644
index 0000000..2b70940
--- /dev/null
+++ b/src/status.rs
@@ -0,0 +1,84 @@
+extern crate regex;
+use regex::Regex;
+
+use std::str::FromStr;
+
+#[derive(Debug)]
+pub enum Status {
+ Input(String),
+ Success(String),
+ SuccessEndOfClientCertificateSession(String),
+ RedirectTemporary(String),
+ RedirectPermanent(String),
+ TemporaryFailure(String),
+ ServerUnavailable(String),
+ CGIError(String),
+ ProxyError(String),
+ SlowDown(String),
+ PermanentFailure(String),
+ NotFound(String),
+ Gone(String),
+ ProxyRequestRefused(String),
+ BadRequest(String),
+ ClientCertificateRequired(String),
+ TransientCertificateRequired(String),
+ AuthorisedCertificatedRequired(String),
+ CertificateNotAccepted(String),
+ FutureCertificateRejected(String),
+ ExpiredCertificateRejected(String),
+ Unknown(String),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ParseError;
+
+const STATUS_REGEX: &str = r"^(\d{1,3})[ \t](.*)\r\n$";
+
+impl FromStr for Status {
+ type Err = ParseError;
+
+ // Parses a &str into an instance of 'Header'
+ fn from_str(line: &str) -> Result<Self, Self::Err> {
+ let status_regexp = Regex::new(STATUS_REGEX).unwrap();
+
+ match status_regexp.captures(&line) {
+ Some(caps) => {
+ let code_str = caps.get(1).map_or("", |m| m.as_str());
+ let meta_str = caps.get(2).map_or("", |m| m.as_str());
+
+ let code: i16 = code_str.parse().expect("2");
+ let meta = meta_str.to_string();
+
+ Ok(make_status(code, meta))
+ }
+ None => Err(ParseError),
+ }
+ }
+}
+
+fn make_status(code: i16, meta: String) -> Status {
+ match code {
+ 10 => Status::Input(meta),
+ 20 => Status::Success(meta),
+ 21 => Status::SuccessEndOfClientCertificateSession(meta),
+ 30 => Status::RedirectTemporary(meta),
+ 31 => Status::RedirectPermanent(meta),
+ 40 => Status::TemporaryFailure(meta),
+ 41 => Status::ServerUnavailable(meta),
+ 42 => Status::CGIError(meta),
+ 43 => Status::ProxyError(meta),
+ 44 => Status::SlowDown(meta),
+ 50 => Status::PermanentFailure(meta),
+ 51 => Status::NotFound(meta),
+ 52 => Status::Gone(meta),
+ 53 => Status::ProxyRequestRefused(meta),
+ 59 => Status::BadRequest(meta),
+ 60 => Status::ClientCertificateRequired(meta),
+ 61 => Status::TransientCertificateRequired(meta),
+ 62 => Status::AuthorisedCertificatedRequired(meta),
+ 63 => Status::CertificateNotAccepted(meta),
+ 64 => Status::FutureCertificateRejected(meta),
+ 65 => Status::ExpiredCertificateRejected(meta),
+ _ => Status::Unknown(meta),
+ }
+}
---
Served by Pollux Gemini Server.
-- Response ended
-- Page fetched on Sun May 19 07:56:29 2024