2019-03-06 06:10:08 +00:00
|
|
|
//! Static files support
|
2020-09-10 13:46:35 +00:00
|
|
|
|
|
|
|
#![deny(rust_2018_idioms)]
|
|
|
|
#![allow(clippy::borrow_interior_mutable_const)]
|
|
|
|
|
2019-03-06 06:10:08 +00:00
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::fmt::Write;
|
|
|
|
use std::fs::{DirEntry, File};
|
2019-11-21 05:31:31 +00:00
|
|
|
use std::future::Future;
|
2019-03-06 06:10:08 +00:00
|
|
|
use std::io::{Read, Seek};
|
|
|
|
use std::path::{Path, PathBuf};
|
2019-11-21 05:31:31 +00:00
|
|
|
use std::pin::Pin;
|
2019-03-06 06:10:08 +00:00
|
|
|
use std::rc::Rc;
|
2019-11-21 05:31:31 +00:00
|
|
|
use std::task::{Context, Poll};
|
2019-03-06 06:10:08 +00:00
|
|
|
use std::{cmp, io};
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
use actix_service::boxed::{self, BoxService, BoxServiceFactory};
|
2019-11-21 05:31:31 +00:00
|
|
|
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
2019-04-01 01:19:18 +00:00
|
|
|
use actix_web::dev::{
|
2019-04-15 14:32:49 +00:00
|
|
|
AppService, HttpServiceFactory, Payload, ResourceDef, ServiceRequest,
|
2019-04-07 21:43:07 +00:00
|
|
|
ServiceResponse,
|
2019-04-01 01:19:18 +00:00
|
|
|
};
|
2019-12-02 17:33:39 +00:00
|
|
|
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
|
2019-10-08 04:13:16 +00:00
|
|
|
use actix_web::guard::Guard;
|
2019-10-08 04:09:40 +00:00
|
|
|
use actix_web::http::header::{self, DispositionType};
|
|
|
|
use actix_web::http::Method;
|
2019-11-21 05:31:31 +00:00
|
|
|
use actix_web::{web, FromRequest, HttpRequest, HttpResponse};
|
2019-03-06 06:10:08 +00:00
|
|
|
use bytes::Bytes;
|
2020-05-18 02:41:07 +00:00
|
|
|
use futures_core::Stream;
|
|
|
|
use futures_util::future::{ok, ready, Either, FutureExt, LocalBoxFuture, Ready};
|
2019-08-11 20:43:29 +00:00
|
|
|
use mime_guess::from_ext;
|
2019-08-13 17:48:11 +00:00
|
|
|
use percent_encoding::{utf8_percent_encode, CONTROLS};
|
2019-03-06 06:10:08 +00:00
|
|
|
use v_htmlescape::escape as escape_html_entity;
|
|
|
|
|
|
|
|
mod error;
|
|
|
|
mod named;
|
2019-03-06 17:27:02 +00:00
|
|
|
mod range;
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-03-07 07:39:08 +00:00
|
|
|
use self::error::{FilesError, UriSegmentError};
|
2019-03-06 06:10:08 +00:00
|
|
|
pub use crate::named::NamedFile;
|
2019-03-06 17:27:02 +00:00
|
|
|
pub use crate::range::HttpRange;
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
|
|
|
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
2019-03-06 06:10:08 +00:00
|
|
|
|
|
|
|
/// Return the MIME type associated with a filename extension (case-insensitive).
|
|
|
|
/// If `ext` is empty or no associated type for the extension was found, returns
|
|
|
|
/// the type `application/octet-stream`.
|
|
|
|
#[inline]
|
|
|
|
pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
|
2019-08-11 20:43:29 +00:00
|
|
|
from_ext(ext).first_or_octet_stream()
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-12-02 17:33:39 +00:00
|
|
|
fn handle_error(err: BlockingError<io::Error>) -> Error {
|
|
|
|
match err {
|
|
|
|
BlockingError::Error(err) => err.into(),
|
|
|
|
BlockingError::Canceled => ErrorInternalServerError("Unexpected error"),
|
|
|
|
}
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
#[doc(hidden)]
|
|
|
|
/// A helper created from a `std::fs::File` which reads the file
|
|
|
|
/// chunk-by-chunk on a `ThreadPool`.
|
|
|
|
pub struct ChunkedReadFile {
|
|
|
|
size: u64,
|
|
|
|
offset: u64,
|
|
|
|
file: Option<File>,
|
2020-09-10 13:46:35 +00:00
|
|
|
#[allow(clippy::type_complexity)]
|
2019-12-02 17:33:39 +00:00
|
|
|
fut:
|
|
|
|
Option<LocalBoxFuture<'static, Result<(File, Bytes), BlockingError<io::Error>>>>,
|
2019-03-06 06:10:08 +00:00
|
|
|
counter: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Stream for ChunkedReadFile {
|
2019-11-21 05:31:31 +00:00
|
|
|
type Item = Result<Bytes, Error>;
|
|
|
|
|
|
|
|
fn poll_next(
|
|
|
|
mut self: Pin<&mut Self>,
|
2020-09-10 13:46:35 +00:00
|
|
|
cx: &mut Context<'_>,
|
2019-11-21 05:31:31 +00:00
|
|
|
) -> Poll<Option<Self::Item>> {
|
|
|
|
if let Some(ref mut fut) = self.fut {
|
|
|
|
return match Pin::new(fut).poll(cx) {
|
2019-12-02 17:33:39 +00:00
|
|
|
Poll::Ready(Ok((file, bytes))) => {
|
2019-03-06 06:10:08 +00:00
|
|
|
self.fut.take();
|
|
|
|
self.file = Some(file);
|
|
|
|
self.offset += bytes.len() as u64;
|
|
|
|
self.counter += bytes.len() as u64;
|
2019-11-21 05:31:31 +00:00
|
|
|
Poll::Ready(Some(Ok(bytes)))
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
2019-12-02 17:33:39 +00:00
|
|
|
Poll::Ready(Err(e)) => Poll::Ready(Some(Err(handle_error(e)))),
|
2019-11-21 05:31:31 +00:00
|
|
|
Poll::Pending => Poll::Pending,
|
2019-03-06 06:10:08 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
let size = self.size;
|
|
|
|
let offset = self.offset;
|
|
|
|
let counter = self.counter;
|
|
|
|
|
|
|
|
if size == counter {
|
2019-11-21 05:31:31 +00:00
|
|
|
Poll::Ready(None)
|
2019-03-06 06:10:08 +00:00
|
|
|
} else {
|
|
|
|
let mut file = self.file.take().expect("Use after completion");
|
2019-11-21 05:31:31 +00:00
|
|
|
self.fut = Some(
|
|
|
|
web::block(move || {
|
|
|
|
let max_bytes: usize;
|
|
|
|
max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize;
|
|
|
|
let mut buf = Vec::with_capacity(max_bytes);
|
|
|
|
file.seek(io::SeekFrom::Start(offset))?;
|
|
|
|
let nbytes =
|
|
|
|
file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?;
|
|
|
|
if nbytes == 0 {
|
|
|
|
return Err(io::ErrorKind::UnexpectedEof.into());
|
|
|
|
}
|
|
|
|
Ok((file, Bytes::from(buf)))
|
|
|
|
})
|
|
|
|
.boxed_local(),
|
|
|
|
);
|
|
|
|
self.poll_next(cx)
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type DirectoryRenderer =
|
2019-07-17 09:48:37 +00:00
|
|
|
dyn Fn(&Directory, &HttpRequest) -> Result<ServiceResponse, io::Error>;
|
2019-03-06 06:10:08 +00:00
|
|
|
|
|
|
|
/// A directory; responds with the generated directory listing.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Directory {
|
|
|
|
/// Base directory
|
|
|
|
pub base: PathBuf,
|
|
|
|
/// Path of subdirectory to generate listing for
|
|
|
|
pub path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Directory {
|
|
|
|
/// Create a new directory
|
|
|
|
pub fn new(base: PathBuf, path: PathBuf) -> Directory {
|
|
|
|
Directory { base, path }
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Is this entry visible from this directory?
|
|
|
|
pub fn is_visible(&self, entry: &io::Result<DirEntry>) -> bool {
|
|
|
|
if let Ok(ref entry) = *entry {
|
|
|
|
if let Some(name) = entry.file_name().to_str() {
|
|
|
|
if name.starts_with('.') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Ok(ref md) = entry.metadata() {
|
|
|
|
let ft = md.file_type();
|
|
|
|
return ft.is_dir() || ft.is_file() || ft.is_symlink();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// show file url as relative to static path
|
|
|
|
macro_rules! encode_file_url {
|
|
|
|
($path:ident) => {
|
2019-12-22 07:43:41 +00:00
|
|
|
utf8_percent_encode(&$path, CONTROLS)
|
2019-03-06 06:10:08 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// " -- " & -- & ' -- ' < -- < > -- > / -- /
|
|
|
|
macro_rules! encode_file_name {
|
|
|
|
($entry:ident) => {
|
|
|
|
escape_html_entity(&$entry.file_name().to_string_lossy())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn directory_listing(
|
|
|
|
dir: &Directory,
|
|
|
|
req: &HttpRequest,
|
|
|
|
) -> Result<ServiceResponse, io::Error> {
|
|
|
|
let index_of = format!("Index of {}", req.path());
|
|
|
|
let mut body = String::new();
|
|
|
|
let base = Path::new(req.path());
|
|
|
|
|
|
|
|
for entry in dir.path.read_dir()? {
|
|
|
|
if dir.is_visible(&entry) {
|
|
|
|
let entry = entry.unwrap();
|
|
|
|
let p = match entry.path().strip_prefix(&dir.path) {
|
2019-12-22 12:39:25 +00:00
|
|
|
Ok(p) if cfg!(windows) => {
|
|
|
|
base.join(p).to_string_lossy().replace("\\", "/")
|
|
|
|
}
|
2019-12-22 07:43:41 +00:00
|
|
|
Ok(p) => base.join(p).to_string_lossy().into_owned(),
|
2019-03-06 06:10:08 +00:00
|
|
|
Err(_) => continue,
|
|
|
|
};
|
|
|
|
|
|
|
|
// if file is a directory, add '/' to the end of the name
|
|
|
|
if let Ok(metadata) = entry.metadata() {
|
|
|
|
if metadata.is_dir() {
|
|
|
|
let _ = write!(
|
|
|
|
body,
|
|
|
|
"<li><a href=\"{}\">{}/</a></li>",
|
|
|
|
encode_file_url!(p),
|
|
|
|
encode_file_name!(entry),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
let _ = write!(
|
|
|
|
body,
|
|
|
|
"<li><a href=\"{}\">{}</a></li>",
|
|
|
|
encode_file_url!(p),
|
|
|
|
encode_file_name!(entry),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let html = format!(
|
|
|
|
"<html>\
|
|
|
|
<head><title>{}</title></head>\
|
|
|
|
<body><h1>{}</h1>\
|
|
|
|
<ul>\
|
|
|
|
{}\
|
|
|
|
</ul></body>\n</html>",
|
|
|
|
index_of, index_of, body
|
|
|
|
);
|
|
|
|
Ok(ServiceResponse::new(
|
|
|
|
req.clone(),
|
|
|
|
HttpResponse::Ok()
|
|
|
|
.content_type("text/html; charset=utf-8")
|
|
|
|
.body(html),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2020-09-10 13:46:35 +00:00
|
|
|
type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType;
|
2019-03-25 20:02:37 +00:00
|
|
|
|
2019-03-06 06:10:08 +00:00
|
|
|
/// Static files handling
|
|
|
|
///
|
2019-03-07 20:32:40 +00:00
|
|
|
/// `Files` service must be registered with `App::service()` method.
|
2019-03-06 06:10:08 +00:00
|
|
|
///
|
2019-03-06 17:27:02 +00:00
|
|
|
/// ```rust
|
|
|
|
/// use actix_web::App;
|
2020-09-10 13:46:35 +00:00
|
|
|
/// use actix_files::Files;
|
2019-03-06 06:10:08 +00:00
|
|
|
///
|
2020-09-10 13:46:35 +00:00
|
|
|
/// let app = App::new()
|
|
|
|
/// .service(Files::new("/static", "."));
|
2019-03-06 06:10:08 +00:00
|
|
|
/// ```
|
2019-04-13 21:50:54 +00:00
|
|
|
pub struct Files {
|
2019-03-06 23:47:15 +00:00
|
|
|
path: String,
|
2019-03-06 06:10:08 +00:00
|
|
|
directory: PathBuf,
|
|
|
|
index: Option<String>,
|
|
|
|
show_index: bool,
|
2019-10-14 15:23:15 +00:00
|
|
|
redirect_to_slash: bool,
|
2019-04-13 21:50:54 +00:00
|
|
|
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
|
2019-03-06 06:10:08 +00:00
|
|
|
renderer: Rc<DirectoryRenderer>,
|
2019-03-25 20:02:37 +00:00
|
|
|
mime_override: Option<Rc<MimeOverride>>,
|
|
|
|
file_flags: named::Flags,
|
2020-07-21 23:28:33 +00:00
|
|
|
// FIXME: Should re-visit later.
|
|
|
|
#[allow(clippy::redundant_allocation)]
|
2019-10-08 04:09:40 +00:00
|
|
|
guards: Option<Rc<Box<dyn Guard>>>,
|
2019-03-25 20:02:37 +00:00
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
impl Clone for Files {
|
2019-03-25 20:02:37 +00:00
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
directory: self.directory.clone(),
|
|
|
|
index: self.index.clone(),
|
|
|
|
show_index: self.show_index,
|
2019-10-14 15:23:15 +00:00
|
|
|
redirect_to_slash: self.redirect_to_slash,
|
2019-03-25 20:02:37 +00:00
|
|
|
default: self.default.clone(),
|
|
|
|
renderer: self.renderer.clone(),
|
|
|
|
file_flags: self.file_flags,
|
|
|
|
path: self.path.clone(),
|
|
|
|
mime_override: self.mime_override.clone(),
|
2019-10-08 04:09:40 +00:00
|
|
|
guards: self.guards.clone(),
|
2019-03-25 20:02:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
impl Files {
|
2019-03-07 07:39:08 +00:00
|
|
|
/// Create new `Files` instance for specified base directory.
|
2019-03-06 06:10:08 +00:00
|
|
|
///
|
2019-03-07 20:32:40 +00:00
|
|
|
/// `File` uses `ThreadPool` for blocking filesystem operations.
|
2019-03-06 06:10:08 +00:00
|
|
|
/// By default pool with 5x threads of available cpus is used.
|
2020-01-15 19:09:58 +00:00
|
|
|
/// Pool size can be changed by setting ACTIX_THREADPOOL environment variable.
|
2019-04-13 21:50:54 +00:00
|
|
|
pub fn new<T: Into<PathBuf>>(path: &str, dir: T) -> Files {
|
2019-12-12 01:04:53 +00:00
|
|
|
let orig_dir = dir.into();
|
|
|
|
let dir = match orig_dir.canonicalize() {
|
|
|
|
Ok(canon_dir) => canon_dir,
|
|
|
|
Err(_) => {
|
|
|
|
log::error!("Specified path is not a directory: {:?}", orig_dir);
|
|
|
|
PathBuf::new()
|
|
|
|
}
|
|
|
|
};
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-03-07 07:39:08 +00:00
|
|
|
Files {
|
2019-03-06 23:47:15 +00:00
|
|
|
path: path.to_string(),
|
2019-03-06 06:10:08 +00:00
|
|
|
directory: dir,
|
|
|
|
index: None,
|
|
|
|
show_index: false,
|
2019-10-14 15:23:15 +00:00
|
|
|
redirect_to_slash: false,
|
2019-03-06 06:10:08 +00:00
|
|
|
default: Rc::new(RefCell::new(None)),
|
|
|
|
renderer: Rc::new(directory_listing),
|
2019-03-25 20:02:37 +00:00
|
|
|
mime_override: None,
|
|
|
|
file_flags: named::Flags::default(),
|
2019-10-08 04:09:40 +00:00
|
|
|
guards: None,
|
2019-03-06 17:27:02 +00:00
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Show files listing for directories.
|
|
|
|
///
|
|
|
|
/// By default show files listing is disabled.
|
|
|
|
pub fn show_files_listing(mut self) -> Self {
|
|
|
|
self.show_index = true;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-10-14 15:23:15 +00:00
|
|
|
/// Redirects to a slash-ended path when browsing a directory.
|
|
|
|
///
|
|
|
|
/// By default never redirect.
|
|
|
|
pub fn redirect_to_slash_directory(mut self) -> Self {
|
|
|
|
self.redirect_to_slash = true;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-03-06 06:10:08 +00:00
|
|
|
/// Set custom directory renderer
|
|
|
|
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
|
|
|
|
where
|
2019-11-20 17:33:22 +00:00
|
|
|
for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result<ServiceResponse, io::Error>
|
|
|
|
+ 'static,
|
2019-03-06 06:10:08 +00:00
|
|
|
{
|
|
|
|
self.renderer = Rc::new(f);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-03-25 20:02:37 +00:00
|
|
|
/// Specifies mime override callback
|
2019-03-25 20:43:02 +00:00
|
|
|
pub fn mime_override<F>(mut self, f: F) -> Self
|
|
|
|
where
|
2020-09-10 13:46:35 +00:00
|
|
|
F: Fn(&mime::Name<'_>) -> DispositionType + 'static,
|
2019-03-25 20:43:02 +00:00
|
|
|
{
|
2019-03-25 20:02:37 +00:00
|
|
|
self.mime_override = Some(Rc::new(f));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-03-06 06:10:08 +00:00
|
|
|
/// Set index file
|
|
|
|
///
|
|
|
|
/// Shows specific index file for directory "/" instead of
|
|
|
|
/// showing files listing.
|
2019-03-25 20:02:37 +00:00
|
|
|
pub fn index_file<T: Into<String>>(mut self, index: T) -> Self {
|
2019-03-06 06:10:08 +00:00
|
|
|
self.index = Some(index.into());
|
|
|
|
self
|
|
|
|
}
|
2019-03-25 20:02:37 +00:00
|
|
|
|
|
|
|
#[inline]
|
2019-07-20 05:43:49 +00:00
|
|
|
/// Specifies whether to use ETag or not.
|
2019-03-25 20:02:37 +00:00
|
|
|
///
|
2019-07-20 05:43:49 +00:00
|
|
|
/// Default is true.
|
2019-03-25 20:02:37 +00:00
|
|
|
pub fn use_etag(mut self, value: bool) -> Self {
|
|
|
|
self.file_flags.set(named::Flags::ETAG, value);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2019-07-20 05:43:49 +00:00
|
|
|
/// Specifies whether to use Last-Modified or not.
|
2019-03-25 20:02:37 +00:00
|
|
|
///
|
2019-07-20 05:43:49 +00:00
|
|
|
/// Default is true.
|
2019-03-25 20:02:37 +00:00
|
|
|
pub fn use_last_modified(mut self, value: bool) -> Self {
|
|
|
|
self.file_flags.set(named::Flags::LAST_MD, value);
|
|
|
|
self
|
|
|
|
}
|
2019-04-01 01:19:18 +00:00
|
|
|
|
2019-10-08 04:09:40 +00:00
|
|
|
/// Specifies custom guards to use for directory listings and files.
|
|
|
|
///
|
|
|
|
/// Default behaviour allows GET and HEAD.
|
|
|
|
#[inline]
|
|
|
|
pub fn use_guards<G: Guard + 'static>(mut self, guards: G) -> Self {
|
|
|
|
self.guards = Some(Rc::new(Box::new(guards)));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-07-20 05:43:49 +00:00
|
|
|
/// Disable `Content-Disposition` header.
|
|
|
|
///
|
|
|
|
/// By default Content-Disposition` header is enabled.
|
|
|
|
#[inline]
|
|
|
|
pub fn disable_content_disposition(mut self) -> Self {
|
|
|
|
self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2019-04-01 01:19:18 +00:00
|
|
|
/// Sets default handler which is used when no matched file could be found.
|
|
|
|
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
|
|
|
where
|
2019-11-21 05:31:31 +00:00
|
|
|
F: IntoServiceFactory<U>,
|
|
|
|
U: ServiceFactory<
|
2019-05-12 15:34:51 +00:00
|
|
|
Config = (),
|
2019-04-13 21:50:54 +00:00
|
|
|
Request = ServiceRequest,
|
2019-04-01 01:19:18 +00:00
|
|
|
Response = ServiceResponse,
|
|
|
|
Error = Error,
|
|
|
|
> + 'static,
|
|
|
|
{
|
|
|
|
// create and configure default resource
|
2019-11-21 05:31:31 +00:00
|
|
|
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory(
|
|
|
|
f.into_factory().map_init_err(|_| ()),
|
2019-04-01 01:19:18 +00:00
|
|
|
)))));
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
impl HttpServiceFactory for Files {
|
2019-04-15 14:32:49 +00:00
|
|
|
fn register(self, config: &mut AppService) {
|
2019-03-06 23:47:15 +00:00
|
|
|
if self.default.borrow().is_none() {
|
|
|
|
*self.default.borrow_mut() = Some(config.default_service());
|
|
|
|
}
|
|
|
|
let rdef = if config.is_root() {
|
|
|
|
ResourceDef::root_prefix(&self.path)
|
|
|
|
} else {
|
|
|
|
ResourceDef::prefix(&self.path)
|
|
|
|
};
|
2019-03-09 22:06:24 +00:00
|
|
|
config.register_service(rdef, None, self, None)
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 05:31:31 +00:00
|
|
|
impl ServiceFactory for Files {
|
2019-04-13 21:50:54 +00:00
|
|
|
type Request = ServiceRequest;
|
2019-03-06 06:10:08 +00:00
|
|
|
type Response = ServiceResponse;
|
2019-03-10 23:39:46 +00:00
|
|
|
type Error = Error;
|
2019-10-14 15:23:15 +00:00
|
|
|
type Config = ();
|
2019-04-13 21:50:54 +00:00
|
|
|
type Service = FilesService;
|
2019-03-06 06:10:08 +00:00
|
|
|
type InitError = ();
|
2019-11-21 05:31:31 +00:00
|
|
|
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-12-02 15:37:13 +00:00
|
|
|
fn new_service(&self, _: ()) -> Self::Future {
|
2019-04-01 01:19:18 +00:00
|
|
|
let mut srv = FilesService {
|
|
|
|
directory: self.directory.clone(),
|
|
|
|
index: self.index.clone(),
|
|
|
|
show_index: self.show_index,
|
2019-10-14 15:23:15 +00:00
|
|
|
redirect_to_slash: self.redirect_to_slash,
|
2019-04-01 01:19:18 +00:00
|
|
|
default: None,
|
|
|
|
renderer: self.renderer.clone(),
|
|
|
|
mime_override: self.mime_override.clone(),
|
|
|
|
file_flags: self.file_flags,
|
2019-10-08 04:09:40 +00:00
|
|
|
guards: self.guards.clone(),
|
2019-04-01 01:19:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(ref default) = *self.default.borrow() {
|
2019-11-21 05:31:31 +00:00
|
|
|
default
|
2019-12-02 15:37:13 +00:00
|
|
|
.new_service(())
|
2019-11-21 05:31:31 +00:00
|
|
|
.map(move |result| match result {
|
|
|
|
Ok(default) => {
|
2019-04-01 01:19:18 +00:00
|
|
|
srv.default = Some(default);
|
2019-11-21 05:31:31 +00:00
|
|
|
Ok(srv)
|
|
|
|
}
|
|
|
|
Err(_) => Err(()),
|
|
|
|
})
|
|
|
|
.boxed_local()
|
2019-04-01 01:19:18 +00:00
|
|
|
} else {
|
2019-11-21 05:31:31 +00:00
|
|
|
ok(srv).boxed_local()
|
2019-04-01 01:19:18 +00:00
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
pub struct FilesService {
|
2019-04-01 01:19:18 +00:00
|
|
|
directory: PathBuf,
|
|
|
|
index: Option<String>,
|
|
|
|
show_index: bool,
|
2019-10-14 15:23:15 +00:00
|
|
|
redirect_to_slash: bool,
|
2019-04-13 21:50:54 +00:00
|
|
|
default: Option<HttpService>,
|
2019-04-01 01:19:18 +00:00
|
|
|
renderer: Rc<DirectoryRenderer>,
|
|
|
|
mime_override: Option<Rc<MimeOverride>>,
|
|
|
|
file_flags: named::Flags,
|
2020-07-21 23:28:33 +00:00
|
|
|
// FIXME: Should re-visit later.
|
|
|
|
#[allow(clippy::redundant_allocation)]
|
2019-10-08 04:09:40 +00:00
|
|
|
guards: Option<Rc<Box<dyn Guard>>>,
|
2019-04-01 01:19:18 +00:00
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
impl FilesService {
|
2020-09-10 13:46:35 +00:00
|
|
|
#[allow(clippy::type_complexity)]
|
2019-04-01 01:19:18 +00:00
|
|
|
fn handle_err(
|
|
|
|
&mut self,
|
|
|
|
e: io::Error,
|
2019-04-18 23:03:13 +00:00
|
|
|
req: ServiceRequest,
|
2019-04-01 01:19:18 +00:00
|
|
|
) -> Either<
|
2019-11-21 05:31:31 +00:00
|
|
|
Ready<Result<ServiceResponse, Error>>,
|
|
|
|
LocalBoxFuture<'static, Result<ServiceResponse, Error>>,
|
2019-04-01 01:19:18 +00:00
|
|
|
> {
|
|
|
|
log::debug!("Files: Failed to handle {}: {}", req.path(), e);
|
|
|
|
if let Some(ref mut default) = self.default {
|
2019-11-21 05:31:31 +00:00
|
|
|
Either::Right(default.call(req))
|
2019-04-01 01:19:18 +00:00
|
|
|
} else {
|
2019-11-21 05:31:31 +00:00
|
|
|
Either::Left(ok(req.error_response(e)))
|
2019-04-01 01:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
impl Service for FilesService {
|
|
|
|
type Request = ServiceRequest;
|
2019-03-06 06:10:08 +00:00
|
|
|
type Response = ServiceResponse;
|
2019-03-10 23:39:46 +00:00
|
|
|
type Error = Error;
|
2020-09-10 13:46:35 +00:00
|
|
|
#[allow(clippy::type_complexity)]
|
2019-04-01 01:19:18 +00:00
|
|
|
type Future = Either<
|
2019-11-21 05:31:31 +00:00
|
|
|
Ready<Result<Self::Response, Self::Error>>,
|
|
|
|
LocalBoxFuture<'static, Result<Self::Response, Self::Error>>,
|
2019-04-01 01:19:18 +00:00
|
|
|
>;
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2020-09-10 13:46:35 +00:00
|
|
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
2019-11-21 05:31:31 +00:00
|
|
|
Poll::Ready(Ok(()))
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
fn call(&mut self, req: ServiceRequest) -> Self::Future {
|
2019-10-08 04:09:40 +00:00
|
|
|
let is_method_valid = if let Some(guard) = &self.guards {
|
|
|
|
// execute user defined guards
|
|
|
|
(**guard).check(req.head())
|
|
|
|
} else {
|
2020-07-21 23:28:33 +00:00
|
|
|
// default behavior
|
|
|
|
matches!(*req.method(), Method::HEAD | Method::GET)
|
2019-10-08 04:09:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if !is_method_valid {
|
2019-11-21 05:31:31 +00:00
|
|
|
return Either::Left(ok(req.into_response(
|
2019-10-08 04:09:40 +00:00
|
|
|
actix_web::HttpResponse::MethodNotAllowed()
|
|
|
|
.header(header::CONTENT_TYPE, "text/plain")
|
2019-10-08 04:13:16 +00:00
|
|
|
.body("Request did not meet this resource's requirements."),
|
2019-10-08 04:09:40 +00:00
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2019-03-09 22:06:24 +00:00
|
|
|
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
|
2019-03-06 06:10:08 +00:00
|
|
|
Ok(item) => item,
|
2019-11-21 05:31:31 +00:00
|
|
|
Err(e) => return Either::Left(ok(req.error_response(e))),
|
2019-03-06 06:10:08 +00:00
|
|
|
};
|
|
|
|
|
2020-04-21 03:09:35 +00:00
|
|
|
// full file path
|
2019-03-06 06:10:08 +00:00
|
|
|
let path = match self.directory.join(&real_path.0).canonicalize() {
|
|
|
|
Ok(path) => path,
|
2019-04-18 23:03:13 +00:00
|
|
|
Err(e) => return self.handle_err(e, req),
|
2019-03-06 06:10:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if path.is_dir() {
|
|
|
|
if let Some(ref redir_index) = self.index {
|
2019-10-14 15:23:15 +00:00
|
|
|
if self.redirect_to_slash && !req.path().ends_with('/') {
|
|
|
|
let redirect_to = format!("{}/", req.path());
|
2019-11-21 05:31:31 +00:00
|
|
|
return Either::Left(ok(req.into_response(
|
2019-10-14 15:23:15 +00:00
|
|
|
HttpResponse::Found()
|
|
|
|
.header(header::LOCATION, redirect_to)
|
|
|
|
.body("")
|
|
|
|
.into_body(),
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2019-03-06 06:10:08 +00:00
|
|
|
let path = path.join(redir_index);
|
|
|
|
|
2019-03-25 20:02:37 +00:00
|
|
|
match NamedFile::open(path) {
|
|
|
|
Ok(mut named_file) => {
|
|
|
|
if let Some(ref mime_override) = self.mime_override {
|
2019-03-25 20:43:02 +00:00
|
|
|
let new_disposition =
|
|
|
|
mime_override(&named_file.content_type.type_());
|
2019-03-25 20:02:37 +00:00
|
|
|
named_file.content_disposition.disposition = new_disposition;
|
|
|
|
}
|
|
|
|
|
|
|
|
named_file.flags = self.file_flags;
|
2019-04-18 23:03:13 +00:00
|
|
|
let (req, _) = req.into_parts();
|
2019-11-21 05:31:31 +00:00
|
|
|
Either::Left(ok(match named_file.into_response(&req) {
|
2019-04-18 23:03:13 +00:00
|
|
|
Ok(item) => ServiceResponse::new(req, item),
|
|
|
|
Err(e) => ServiceResponse::from_err(e, req),
|
2019-04-01 01:19:18 +00:00
|
|
|
}))
|
2019-03-25 20:43:02 +00:00
|
|
|
}
|
2019-07-17 09:48:37 +00:00
|
|
|
Err(e) => self.handle_err(e, req),
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
} else if self.show_index {
|
|
|
|
let dir = Directory::new(self.directory.clone(), path);
|
2019-04-18 23:03:13 +00:00
|
|
|
let (req, _) = req.into_parts();
|
2019-03-06 06:10:08 +00:00
|
|
|
let x = (self.renderer)(&dir, &req);
|
|
|
|
match x {
|
2019-11-21 05:31:31 +00:00
|
|
|
Ok(resp) => Either::Left(ok(resp)),
|
|
|
|
Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-11-21 05:31:31 +00:00
|
|
|
Either::Left(ok(ServiceResponse::from_err(
|
2019-03-07 07:39:08 +00:00
|
|
|
FilesError::IsDirectory,
|
2019-04-18 23:03:13 +00:00
|
|
|
req.into_parts().0,
|
2019-04-01 01:19:18 +00:00
|
|
|
)))
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-03-25 20:02:37 +00:00
|
|
|
match NamedFile::open(path) {
|
|
|
|
Ok(mut named_file) => {
|
|
|
|
if let Some(ref mime_override) = self.mime_override {
|
2019-03-25 20:43:02 +00:00
|
|
|
let new_disposition =
|
|
|
|
mime_override(&named_file.content_type.type_());
|
2019-03-25 20:02:37 +00:00
|
|
|
named_file.content_disposition.disposition = new_disposition;
|
|
|
|
}
|
|
|
|
|
|
|
|
named_file.flags = self.file_flags;
|
2019-04-18 23:03:13 +00:00
|
|
|
let (req, _) = req.into_parts();
|
2019-11-21 05:31:31 +00:00
|
|
|
match named_file.into_response(&req) {
|
2019-04-01 01:19:18 +00:00
|
|
|
Ok(item) => {
|
2019-11-21 05:31:31 +00:00
|
|
|
Either::Left(ok(ServiceResponse::new(req.clone(), item)))
|
2019-04-01 01:19:18 +00:00
|
|
|
}
|
2019-11-21 05:31:31 +00:00
|
|
|
Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))),
|
2019-03-25 20:02:37 +00:00
|
|
|
}
|
2019-03-25 20:43:02 +00:00
|
|
|
}
|
2019-04-18 23:03:13 +00:00
|
|
|
Err(e) => self.handle_err(e, req),
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-09 22:06:24 +00:00
|
|
|
#[derive(Debug)]
|
2019-03-06 06:10:08 +00:00
|
|
|
struct PathBufWrp(PathBuf);
|
|
|
|
|
|
|
|
impl PathBufWrp {
|
2019-03-09 22:06:24 +00:00
|
|
|
fn get_pathbuf(path: &str) -> Result<Self, UriSegmentError> {
|
2019-03-06 06:10:08 +00:00
|
|
|
let mut buf = PathBuf::new();
|
2019-03-09 22:06:24 +00:00
|
|
|
for segment in path.split('/') {
|
2019-03-06 06:10:08 +00:00
|
|
|
if segment == ".." {
|
|
|
|
buf.pop();
|
|
|
|
} else if segment.starts_with('.') {
|
|
|
|
return Err(UriSegmentError::BadStart('.'));
|
|
|
|
} else if segment.starts_with('*') {
|
|
|
|
return Err(UriSegmentError::BadStart('*'));
|
|
|
|
} else if segment.ends_with(':') {
|
|
|
|
return Err(UriSegmentError::BadEnd(':'));
|
|
|
|
} else if segment.ends_with('>') {
|
|
|
|
return Err(UriSegmentError::BadEnd('>'));
|
|
|
|
} else if segment.ends_with('<') {
|
|
|
|
return Err(UriSegmentError::BadEnd('<'));
|
|
|
|
} else if segment.is_empty() {
|
|
|
|
continue;
|
|
|
|
} else if cfg!(windows) && segment.contains('\\') {
|
|
|
|
return Err(UriSegmentError::BadChar('\\'));
|
|
|
|
} else {
|
|
|
|
buf.push(segment)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(PathBufWrp(buf))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
impl FromRequest for PathBufWrp {
|
2019-03-06 06:10:08 +00:00
|
|
|
type Error = UriSegmentError;
|
2019-11-21 05:31:31 +00:00
|
|
|
type Future = Ready<Result<Self, Self::Error>>;
|
2019-04-13 23:35:25 +00:00
|
|
|
type Config = ();
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-04-13 21:50:54 +00:00
|
|
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
2019-11-21 05:31:31 +00:00
|
|
|
ready(PathBufWrp::get_pathbuf(req.match_info().path()))
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use std::fs;
|
2019-03-09 22:06:24 +00:00
|
|
|
use std::iter::FromIterator;
|
2019-03-06 06:10:08 +00:00
|
|
|
use std::ops::Add;
|
|
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
|
|
|
|
use super::*;
|
2019-10-08 04:09:40 +00:00
|
|
|
use actix_web::guard;
|
2019-04-01 17:26:09 +00:00
|
|
|
use actix_web::http::header::{
|
|
|
|
self, ContentDisposition, DispositionParam, DispositionType,
|
|
|
|
};
|
|
|
|
use actix_web::http::{Method, StatusCode};
|
2019-04-13 21:50:54 +00:00
|
|
|
use actix_web::middleware::Compress;
|
2019-11-26 05:25:50 +00:00
|
|
|
use actix_web::test::{self, TestRequest};
|
2019-11-21 05:31:31 +00:00
|
|
|
use actix_web::{App, Responder};
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_file_extension_to_mime() {
|
2019-03-06 06:10:08 +00:00
|
|
|
let m = file_extension_to_mime("jpg");
|
|
|
|
assert_eq!(m, mime::IMAGE_JPEG);
|
|
|
|
|
|
|
|
let m = file_extension_to_mime("invalid extension!!");
|
|
|
|
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
|
|
|
|
|
|
|
let m = file_extension_to_mime("");
|
|
|
|
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
|
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_if_modified_since_without_if_none_match() {
|
|
|
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
|
|
|
let since =
|
|
|
|
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
|
|
|
|
|
|
|
let req = TestRequest::default()
|
|
|
|
.header(header::IF_MODIFIED_SINCE, since)
|
|
|
|
.to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(resp.status(), StatusCode::NOT_MODIFIED);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_if_modified_since_with_if_none_match() {
|
|
|
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
|
|
|
let since =
|
|
|
|
header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60)));
|
|
|
|
|
|
|
|
let req = TestRequest::default()
|
|
|
|
.header(header::IF_NONE_MATCH, "miss_etag")
|
|
|
|
.header(header::IF_MODIFIED_SINCE, since)
|
|
|
|
.to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_ne!(resp.status(), StatusCode::NOT_MODIFIED);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_text() {
|
|
|
|
assert!(NamedFile::open("test--").is_err());
|
|
|
|
let mut file = NamedFile::open("Cargo.toml").unwrap();
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"text/x-toml"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"inline; filename=\"Cargo.toml\""
|
|
|
|
);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_content_disposition() {
|
|
|
|
assert!(NamedFile::open("test--").is_err());
|
|
|
|
let mut file = NamedFile::open("Cargo.toml").unwrap();
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-07-20 05:43:49 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"inline; filename=\"Cargo.toml\""
|
|
|
|
);
|
2019-07-20 05:43:49 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let file = NamedFile::open("Cargo.toml")
|
|
|
|
.unwrap()
|
|
|
|
.disable_content_disposition();
|
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
|
2019-07-20 05:43:49 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_non_ascii_file_name() {
|
|
|
|
let mut file =
|
|
|
|
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml")
|
|
|
|
.unwrap();
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-11-06 14:08:37 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"text/x-toml"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-11-06 14:08:37 +00:00
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_set_content_type() {
|
|
|
|
let mut file = NamedFile::open("Cargo.toml")
|
|
|
|
.unwrap()
|
|
|
|
.set_content_type(mime::TEXT_XML);
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"text/xml"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"inline; filename=\"Cargo.toml\""
|
|
|
|
);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_image() {
|
|
|
|
let mut file = NamedFile::open("tests/test.png").unwrap();
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"image/png"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"inline; filename=\"test.png\""
|
|
|
|
);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_image_attachment() {
|
|
|
|
let cd = ContentDisposition {
|
|
|
|
disposition: DispositionType::Attachment,
|
|
|
|
parameters: vec![DispositionParam::Filename(String::from("test.png"))],
|
|
|
|
};
|
|
|
|
let mut file = NamedFile::open("tests/test.png")
|
|
|
|
.unwrap()
|
|
|
|
.set_content_disposition(cd);
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"image/png"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"attachment; filename=\"test.png\""
|
|
|
|
);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_binary() {
|
|
|
|
let mut file = NamedFile::open("tests/test.binary").unwrap();
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"application/octet-stream"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"attachment; filename=\"test.binary\""
|
|
|
|
);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_status_code_text() {
|
|
|
|
let mut file = NamedFile::open("Cargo.toml")
|
|
|
|
.unwrap()
|
|
|
|
.set_status_code(StatusCode::NOT_FOUND);
|
|
|
|
{
|
|
|
|
file.file();
|
|
|
|
let _f: &File = &file;
|
|
|
|
}
|
|
|
|
{
|
|
|
|
let _f: &mut File = &mut file;
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let req = TestRequest::default().to_http_request();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"text/x-toml"
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
"inline; filename=\"Cargo.toml\""
|
|
|
|
);
|
|
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_mime_override() {
|
2020-09-10 13:46:35 +00:00
|
|
|
fn all_attachment(_: &mime::Name<'_>) -> DispositionType {
|
2019-11-26 05:25:50 +00:00
|
|
|
DispositionType::Attachment
|
|
|
|
}
|
2019-03-25 20:02:37 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(
|
|
|
|
Files::new("/", ".")
|
|
|
|
.mime_override(all_attachment)
|
|
|
|
.index_file("Cargo.toml"),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let request = TestRequest::get().uri("/").to_request();
|
|
|
|
let response = test::call_service(&mut srv, request).await;
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
|
|
|
|
let content_disposition = response
|
|
|
|
.headers()
|
|
|
|
.get(header::CONTENT_DISPOSITION)
|
|
|
|
.expect("To have CONTENT_DISPOSITION");
|
|
|
|
let content_disposition = content_disposition
|
|
|
|
.to_str()
|
|
|
|
.expect("Convert CONTENT_DISPOSITION to str");
|
|
|
|
assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\"");
|
2019-03-25 20:02:37 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_ranges_status_code() {
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(Files::new("/test", ".").index_file("Cargo.toml")),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
|
|
|
// Valid range header
|
|
|
|
let request = TestRequest::get()
|
|
|
|
.uri("/t%65st/Cargo.toml")
|
|
|
|
.header(header::RANGE, "bytes=10-20")
|
|
|
|
.to_request();
|
|
|
|
let response = test::call_service(&mut srv, request).await;
|
|
|
|
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
|
|
|
|
|
|
|
|
// Invalid range header
|
|
|
|
let request = TestRequest::get()
|
|
|
|
.uri("/t%65st/Cargo.toml")
|
|
|
|
.header(header::RANGE, "bytes=1-0")
|
|
|
|
.to_request();
|
|
|
|
let response = test::call_service(&mut srv, request).await;
|
|
|
|
|
|
|
|
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_content_range_headers() {
|
2020-07-21 23:28:33 +00:00
|
|
|
let srv = test::start(|| App::new().service(Files::new("/", ".")));
|
2019-11-26 05:25:50 +00:00
|
|
|
|
|
|
|
// Valid range header
|
2020-05-17 21:54:42 +00:00
|
|
|
let response = srv
|
|
|
|
.get("/tests/test.binary")
|
2019-11-26 05:25:50 +00:00
|
|
|
.header(header::RANGE, "bytes=10-20")
|
2020-05-17 21:54:42 +00:00
|
|
|
.send()
|
|
|
|
.await
|
2019-11-26 05:25:50 +00:00
|
|
|
.unwrap();
|
2020-05-17 21:54:42 +00:00
|
|
|
let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
|
|
|
|
assert_eq!(content_range.to_str().unwrap(), "bytes 10-20/100");
|
2019-11-26 05:25:50 +00:00
|
|
|
|
|
|
|
// Invalid range header
|
2020-05-17 21:54:42 +00:00
|
|
|
let response = srv
|
|
|
|
.get("/tests/test.binary")
|
2019-11-26 05:25:50 +00:00
|
|
|
.header(header::RANGE, "bytes=10-5")
|
2020-05-17 21:54:42 +00:00
|
|
|
.send()
|
|
|
|
.await
|
2019-11-26 05:25:50 +00:00
|
|
|
.unwrap();
|
2020-05-17 21:54:42 +00:00
|
|
|
let content_range = response.headers().get(header::CONTENT_RANGE).unwrap();
|
|
|
|
assert_eq!(content_range.to_str().unwrap(), "bytes */100");
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_content_length_headers() {
|
2020-07-21 23:28:33 +00:00
|
|
|
let srv = test::start(|| App::new().service(Files::new("/", ".")));
|
2019-11-26 05:25:50 +00:00
|
|
|
|
|
|
|
// Valid range header
|
2020-05-17 21:54:42 +00:00
|
|
|
let response = srv
|
|
|
|
.get("/tests/test.binary")
|
2019-11-26 05:25:50 +00:00
|
|
|
.header(header::RANGE, "bytes=10-20")
|
2020-05-17 21:54:42 +00:00
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
|
|
|
|
assert_eq!(content_length.to_str().unwrap(), "11");
|
|
|
|
|
|
|
|
// Valid range header, starting from 0
|
|
|
|
let response = srv
|
|
|
|
.get("/tests/test.binary")
|
|
|
|
.header(header::RANGE, "bytes=0-20")
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
|
|
|
|
assert_eq!(content_length.to_str().unwrap(), "21");
|
2019-11-26 05:25:50 +00:00
|
|
|
|
|
|
|
// Without range header
|
2020-05-17 21:54:42 +00:00
|
|
|
let mut response = srv.get("/tests/test.binary").send().await.unwrap();
|
|
|
|
let content_length = response.headers().get(header::CONTENT_LENGTH).unwrap();
|
|
|
|
assert_eq!(content_length.to_str().unwrap(), "100");
|
2019-11-26 05:25:50 +00:00
|
|
|
|
2020-05-17 21:54:42 +00:00
|
|
|
// Should be no transfer-encoding
|
|
|
|
let transfer_encoding = response.headers().get(header::TRANSFER_ENCODING);
|
|
|
|
assert!(transfer_encoding.is_none());
|
2019-11-26 05:25:50 +00:00
|
|
|
|
2020-05-17 21:54:42 +00:00
|
|
|
// Check file contents
|
|
|
|
let bytes = response.body().await.unwrap();
|
2019-11-26 05:25:50 +00:00
|
|
|
let data = Bytes::from(fs::read("tests/test.binary").unwrap());
|
|
|
|
assert_eq!(bytes, data);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_head_content_length_headers() {
|
2020-07-21 23:28:33 +00:00
|
|
|
let srv = test::start(|| App::new().service(Files::new("/", ".")));
|
2020-05-17 21:54:42 +00:00
|
|
|
|
2020-07-21 23:28:33 +00:00
|
|
|
let response = srv.head("/tests/test.binary").send().await.unwrap();
|
2019-11-26 05:25:50 +00:00
|
|
|
|
2020-05-17 21:54:42 +00:00
|
|
|
let content_length = response
|
|
|
|
.headers()
|
|
|
|
.get(header::CONTENT_LENGTH)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(content_length, "100");
|
2019-06-13 09:27:21 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_static_files_with_spaces() {
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(Files::new("/", ".").index_file("Cargo.toml")),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
let request = TestRequest::get()
|
|
|
|
.uri("/tests/test%20space.binary")
|
|
|
|
.to_request();
|
|
|
|
let response = test::call_service(&mut srv, request).await;
|
|
|
|
assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
|
|
|
|
let bytes = test::read_body(response).await;
|
|
|
|
let data = Bytes::from(fs::read("tests/test space.binary").unwrap());
|
|
|
|
assert_eq!(bytes, data);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_files_not_allowed() {
|
|
|
|
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
|
|
|
|
|
|
|
let req = TestRequest::default()
|
|
|
|
.uri("/Cargo.toml")
|
|
|
|
.method(Method::POST)
|
|
|
|
.to_request();
|
|
|
|
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
|
|
|
|
|
|
|
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
|
|
|
let req = TestRequest::default()
|
|
|
|
.method(Method::PUT)
|
|
|
|
.uri("/Cargo.toml")
|
|
|
|
.to_request();
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_files_guards() {
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(Files::new("/", ".").use_guards(guard::Post())),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let req = TestRequest::default()
|
|
|
|
.uri("/Cargo.toml")
|
|
|
|
.method(Method::POST)
|
|
|
|
.to_request();
|
|
|
|
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
2019-10-08 04:09:40 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_content_encoding() {
|
|
|
|
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
2020-07-21 23:28:33 +00:00
|
|
|
web::resource("/").to(|| async {
|
|
|
|
NamedFile::open("Cargo.toml")
|
|
|
|
.unwrap()
|
|
|
|
.set_content_encoding(header::ContentEncoding::Identity)
|
2019-11-26 05:25:50 +00:00
|
|
|
}),
|
|
|
|
))
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let request = TestRequest::get()
|
|
|
|
.uri("/")
|
|
|
|
.header(header::ACCEPT_ENCODING, "gzip")
|
|
|
|
.to_request();
|
|
|
|
let res = test::call_service(&mut srv, request).await;
|
|
|
|
assert_eq!(res.status(), StatusCode::OK);
|
|
|
|
assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
|
2019-04-04 21:00:56 +00:00
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_content_encoding_gzip() {
|
|
|
|
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
|
2020-07-21 23:28:33 +00:00
|
|
|
web::resource("/").to(|| async {
|
|
|
|
NamedFile::open("Cargo.toml")
|
|
|
|
.unwrap()
|
|
|
|
.set_content_encoding(header::ContentEncoding::Gzip)
|
2019-11-26 05:25:50 +00:00
|
|
|
}),
|
|
|
|
))
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let request = TestRequest::get()
|
|
|
|
.uri("/")
|
|
|
|
.header(header::ACCEPT_ENCODING, "gzip")
|
|
|
|
.to_request();
|
|
|
|
let res = test::call_service(&mut srv, request).await;
|
|
|
|
assert_eq!(res.status(), StatusCode::OK);
|
|
|
|
assert_eq!(
|
|
|
|
res.headers()
|
|
|
|
.get(header::CONTENT_ENCODING)
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap(),
|
|
|
|
"gzip"
|
|
|
|
);
|
2019-04-01 01:19:18 +00:00
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_named_file_allowed_method() {
|
|
|
|
let req = TestRequest::default().method(Method::GET).to_http_request();
|
|
|
|
let file = NamedFile::open("Cargo.toml").unwrap();
|
|
|
|
let resp = file.respond_to(&req).await.unwrap();
|
|
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_static_files() {
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(Files::new("/", ".").show_files_listing()),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
let req = TestRequest::with_uri("/missing").to_request();
|
|
|
|
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
|
|
|
|
|
|
let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await;
|
|
|
|
|
|
|
|
let req = TestRequest::default().to_request();
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
|
|
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(Files::new("/", ".").show_files_listing()),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
let req = TestRequest::with_uri("/tests").to_request();
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(
|
|
|
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
"text/html; charset=utf-8"
|
|
|
|
);
|
2019-11-21 05:31:31 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let bytes = test::read_body(resp).await;
|
|
|
|
assert!(format!("{:?}", bytes).contains("/tests/test.png"));
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_redirect_to_slash_directory() {
|
|
|
|
// should not redirect if no index
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
let req = TestRequest::with_uri("/tests").to_request();
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
|
|
|
|
|
|
// should redirect if index present
|
|
|
|
let mut srv = test::init_service(
|
|
|
|
App::new().service(
|
|
|
|
Files::new("/", ".")
|
|
|
|
.index_file("test.png")
|
|
|
|
.redirect_to_slash_directory(),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
let req = TestRequest::with_uri("/tests").to_request();
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::FOUND);
|
|
|
|
|
|
|
|
// should not redirect if the path is wrong
|
|
|
|
let req = TestRequest::with_uri("/not_existing").to_request();
|
|
|
|
let resp = test::call_service(&mut srv, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
2019-10-14 15:23:15 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_static_files_bad_directory() {
|
2019-04-13 21:50:54 +00:00
|
|
|
let _st: Files = Files::new("/", "missing");
|
|
|
|
let _st: Files = Files::new("/", "Cargo.toml");
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_default_handler_file_missing() {
|
|
|
|
let mut st = Files::new("/", ".")
|
|
|
|
.default_handler(|req: ServiceRequest| {
|
|
|
|
ok(req.into_response(HttpResponse::Ok().body("default content")))
|
|
|
|
})
|
2019-12-02 15:37:13 +00:00
|
|
|
.new_service(())
|
2019-11-26 05:25:50 +00:00
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let req = TestRequest::with_uri("/missing").to_srv_request();
|
|
|
|
|
|
|
|
let resp = test::call_service(&mut st, req).await;
|
|
|
|
assert_eq!(resp.status(), StatusCode::OK);
|
|
|
|
let bytes = test::read_body(resp).await;
|
|
|
|
assert_eq!(bytes, Bytes::from_static(b"default content"));
|
2019-04-01 01:19:18 +00:00
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
// #[actix_rt::test]
|
|
|
|
// async fn test_serve_index() {
|
2019-03-07 07:39:08 +00:00
|
|
|
// let st = Files::new(".").index_file("test.binary");
|
2019-03-06 06:10:08 +00:00
|
|
|
// let req = TestRequest::default().uri("/tests").finish();
|
|
|
|
|
|
|
|
// let resp = st.handle(&req).respond_to(&req).unwrap();
|
|
|
|
// let resp = resp.as_msg();
|
|
|
|
// assert_eq!(resp.status(), StatusCode::OK);
|
|
|
|
// assert_eq!(
|
|
|
|
// resp.headers()
|
|
|
|
// .get(header::CONTENT_TYPE)
|
|
|
|
// .expect("content type"),
|
|
|
|
// "application/octet-stream"
|
|
|
|
// );
|
|
|
|
// assert_eq!(
|
|
|
|
// resp.headers()
|
|
|
|
// .get(header::CONTENT_DISPOSITION)
|
|
|
|
// .expect("content disposition"),
|
|
|
|
// "attachment; filename=\"test.binary\""
|
|
|
|
// );
|
|
|
|
|
|
|
|
// let req = TestRequest::default().uri("/tests/").finish();
|
|
|
|
// let resp = st.handle(&req).respond_to(&req).unwrap();
|
|
|
|
// let resp = resp.as_msg();
|
|
|
|
// assert_eq!(resp.status(), StatusCode::OK);
|
|
|
|
// assert_eq!(
|
|
|
|
// resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
// "application/octet-stream"
|
|
|
|
// );
|
|
|
|
// assert_eq!(
|
|
|
|
// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
// "attachment; filename=\"test.binary\""
|
|
|
|
// );
|
|
|
|
|
|
|
|
// // nonexistent index file
|
|
|
|
// let req = TestRequest::default().uri("/tests/unknown").finish();
|
|
|
|
// let resp = st.handle(&req).respond_to(&req).unwrap();
|
|
|
|
// let resp = resp.as_msg();
|
|
|
|
// assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
|
|
|
|
|
|
// let req = TestRequest::default().uri("/tests/unknown/").finish();
|
|
|
|
// let resp = st.handle(&req).respond_to(&req).unwrap();
|
|
|
|
// let resp = resp.as_msg();
|
|
|
|
// assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
|
|
|
// }
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
// #[actix_rt::test]
|
|
|
|
// async fn test_serve_index_nested() {
|
2019-03-07 07:39:08 +00:00
|
|
|
// let st = Files::new(".").index_file("mod.rs");
|
2019-03-06 06:10:08 +00:00
|
|
|
// let req = TestRequest::default().uri("/src/client").finish();
|
|
|
|
// let resp = st.handle(&req).respond_to(&req).unwrap();
|
|
|
|
// let resp = resp.as_msg();
|
|
|
|
// assert_eq!(resp.status(), StatusCode::OK);
|
|
|
|
// assert_eq!(
|
|
|
|
// resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
|
|
|
// "text/x-rust"
|
|
|
|
// );
|
|
|
|
// assert_eq!(
|
|
|
|
// resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
|
|
|
// "inline; filename=\"mod.rs\""
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
// #[actix_rt::test]
|
2019-03-06 06:10:08 +00:00
|
|
|
// fn integration_serve_index() {
|
|
|
|
// let mut srv = test::TestServer::with_factory(|| {
|
|
|
|
// App::new().handler(
|
|
|
|
// "test",
|
2019-03-07 07:39:08 +00:00
|
|
|
// Files::new(".").index_file("Cargo.toml"),
|
2019-03-06 06:10:08 +00:00
|
|
|
// )
|
|
|
|
// });
|
|
|
|
|
|
|
|
// let request = srv.get().uri(srv.url("/test")).finish().unwrap();
|
|
|
|
// let response = srv.execute(request.send()).unwrap();
|
|
|
|
// assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
// let bytes = srv.execute(response.body()).unwrap();
|
|
|
|
// let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
|
|
|
// assert_eq!(bytes, data);
|
|
|
|
|
|
|
|
// let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
|
|
|
|
// let response = srv.execute(request.send()).unwrap();
|
|
|
|
// assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
// let bytes = srv.execute(response.body()).unwrap();
|
|
|
|
// let data = Bytes::from(fs::read("Cargo.toml").unwrap());
|
|
|
|
// assert_eq!(bytes, data);
|
|
|
|
|
|
|
|
// // nonexistent index file
|
|
|
|
// let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap();
|
|
|
|
// let response = srv.execute(request.send()).unwrap();
|
|
|
|
// assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
|
|
|
|
|
|
// let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap();
|
|
|
|
// let response = srv.execute(request.send()).unwrap();
|
|
|
|
// assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
|
|
|
// }
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
// #[actix_rt::test]
|
2019-03-06 06:10:08 +00:00
|
|
|
// fn integration_percent_encoded() {
|
|
|
|
// let mut srv = test::TestServer::with_factory(|| {
|
|
|
|
// App::new().handler(
|
|
|
|
// "test",
|
2019-03-07 07:39:08 +00:00
|
|
|
// Files::new(".").index_file("Cargo.toml"),
|
2019-03-06 06:10:08 +00:00
|
|
|
// )
|
|
|
|
// });
|
|
|
|
|
|
|
|
// let request = srv
|
|
|
|
// .get()
|
|
|
|
// .uri(srv.url("/test/%43argo.toml"))
|
|
|
|
// .finish()
|
|
|
|
// .unwrap();
|
|
|
|
// let response = srv.execute(request.send()).unwrap();
|
|
|
|
// assert_eq!(response.status(), StatusCode::OK);
|
|
|
|
// }
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_path_buf() {
|
2019-03-09 15:39:34 +00:00
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/test/.tt").map(|t| t.0),
|
2019-03-09 15:39:34 +00:00
|
|
|
Err(UriSegmentError::BadStart('.'))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/test/*tt").map(|t| t.0),
|
2019-03-09 15:39:34 +00:00
|
|
|
Err(UriSegmentError::BadStart('*'))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/test/tt:").map(|t| t.0),
|
2019-03-09 15:39:34 +00:00
|
|
|
Err(UriSegmentError::BadEnd(':'))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/test/tt<").map(|t| t.0),
|
2019-03-09 15:39:34 +00:00
|
|
|
Err(UriSegmentError::BadEnd('<'))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/test/tt>").map(|t| t.0),
|
2019-03-09 15:39:34 +00:00
|
|
|
Err(UriSegmentError::BadEnd('>'))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/seg1/seg2/").unwrap().0,
|
|
|
|
PathBuf::from_iter(vec!["seg1", "seg2"])
|
2019-03-09 15:39:34 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
2019-03-09 22:06:24 +00:00
|
|
|
PathBufWrp::get_pathbuf("/seg1/../seg2/").unwrap().0,
|
|
|
|
PathBuf::from_iter(vec!["seg2"])
|
2019-03-09 15:39:34 +00:00
|
|
|
);
|
|
|
|
}
|
2019-03-06 06:10:08 +00:00
|
|
|
}
|