use std::path::{PathBuf, Path};
use std::fs;
use std::error::Error;
use std::fmt;
#[cfg(feature = "cache")]
use time::{self, Timespec};
#[cfg(feature = "cache")]
use std::time::Duration;
use iron::prelude::*;
use iron::{Handler, status};
#[cfg(feature = "cache")]
use iron::modifier::Modifier;
use iron::modifiers::Redirect;
use mount::OriginalUrl;
use requested_path::RequestedPath;
#[derive(Clone)]
pub struct Static {
pub root: PathBuf,
#[cfg(feature = "cache")]
cache: Option<Cache>,
}
impl Static {
#[cfg(feature = "cache")]
pub fn new<P: AsRef<Path>>(root: P) -> Static {
Static {
root: root.as_ref().to_path_buf(),
cache: None
}
}
#[cfg(not(feature = "cache"))]
pub fn new<P: AsRef<Path>>(root: P) -> Static {
Static {
root: root.as_ref().to_path_buf(),
}
}
#[cfg(feature = "cache")]
pub fn cache(self, duration: Duration) -> Static {
self.set(Cache::new(duration))
}
#[cfg(feature = "cache")]
fn try_cache<P: AsRef<Path>>(&self, req: &mut Request, path: P) -> IronResult<Response> {
match self.cache {
None => Ok(Response::with((status::Ok, path.as_ref()))),
Some(ref cache) => cache.handle(req, path.as_ref()),
}
}
}
impl Handler for Static {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
use std::io;
let requested_path = RequestedPath::new(&self.root, req);
let metadata = match fs::metadata(&requested_path.path) {
Ok(meta) => meta,
Err(e) => {
let status = match e.kind() {
io::ErrorKind::NotFound => status::NotFound,
io::ErrorKind::PermissionDenied => status::Forbidden,
_ => status::InternalServerError,
};
return Err(IronError::new(e, status))
},
};
if requested_path.should_redirect(&metadata, req) {
let mut redirect_path = match req.extensions.get::<OriginalUrl>() {
None => &req.url,
Some(original_url) => original_url,
}.clone();
redirect_path.path.push("".to_string());
return Ok(Response::with((status::MovedPermanently,
format!("Redirecting to {}", redirect_path),
Redirect(redirect_path))));
}
match requested_path.get_file(&metadata) {
None => Err(IronError::new(NoFile, status::NotFound)),
#[cfg(feature = "cache")]
Some(path) => self.try_cache(req, path),
#[cfg(not(feature = "cache"))]
Some(path) => {
let path: &Path = &path;
Ok(Response::with((status::Ok, path)))
},
}
}
}
impl Set for Static {}
#[cfg(feature = "cache")]
#[derive(Clone)]
pub struct Cache {
pub duration: Duration,
}
#[cfg(feature = "cache")]
impl Cache {
pub fn new(duration: Duration) -> Cache {
Cache { duration: duration }
}
fn handle<P: AsRef<Path>>(&self, req: &mut Request, path: P) -> IronResult<Response> {
use iron::headers::{IfModifiedSince, HttpDate};
let path = path.as_ref();
let last_modified_time = match fs::metadata(path) {
Err(error) => return Err(IronError::new(error, status::InternalServerError)),
Ok(metadata) => {
use filetime::FileTime;
let time = FileTime::from_last_modification_time(&metadata);
Timespec::new(time.seconds() as i64, time.nanoseconds() as i32)
},
};
let if_modified_since = match req.headers.get::<IfModifiedSince>().cloned() {
None => return Ok(self.response_with_cache(path, last_modified_time)),
Some(IfModifiedSince(HttpDate(time))) => time.to_timespec(),
};
if last_modified_time <= if_modified_since {
Ok(Response::with(status::NotModified))
} else {
Ok(self.response_with_cache(path, last_modified_time))
}
}
fn response_with_cache<P: AsRef<Path>>(&self, path: P, modified: Timespec) -> Response {
use iron::headers::{CacheControl, LastModified, CacheDirective, HttpDate};
let mut response = Response::with((status::Ok, path.as_ref()));
let seconds = self.duration.secs() as u32;
let cache = vec![CacheDirective::Public, CacheDirective::MaxAge(seconds)];
response.headers.set(CacheControl(cache));
response.headers.set(LastModified(HttpDate(time::at(modified))));
response
}
}
#[cfg(feature = "cache")]
impl Modifier<Static> for Cache {
fn modify(self, static_handler: &mut Static) {
static_handler.cache = Some(self);
}
}
#[derive(Debug)]
pub struct NoFile;
impl Error for NoFile {
fn description(&self) -> &str { "File not found" }
}
impl fmt::Display for NoFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}