Browse Source

Don't try to display non-text content in the SelectView

Fix a bug where opening an image was making Asuka crash
after closing the external viewer.
openbsd
Julien Blanchard 7 years ago
parent
commit
15846feec2
  1. 11
      src/content.rs
  2. 18
      src/history.rs
  3. 24
      src/link.rs
  4. 207
      src/main.rs
  5. 2
      src/status.rs

11
src/content.rs

@ -1,5 +1,5 @@
use tempfile::NamedTempFile;
use std::io::{Read, Write};
use tempfile::NamedTempFile;
use native_tls::TlsConnector;
use std::net::{TcpStream, ToSocketAddrs};
@ -30,7 +30,7 @@ pub fn get_data(url: &url::Url) -> Result<(Vec<u8>, Vec<u8>), String> {
let mut res = vec![];
stream.read_to_end(&mut res).unwrap();
let clrf_idx = find_subsequence(&res, b"\r\n");
let clrf_idx = find_clrf(&res);
let content = res.split_off(clrf_idx.unwrap() + 2);
Ok((res, content))
@ -43,7 +43,7 @@ pub fn get_data(url: &url::Url) -> Result<(Vec<u8>, Vec<u8>), String> {
}
None => Err(format!("Could not connect to {}", urlf)),
},
Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e))
Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
}
}
@ -59,6 +59,7 @@ fn write_tmp_file(content: Vec<u8>) -> std::path::PathBuf {
path
}
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|window| window == needle)
fn find_clrf(data: &[u8]) -> Option<usize> {
let clrf = b"\r\n";
data.windows(clrf.len()).position(|window| window == clrf)
}

18
src/history.rs

@ -1,5 +1,5 @@
use url::Url;
use std::sync::Mutex;
use url::Url;
lazy_static! {
static ref HISTORY: Mutex<Vec<Url>> = Mutex::new(vec![]);
@ -23,13 +23,11 @@ pub fn append(url: &str) {
pub fn get_current_host() -> Option<String> {
let history = HISTORY.lock().unwrap();
match history.last() {
Some(current_url) => {
match current_url.host_str() {
Some(host) => Some(String::from(host)),
None => None
}
}
None => None
Some(current_url) => match current_url.host_str() {
Some(host) => Some(String::from(host)),
None => None,
},
None => None,
}
}
@ -40,9 +38,9 @@ pub fn get_current_url() -> Option<String> {
let current_path = current_url.join("./");
match current_path {
Ok(path) => Some(path.to_string()),
Err(_) => None
Err(_) => None,
}
}
None => None
None => None,
}
}

24
src/link.rs

