use std::error::Error;
use std::fs::File;
use std::path::{Path, PathBuf};
use hyper::header::{Location, ContentType};
use mime_guess::guess_mime_type;
use mime::Mime;
use wrappers::Response;
use types::{
PenHTTPError,
PencilResult,
UserError,
};
use http_errors::{
HTTPError,
NotFound,
};
pub trait PathBound {
fn open_resource(&self, resource: &str) -> File;
}
pub fn safe_join(directory: &str, filename: &str) -> Option<PathBuf> {
let directory = Path::new(directory);
let filename = Path::new(filename);
match filename.to_str() {
Some(filename_str) => {
if filename.is_absolute() | (filename_str == "..") | (filename_str.starts_with("../")) {
None
} else {
Some(directory.join(filename_str))
}
},
None => None,
}
}
pub fn abort(code: u16) -> PencilResult {
Err(PenHTTPError(HTTPError::new(code)))
}
pub fn redirect(location: &str, code: u16) -> PencilResult {
let mut response = Response::from(format!(
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL:
<a href=\"{}\">{}</a>. If not click the link.
", location, location));
response.status_code = code;
response.set_content_type("text/html");
response.headers.set(Location(location.to_string()));
Ok(response)
}
pub fn escape(s: String) -> String {
s.replace("&", "&").replace("<", "<")
.replace(">", ">").replace("\"", """)
}
pub fn send_file(filepath: &str, mimetype: Mime, as_attachment: bool) -> PencilResult {
let filepath = Path::new(filepath);
if !filepath.is_file() {
return Err(PenHTTPError(NotFound));
}
let file = match File::open(&filepath) {
Ok(file) => file,
Err(e) => {
return Err(UserError::new(format!("couldn't open {}: {}", filepath.display(), e.description())).into());
}
};
let mut response: Response = file.into();
response.headers.set(ContentType(mimetype));
if as_attachment {
match filepath.file_name() {
Some(file) => {
match file.to_str() {
Some(filename) => {
let content_disposition = format!("attachment; filename={}", filename);
response.headers.set_raw("Content-Disposition", vec![content_disposition.as_bytes().to_vec()]);
},
None => {
return Err(UserError::new("filename unavailable, required for sending as attachment.").into());
}
}
},
None => {
return Err(UserError::new("filename unavailable, required for sending as attachment.").into());
}
}
}
Ok(response)
}
pub fn send_from_directory(directory: &str, filename: &str,
as_attachment: bool) -> PencilResult {
match safe_join(directory, filename) {
Some(filepath) => {
let mimetype = guess_mime_type(filepath.as_path());
match filepath.as_path().to_str() {
Some(filepath) => {
send_file(filepath, mimetype, as_attachment)
},
None => {
Err(PenHTTPError(NotFound))
}
}
},
None => {
Err(PenHTTPError(NotFound))
}
}
}