2021-03-19 12:25:35 +01:00
|
|
|
use std::{fmt, io, path::PathBuf, rc::Rc};
|
2020-09-21 00:18:25 +02:00
|
|
|
|
|
|
|
use actix_service::Service;
|
2021-04-01 16:26:13 +02:00
|
|
|
use actix_utils::future::ok;
|
2020-09-21 00:18:25 +02:00
|
|
|
use actix_web::{
|
|
|
|
dev::{ServiceRequest, ServiceResponse},
|
|
|
|
error::Error,
|
|
|
|
guard::Guard,
|
|
|
|
http::{header, Method},
|
|
|
|
HttpResponse,
|
|
|
|
};
|
2021-04-01 16:26:13 +02:00
|
|
|
use futures_core::future::LocalBoxFuture;
|
2020-09-21 00:18:25 +02:00
|
|
|
|
|
|
|
use crate::{
|
2021-02-12 00:03:17 +01:00
|
|
|
named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile,
|
2021-06-25 15:21:57 +02:00
|
|
|
PathBufWrap, PathFilter,
|
2020-09-21 00:18:25 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Assembled file serving service.
|
|
|
|
pub struct FilesService {
|
|
|
|
pub(crate) directory: PathBuf,
|
|
|
|
pub(crate) index: Option<String>,
|
|
|
|
pub(crate) show_index: bool,
|
|
|
|
pub(crate) redirect_to_slash: bool,
|
|
|
|
pub(crate) default: Option<HttpService>,
|
|
|
|
pub(crate) renderer: Rc<DirectoryRenderer>,
|
|
|
|
pub(crate) mime_override: Option<Rc<MimeOverride>>,
|
2021-06-25 15:21:57 +02:00
|
|
|
pub(crate) path_filter: Option<Rc<PathFilter>>,
|
2020-09-21 00:18:25 +02:00
|
|
|
pub(crate) file_flags: named::Flags,
|
|
|
|
pub(crate) guards: Option<Rc<dyn Guard>>,
|
2020-12-26 04:36:15 +01:00
|
|
|
pub(crate) hidden_files: bool,
|
2020-09-21 00:18:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl FilesService {
|
2021-04-01 16:26:13 +02:00
|
|
|
fn handle_err(
|
|
|
|
&self,
|
|
|
|
err: io::Error,
|
|
|
|
req: ServiceRequest,
|
|
|
|
) -> LocalBoxFuture<'static, Result<ServiceResponse, Error>> {
|
|
|
|
log::debug!("error handling {}: {}", req.path(), err);
|
2020-09-21 00:18:25 +02:00
|
|
|
|
2021-02-07 02:00:40 +01:00
|
|
|
if let Some(ref default) = self.default {
|
2021-04-01 16:26:13 +02:00
|
|
|
Box::pin(default.call(req))
|
2020-09-21 00:18:25 +02:00
|
|
|
} else {
|
2021-04-01 16:26:13 +02:00
|
|
|
Box::pin(ok(req.error_response(err)))
|
2020-09-21 00:18:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for FilesService {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.write_str("FilesService")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 00:47:04 +01:00
|
|
|
impl Service<ServiceRequest> for FilesService {
|
2020-09-21 00:18:25 +02:00
|
|
|
type Response = ServiceResponse;
|
|
|
|
type Error = Error;
|
2021-04-01 16:26:13 +02:00
|
|
|
type Future = LocalBoxFuture<'static, Result<ServiceResponse, Error>>;
|
2020-09-21 00:18:25 +02:00
|
|
|
|
2021-02-07 02:00:40 +01:00
|
|
|
actix_service::always_ready!();
|
2020-09-21 00:18:25 +02:00
|
|
|
|
2021-02-07 02:00:40 +01:00
|
|
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
2020-09-21 00:18:25 +02:00
|
|
|
let is_method_valid = if let Some(guard) = &self.guards {
|
|
|
|
// execute user defined guards
|
|
|
|
(**guard).check(req.head())
|
|
|
|
} else {
|
|
|
|
// default behavior
|
|
|
|
matches!(*req.method(), Method::HEAD | Method::GET)
|
|
|
|
};
|
|
|
|
|
|
|
|
if !is_method_valid {
|
2021-04-01 16:26:13 +02:00
|
|
|
return Box::pin(ok(req.into_response(
|
2020-09-21 00:18:25 +02:00
|
|
|
actix_web::HttpResponse::MethodNotAllowed()
|
2021-01-15 03:11:10 +01:00
|
|
|
.insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8))
|
2020-09-21 00:18:25 +02:00
|
|
|
.body("Request did not meet this resource's requirements."),
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2020-12-26 04:36:15 +01:00
|
|
|
let real_path =
|
|
|
|
match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) {
|
|
|
|
Ok(item) => item,
|
2021-04-01 16:26:13 +02:00
|
|
|
Err(e) => return Box::pin(ok(req.error_response(e))),
|
2020-12-26 04:36:15 +01:00
|
|
|
};
|
2020-09-21 00:18:25 +02:00
|
|
|
|
2021-06-25 15:21:57 +02:00
|
|
|
if let Some(filter) = &self.path_filter {
|
|
|
|
if !filter(real_path.as_ref(), req.head()) {
|
|
|
|
if let Some(ref default) = self.default {
|
|
|
|
return Box::pin(default.call(req));
|
|
|
|
} else {
|
|
|
|
return Box::pin(ok(
|
|
|
|
req.into_response(actix_web::HttpResponse::NotFound().finish())
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 00:18:25 +02:00
|
|
|
// full file path
|
2021-04-13 06:28:30 +02:00
|
|
|
let path = self.directory.join(&real_path);
|
|
|
|
if let Err(err) = path.canonicalize() {
|
|
|
|
return Box::pin(self.handle_err(err, req));
|
|
|
|
}
|
2020-09-21 00:18:25 +02:00
|
|
|
|
|
|
|
if path.is_dir() {
|
2021-05-26 11:42:29 +02:00
|
|
|
if self.redirect_to_slash
|
|
|
|
&& !req.path().ends_with('/')
|
|
|
|
&& (self.index.is_some() || self.show_index)
|
|
|
|
{
|
|
|
|
let redirect_to = format!("{}/", req.path());
|
|
|
|
|
|
|
|
return Box::pin(ok(req.into_response(
|
|
|
|
HttpResponse::Found()
|
|
|
|
.insert_header((header::LOCATION, redirect_to))
|
|
|
|
.finish(),
|
|
|
|
)));
|
|
|
|
}
|
2020-09-21 00:18:25 +02:00
|
|
|
|
2021-06-19 22:00:31 +02:00
|
|
|
let serve_named_file = |req: ServiceRequest, mut named_file: NamedFile| {
|
|
|
|
if let Some(ref mime_override) = self.mime_override {
|
|
|
|
let new_disposition = mime_override(&named_file.content_type.type_());
|
|
|
|
named_file.content_disposition.disposition = new_disposition;
|
2020-09-21 00:18:25 +02:00
|
|
|
}
|
2021-06-19 22:00:31 +02:00
|
|
|
named_file.flags = self.file_flags;
|
|
|
|
|
|
|
|
let (req, _) = req.into_parts();
|
|
|
|
let res = named_file.into_response(&req);
|
|
|
|
Box::pin(ok(ServiceResponse::new(req, res)))
|
|
|
|
};
|
|
|
|
|
|
|
|
let show_index = |req: ServiceRequest| {
|
|
|
|
let dir = Directory::new(self.directory.clone(), path.clone());
|
2020-09-21 00:18:25 +02:00
|
|
|
|
|
|
|
let (req, _) = req.into_parts();
|
|
|
|
let x = (self.renderer)(&dir, &req);
|
|
|
|
|
2021-04-01 16:26:13 +02:00
|
|
|
Box::pin(match x {
|
|
|
|
Ok(resp) => ok(resp),
|
|
|
|
Err(err) => ok(ServiceResponse::from_err(err, req)),
|
|
|
|
})
|
2021-06-19 22:00:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
match self.index {
|
|
|
|
Some(ref index) => match NamedFile::open(path.join(index)) {
|
|
|
|
Ok(named_file) => serve_named_file(req, named_file),
|
|
|
|
Err(_) if self.show_index => show_index(req),
|
|
|
|
Err(err) => self.handle_err(err, req),
|
|
|
|
},
|
|
|
|
None if self.show_index => show_index(req),
|
|
|
|
_ => Box::pin(ok(ServiceResponse::from_err(
|
2020-09-21 00:18:25 +02:00
|
|
|
FilesError::IsDirectory,
|
|
|
|
req.into_parts().0,
|
2021-06-19 22:00:31 +02:00
|
|
|
))),
|
2020-09-21 00:18:25 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
match NamedFile::open(path) {
|
|
|
|
Ok(mut named_file) => {
|
|
|
|
if let Some(ref mime_override) = self.mime_override {
|
2021-02-12 00:03:17 +01:00
|
|
|
let new_disposition = mime_override(&named_file.content_type.type_());
|
2020-09-21 00:18:25 +02:00
|
|
|
named_file.content_disposition.disposition = new_disposition;
|
|
|
|
}
|
|
|
|
named_file.flags = self.file_flags;
|
|
|
|
|
|
|
|
let (req, _) = req.into_parts();
|
2021-01-08 23:17:19 +01:00
|
|
|
let res = named_file.into_response(&req);
|
2021-04-01 16:26:13 +02:00
|
|
|
Box::pin(ok(ServiceResponse::new(req, res)))
|
2020-09-21 00:18:25 +02:00
|
|
|
}
|
2021-04-01 16:26:13 +02:00
|
|
|
Err(err) => self.handle_err(err, req),
|
2020-09-21 00:18:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|