2018-02-10 01:00:55 +01:00
|
|
|
use std::io::prelude::*;
|
|
|
|
use std::fs::{create_dir_all, File};
|
|
|
|
|
|
|
|
use rocket::Route;
|
|
|
|
use rocket::response::Content;
|
|
|
|
use rocket::http::ContentType;
|
|
|
|
|
|
|
|
use reqwest;
|
|
|
|
|
|
|
|
use CONFIG;
|
|
|
|
|
|
|
|
pub fn routes() -> Vec<Route> {
|
|
|
|
routes![icon]
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/<domain>/icon.png")]
|
|
|
|
fn icon(domain: String) -> Content<Vec<u8>> {
|
2018-02-17 18:48:42 +01:00
|
|
|
let icon_type = ContentType::new("image", "x-icon");
|
|
|
|
|
2018-02-10 01:00:55 +01:00
|
|
|
// Validate the domain to avoid directory traversal attacks
|
|
|
|
if domain.contains("/") || domain.contains("..") {
|
2018-02-17 18:48:42 +01:00
|
|
|
return Content(icon_type, get_fallback_icon());
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
let icon = get_icon(&domain);
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-02-17 18:48:42 +01:00
|
|
|
Content(icon_type, icon)
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
fn get_icon (domain: &str) -> Vec<u8> {
|
|
|
|
let path = format!("{}/{}.png", CONFIG.icon_cache_folder, domain);
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
if let Some(icon) = get_cached_icon(&path) {
|
|
|
|
return icon;
|
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
let url = get_icon_url(&domain);
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
// Get the icon, or fallback in case of error
|
|
|
|
match download_icon(&url) {
|
|
|
|
Ok(icon) => {
|
|
|
|
save_icon(&path, &icon);
|
|
|
|
icon
|
|
|
|
},
|
|
|
|
Err(_) => get_fallback_icon()
|
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
fn get_cached_icon(path: &str) -> Option<Vec<u8>> {
|
2018-02-15 00:53:11 +01:00
|
|
|
// Try to read the cached icon, and return it if it exists
|
2018-06-11 15:44:37 +02:00
|
|
|
if let Ok(mut f) = File::open(path) {
|
|
|
|
let mut buffer = Vec::new();
|
|
|
|
|
|
|
|
if f.read_to_end(&mut buffer).is_ok() {
|
2018-06-12 21:09:42 +02:00
|
|
|
return Some(buffer);
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_icon_url(domain: &str) -> String {
|
|
|
|
if CONFIG.local_icon_extractor {
|
|
|
|
format!("http://{}/favicon.ico", domain)
|
|
|
|
} else {
|
|
|
|
format!("https://icons.bitwarden.com/{}/icon.png", domain)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn download_icon(url: &str) -> Result<Vec<u8>, reqwest::Error> {
|
|
|
|
println!("Downloading icon for {}...", url);
|
|
|
|
let mut res = reqwest::get(url)?;
|
|
|
|
|
|
|
|
res = res.error_for_status()?;
|
|
|
|
|
|
|
|
let mut buffer: Vec<u8> = vec![];
|
|
|
|
res.copy_to(&mut buffer)?;
|
|
|
|
|
|
|
|
Ok(buffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn save_icon(path: &str, icon: &[u8]) {
|
|
|
|
create_dir_all(&CONFIG.icon_cache_folder).expect("Error creating icon cache");
|
2018-02-10 01:00:55 +01:00
|
|
|
|
2018-06-11 15:44:37 +02:00
|
|
|
if let Ok(mut f) = File::create(path) {
|
2018-06-12 21:09:42 +02:00
|
|
|
f.write_all(icon).expect("Error writing icon file");
|
2018-02-10 01:00:55 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-06-12 21:09:42 +02:00
|
|
|
const FALLBACK_ICON_URL: &str = "https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png";
|
|
|
|
|
2018-02-10 01:00:55 +01:00
|
|
|
fn get_fallback_icon() -> Vec<u8> {
|
2018-06-12 21:09:42 +02:00
|
|
|
let path = format!("{}/default.png", CONFIG.icon_cache_folder);
|
|
|
|
|
|
|
|
if let Some(icon) = get_cached_icon(&path) {
|
|
|
|
return icon;
|
|
|
|
}
|
|
|
|
|
|
|
|
match download_icon(FALLBACK_ICON_URL) {
|
|
|
|
Ok(icon) => {
|
|
|
|
save_icon(&path, &icon);
|
|
|
|
icon
|
|
|
|
},
|
|
|
|
Err(_) => vec![]
|
|
|
|
}
|
2018-02-10 01:00:55 +01:00
|
|
|
}
|