@ -1,8 +1,8 @@
extern crate regex;
use json::JsonValue;
use regex::Regex;
use url::Url;
use std::str::FromStr;
use json::JsonValue;
use url::Url;
#[derive(Debug)]
pub enum Link {
@ -50,19 +50,15 @@ impl FromStr for Link {
fn make_link(url: String, label: String) -> Option<Link> {
let urlp = Url::parse(&url);
match urlp {
Ok(url) => {
match url.scheme() {
"gemini" => Some(Link::Gemini(url, label)),
"gopher" => Some(Link::Gopher(url, label)),
"http" => Some(Link::Http(url, label)),
"https" => Some(Link::Http(url, label)),
_ => Some(Link::Unknown(url, label))
}
},
Err(url::ParseError::RelativeUrlWithoutBase) => {
Some(Link::Relative(url, label))
Ok(url) => match url.scheme() {
"gemini" => Some(Link::Gemini(url, label)),
"gopher" => Some(Link::Gopher(url, label)),
"http" => Some(Link::Http(url, label)),
"https" => Some(Link::Http(url, label)),
_ => Some(Link::Unknown(url, label)),
},
_ => None
Err(url::ParseError::RelativeUrlWithoutBase) => Some(Link::Relative(url, label)),
_ => None,
}
}

207
src/main.rs

@ -23,9 +23,9 @@ use status::Status;
mod link;
use link::Link;
mod absolute;
mod content;
mod history;
mod absolute;
const HELP: &str = "Welcome to Asuka Gemini browser!
@ -151,15 +151,15 @@ fn visit_url(s: &mut Cursive, url: &Url) {
}
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));
}
Ok(url) => match content::get_data(&url) {
Ok((meta, new_content)) => {
history::append(url.as_str());
// handle meta header
let response = handle_response_status(s, &url, meta, new_content);
draw_content(s, &url, response);
}
Err(msg) => {
s.add_layer(Dialog::info(msg));
}
},
Err(_) => {
@ -168,87 +168,12 @@ fn visit_url(s: &mut Cursive, url: &Url) {
}
}
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 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."),
};
// set title and clear old content
set_title(s, url.as_str());
main_view.clear();
// draw new content lines
for line in content_str.lines() {
match Link::from_str(line) {
Ok(link) => match link {
Link::Http(url, label) => {
let mut formatted = StyledString::new();
let www_label = format!("{} [WWW]", label);
formatted.append(StyledString::styled(
www_label,
Style::from(Color::Dark(BaseColor::Green)).combine(Effect::Bold),
));
let data = object! {
"type" => "www",
"url" => url.to_string()
};
main_view.add_item(formatted, json::stringify(data))
}
Link::Gopher(url, label) => {
let mut formatted = StyledString::new();
let gopher_label = format!("{} [Gopher]", label);
formatted.append(StyledString::styled(
gopher_label,
Style::from(Color::Light(BaseColor::Magenta)).combine(Effect::Bold),
));
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,
Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
));
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,
Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
));
let data = object! {
"type" => "gemini",
"url" => url.to_string()
};
main_view.add_item(formatted, json::stringify(data))
}
Link::Unknown(_, _) => (),
},
Err(_) => main_view.add_item(str::replace(line, "\t", " "), String::from("0")),
}
}
}
fn handle_response_status(s: &mut Cursive, url: &Url, meta: Vec<u8>, content: Vec<u8>) {
fn handle_response_status(
s: &mut Cursive,
url: &Url,
meta: Vec<u8>,
content: Vec<u8>,
) -> Option<Vec<u8>> {
let url_copy = url.clone();
let meta_str = String::from_utf8_lossy(&meta).to_string();
@ -257,38 +182,126 @@ fn handle_response_status(s: &mut Cursive, url: &Url, meta: Vec<u8>, content: Ve
Status::Success(meta) => {
if meta.starts_with("text/") {
// display text files.
{}
Some(content)
} else {
// download and try to open the rest.
content::download(content);
return;
None
}
}
Status::Gone(_meta) => {
s.add_layer(Dialog::info("Sorry page is gone."));
return;
None
}
Status::RedirectTemporary(new_url) | Status::RedirectPermanent(new_url) => {
return follow_link(s, &new_url)
follow_link(s, &new_url);
None
}
Status::TransientCertificateRequired(_meta)
| Status::AuthorisedCertificatedRequired(_meta) => {
s.add_layer(Dialog::info(
"You need a valid certificate to access this page.",
));
return;
None
}
Status::Input(message) => {
prompt_for_answer(s, url_copy, message);
None
}
other_status => {
s.add_layer(Dialog::info(format!("ERROR: {:?}", other_status)));
return;
None
}
}
} else {
None
}
}
fn draw_content(s: &mut Cursive, url: &Url, content: Option<Vec<u8>>) {
match content {
Some(data) => {
let mut main_view = match s.find_id::<SelectView>("main") {
Some(view) => view,
None => panic!("Can't find main view."),
};
// set title and clear old content
set_title(s, url.as_str());
main_view.clear();
let content_str = String::from_utf8_lossy(&data).to_string();
// draw new content lines
for line in content_str.lines() {
match Link::from_str(line) {
Ok(link) => match link {
Link::Http(url, label) => {
let mut formatted = StyledString::new();
let www_label = format!("{} [WWW]", label);
formatted.append(StyledString::styled(
www_label,
Style::from(Color::Dark(BaseColor::Green)).combine(Effect::Bold),
));
let data = object! {
"type" => "www",
"url" => url.to_string()
};
main_view.add_item(formatted, json::stringify(data))
}
Link::Gopher(url, label) => {
let mut formatted = StyledString::new();
let gopher_label = format!("{} [Gopher]", label);
formatted.append(StyledString::styled(
gopher_label,
Style::from(Color::Light(BaseColor::Magenta)).combine(Effect::Bold),
));
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,
Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
));
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,
Style::from(Color::Light(BaseColor::Blue)).combine(Effect::Bold),
));
let data = object! {
"type" => "gemini",
"url" => url.to_string()
};
main_view.add_item(formatted, json::stringify(data))
}
Link::Unknown(_, _) => (),
},
Err(_) => {
main_view.add_item(str::replace(line, "\t", " "), String::from("0"))
}
}
}
}
None => (),
};
}
fn set_title(s: &mut Cursive, text: &str) {
let mut container = match s.find_id::<Dialog>("container") {
Some(view) => view,

2
src/status.rs

@ -79,6 +79,6 @@ fn make_status(code: i16, meta: String) -> Status {
63 => Status::CertificateNotAccepted(meta),
64 => Status::FutureCertificateRejected(meta),
65 => Status::ExpiredCertificateRejected(meta),
_ => Status::Unknown(meta)
_ => Status::Unknown(meta),
}
}

Loading…
Cancel
Save