aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/auth.rs105
-rw-r--r--src/errors.rs2
-rw-r--r--src/file_upload.rs139
-rw-r--r--src/listing.rs140
-rw-r--r--src/main.rs103
-rw-r--r--src/pipe.rs22
6 files changed, 283 insertions, 228 deletions
diff --git a/src/auth.rs b/src/auth.rs
index 6081a9d..31826d8 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,6 +1,7 @@
+use actix_web::dev::{Body, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::http::{header, StatusCode};
-use actix_web::middleware::{Middleware, Response};
use actix_web::{HttpRequest, HttpResponse, Result};
+use futures::future;
use sha2::{Digest, Sha256, Sha512};
use crate::errors::{self, ContextualError};
@@ -87,16 +88,61 @@ pub fn get_hash<T: Digest>(text: &str) -> Vec<u8> {
hasher.finalize().to_vec()
}
-impl Middleware<crate::MiniserveConfig> for Auth {
- fn response(
- &self,
- req: &HttpRequest<crate::MiniserveConfig>,
- resp: HttpResponse,
- ) -> Result<Response> {
- let required_auth = &req.state().auth;
+pub struct AuthMiddleware<S> {
+ service: S,
+}
+
+impl<S> Transform<S> for Auth
+where
+ S: Service<
+ Request = ServiceRequest,
+ Response = ServiceResponse<Body>,
+ Error = actix_web::Error,
+ >,
+ S::Future: 'static,
+{
+ type Request = ServiceRequest;
+ type Response = ServiceResponse<Body>;
+ type Error = S::Error;
+ type Future = future::Ready<Result<Self::Transform, Self::InitError>>;
+ type Transform = AuthMiddleware<S>;
+ type InitError = ();
+ fn new_transform(&self, service: S) -> Self::Future {
+ future::ok(AuthMiddleware { service })
+ }
+}
+
+impl<S> Service for AuthMiddleware<S>
+where
+ S: Service<
+ Request = ServiceRequest,
+ Response = ServiceResponse<Body>,
+ Error = actix_web::Error,
+ >,
+ S::Future: 'static,
+{
+ type Request = ServiceRequest;
+ type Response = ServiceResponse<Body>;
+ type Error = S::Error;
+ type Future =
+ std::pin::Pin<Box<dyn future::Future<Output = Result<Self::Response, Self::Error>>>>;
+
+ fn poll_ready(
+ &mut self,
+ cx: &mut std::task::Context,
+ ) -> std::task::Poll<Result<(), Self::Error>> {
+ self.service.poll_ready(cx)
+ }
+
+ fn call(&mut self, req: Self::Request) -> Self::Future {
+ let (req, pl) = req.into_parts();
+ let required_auth = &req.app_data::<crate::MiniserveConfig>().unwrap().auth;
if required_auth.is_empty() {
- return Ok(Response::Done(resp));
+ let resp = self
+ .service
+ .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!()));
+ return Box::pin(async { resp.await });
}
if let Some(auth_headers) = req.headers().get(header::AUTHORIZATION) {
@@ -104,30 +150,38 @@ impl Middleware<crate::MiniserveConfig> for Auth {
Ok(auth_req) => auth_req,
Err(err) => {
let auth_err = ContextualError::HTTPAuthenticationError(Box::new(err));
- return Ok(Response::Done(HttpResponse::BadRequest().body(
- build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST),
+ let body =
+ build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST);
+ return Box::pin(future::ok(ServiceResponse::new(
+ req,
+ HttpResponse::BadRequest().body(body),
)));
}
};
if match_auth(auth_req, required_auth) {
- return Ok(Response::Done(resp));
+ let resp = self
+ .service
+ .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!()));
+ return Box::pin(async { resp.await });
}
}
- Ok(Response::Done(
+ let body = build_unauthorized_response(
+ &req,
+ ContextualError::InvalidHTTPCredentials,
+ true,
+ StatusCode::UNAUTHORIZED,
+ );
+ Box::pin(future::ok(ServiceResponse::new(
+ req,
HttpResponse::Unauthorized()
.header(
header::WWW_AUTHENTICATE,
header::HeaderValue::from_static("Basic realm=\"miniserve\""),
)
- .body(build_unauthorized_response(
- &req,
- ContextualError::InvalidHTTPCredentials,
- true,
- StatusCode::UNAUTHORIZED,
- )),
- ))
+ .body(body),
+ )))
}
}
@@ -135,18 +189,19 @@ impl Middleware<crate::MiniserveConfig> for Auth {
/// The reason why log_error_chain is optional is to handle cases where the auth pop-up appears and when the user clicks Cancel.
/// In those case, we do not log the error to the terminal since it does not really matter.
fn build_unauthorized_response(
- req: &HttpRequest<crate::MiniserveConfig>,
+ req: &HttpRequest,
error: ContextualError,
log_error_chain: bool,
error_code: StatusCode,
) -> String {
+ let state = req.app_data::<crate::MiniserveConfig>().unwrap();
let error = ContextualError::HTTPAuthenticationError(Box::new(error));
if log_error_chain {
errors::log_error_chain(error.to_string());
}
- let return_path = match &req.state().random_route {
- Some(random_route) => format!("/{}", random_route),
+ let return_path = match state.random_route {
+ Some(ref random_route) => format!("/{}", random_route),
None => "/".to_string(),
};
@@ -156,8 +211,8 @@ fn build_unauthorized_response(
&return_path,
None,
None,
- req.state().default_color_scheme,
- req.state().default_color_scheme,
+ state.default_color_scheme,
+ state.default_color_scheme,
false,
false,
)
diff --git a/src/errors.rs b/src/errors.rs
index 2878e37..feb91e2 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -12,7 +12,7 @@ pub enum ContextualError {
/// MultipartError, which might occur during file upload, when processing the multipart request fails
#[fail(display = "Failed to process multipart request\ncaused by: {}", _0)]
- MultipartError(actix_web::error::MultipartError),
+ MultipartError(actix_multipart::MultipartError),
/// This error might occur when decoding the HTTP authentication header.
#[fail(
diff --git a/src/file_upload.rs b/src/file_upload.rs
index f9bf002..136bd45 100644
--- a/src/file_upload.rs
+++ b/src/file_upload.rs
@@ -1,13 +1,12 @@
use actix_web::{
- dev,
http::{header, StatusCode},
- multipart, FutureResponse, HttpMessage, HttpRequest, HttpResponse,
+ HttpRequest, HttpResponse,
};
-use futures::{future, future::FutureResult, Future, Stream};
+use futures::{future, Future, FutureExt, Stream, TryStreamExt};
use std::{
- fs,
io::Write,
path::{Component, PathBuf},
+ pin::Pin,
};
use crate::errors::{self, ContextualError};
@@ -17,12 +16,12 @@ use crate::themes::ColorScheme;
/// Create future to save file.
fn save_file(
- field: multipart::Field<dev::Payload>,
+ field: actix_multipart::Field,
file_path: PathBuf,
overwrite_files: bool,
-) -> Box<dyn Future<Item = i64, Error = ContextualError>> {
+) -> Pin<Box<dyn Future<Output = Result<i64, ContextualError>>>> {
if !overwrite_files && file_path.exists() {
- return Box::new(future::err(ContextualError::CustomError(
+ return Box::pin(future::err(ContextualError::CustomError(
"File already exists, and the overwrite_files option has not been set".to_string(),
)));
}
@@ -30,85 +29,75 @@ fn save_file(
let mut file = match std::fs::File::create(&file_path) {
Ok(file) => file,
Err(e) => {
- return Box::new(future::err(ContextualError::IOError(
+ return Box::pin(future::err(ContextualError::IOError(
format!("Failed to create {}", file_path.display()),
e,
)));
}
};
- Box::new(
+ Box::pin(
field
.map_err(ContextualError::MultipartError)
- .fold(0i64, move |acc, bytes| {
+ .try_fold(0i64, move |acc, bytes| {
let rt = file
.write_all(bytes.as_ref())
.map(|_| acc + bytes.len() as i64)
.map_err(|e| {
ContextualError::IOError("Failed to write to file".to_string(), e)
});
- future::result(rt)
+ future::ready(rt)
}),
)
}
/// Create new future to handle file as multipart data.
fn handle_multipart(
- item: multipart::MultipartItem<dev::Payload>,
+ field: actix_multipart::Field,
mut file_path: PathBuf,
overwrite_files: bool,
-) -> Box<dyn Stream<Item = i64, Error = ContextualError>> {
- match item {
- multipart::MultipartItem::Field(field) => {
- let filename = field
- .headers()
- .get(header::CONTENT_DISPOSITION)
+) -> Pin<Box<dyn Stream<Item = Result<i64, ContextualError>>>> {
+ let filename = field
+ .headers()
+ .get(header::CONTENT_DISPOSITION)
+ .ok_or(ContextualError::ParseError)
+ .and_then(|cd| {
+ header::ContentDisposition::from_raw(cd).map_err(|_| ContextualError::ParseError)
+ })
+ .and_then(|content_disposition| {
+ content_disposition
+ .get_filename()
.ok_or(ContextualError::ParseError)
- .and_then(|cd| {
- header::ContentDisposition::from_raw(cd)
- .map_err(|_| ContextualError::ParseError)
- })
- .and_then(|content_disposition| {
- content_disposition
- .get_filename()
- .ok_or(ContextualError::ParseError)
- .map(String::from)
- });
- let err = |e: ContextualError| Box::new(future::err(e).into_stream());
- match filename {
- Ok(f) => {
- match fs::metadata(&file_path) {
- Ok(metadata) => {
- if !metadata.is_dir() {
- return err(ContextualError::InvalidPathError(format!(
- "cannot upload file to {}, since it's not a directory",
- &file_path.display()
- )));
- } else if metadata.permissions().readonly() {
- return err(ContextualError::InsufficientPermissionsError(
- file_path.display().to_string(),
- ));
- }
- }
- Err(_) => {
- return err(ContextualError::InsufficientPermissionsError(
- file_path.display().to_string(),
- ));
- }
+ .map(String::from)
+ });
+ let err = |e: ContextualError| Box::pin(future::err(e).into_stream());
+ match filename {
+ Ok(f) => {
+ match std::fs::metadata(&file_path) {
+ Ok(metadata) => {
+ if !metadata.is_dir() {
+ return err(ContextualError::InvalidPathError(format!(
+ "cannot upload file to {}, since it's not a directory",
+ &file_path.display()
+ )));
+ } else if metadata.permissions().readonly() {
+ return err(ContextualError::InsufficientPermissionsError(
+ file_path.display().to_string(),
+ ));
}
- file_path = file_path.join(f);
- Box::new(save_file(field, file_path, overwrite_files).into_stream())
}
- Err(e) => err(e(
- "HTTP header".to_string(),
- "Failed to retrieve the name of the file to upload".to_string(),
- )),
+ Err(_) => {
+ return err(ContextualError::InsufficientPermissionsError(
+ file_path.display().to_string(),
+ ));
+ }
}
+ file_path = file_path.join(f);
+ Box::pin(save_file(field, file_path, overwrite_files).into_stream())
}
- multipart::MultipartItem::Nested(mp) => Box::new(
- mp.map_err(ContextualError::MultipartError)
- .map(move |item| handle_multipart(item, file_path.clone(), overwrite_files))
- .flatten(),
- ),
+ Err(e) => err(e(
+ "HTTP header".to_string(),
+ "Failed to retrieve the name of the file to upload".to_string(),
+ )),
}
}
@@ -118,17 +107,19 @@ fn handle_multipart(
/// invalid.
/// This method returns future.
pub fn upload_file(
- req: &HttpRequest<crate::MiniserveConfig>,
+ req: HttpRequest,
+ payload: actix_web::web::Payload,
default_color_scheme: ColorScheme,
uses_random_route: bool,
-) -> FutureResponse<HttpResponse> {
+) -> Pin<Box<dyn Future<Output = Result<HttpResponse, actix_web::Error>>>> {
+ let conf = req.app_data::<crate::MiniserveConfig>().unwrap();
let return_path = if let Some(header) = req.headers().get(header::REFERER) {
header.to_str().unwrap_or("/").to_owned()
} else {
"/".to_string()
};
- let query_params = listing::extract_query_parameters(req);
+ let query_params = listing::extract_query_parameters(&req);
let color_scheme = query_params.theme.unwrap_or(default_color_scheme);
let upload_path = match query_params.path.clone() {
Some(path) => match path.strip_prefix(Component::RootDir) {
@@ -139,7 +130,7 @@ pub fn upload_file(
let err = ContextualError::InvalidHTTPRequestError(
"Missing query parameter 'path'".to_string(),
);
- return Box::new(create_error_response(
+ return Box::pin(create_error_response(
&err.to_string(),
StatusCode::BAD_REQUEST,
&return_path,
@@ -152,14 +143,14 @@ pub fn upload_file(
}
};
- let app_root_dir = match req.state().path.canonicalize() {
+ let app_root_dir = match conf.path.canonicalize() {
Ok(dir) => dir,
Err(e) => {
let err = ContextualError::IOError(
"Failed to resolve path served by miniserve".to_string(),
e,
);
- return Box::new(create_error_response(
+ return Box::pin(create_error_response(
&err.to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
&return_path,
@@ -179,7 +170,7 @@ pub fn upload_file(
let err = ContextualError::InvalidHTTPRequestError(
"Invalid value for 'path' parameter".to_string(),
);
- return Box::new(create_error_response(
+ return Box::pin(create_error_response(
&err.to_string(),
StatusCode::BAD_REQUEST,
&return_path,
@@ -191,13 +182,13 @@ pub fn upload_file(
));
}
};
- let overwrite_files = req.state().overwrite_files;
- Box::new(
- req.multipart()
+ let overwrite_files = conf.overwrite_files;
+ Box::pin(
+ actix_multipart::Multipart::new(req.headers(), payload)
.map_err(ContextualError::MultipartError)
- .map(move |item| handle_multipart(item, target_dir.clone(), overwrite_files))
- .flatten()
- .collect()
+ .map_ok(move |item| handle_multipart(item, target_dir.clone(), overwrite_files))
+ .try_flatten()
+ .try_collect::<Vec<_>>()
.then(move |e| match e {
Ok(_) => future::ok(
HttpResponse::SeeOther()
@@ -229,7 +220,7 @@ fn create_error_response(
color_scheme: ColorScheme,
default_color_scheme: ColorScheme,
uses_random_route: bool,
-) -> FutureResult<HttpResponse, actix_web::error::Error> {
+) -> future::Ready<Result<HttpResponse, actix_web::Error>> {
errors::log_error_chain(description.to_string());
future::ok(
HttpResponse::BadRequest()
diff --git a/src/listing.rs b/src/listing.rs
index 063fbf3..a4eda88 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -1,7 +1,9 @@
+use actix_web::body::Body;
+use actix_web::dev::ServiceResponse;
use actix_web::http::StatusCode;
-use actix_web::{fs, Body, FromRequest, HttpRequest, HttpResponse, Query, Result};
+use actix_web::web::Query;
+use actix_web::{HttpRequest, HttpResponse, Result};
use bytesize::ByteSize;
-use futures::Stream;
use htmlescape::encode_minimal as escape_html_entity;
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use qrcodegen::{QrCode, QrCodeEcc};
@@ -122,17 +124,17 @@ impl Entry {
}
}
-pub fn file_handler(req: &HttpRequest<crate::MiniserveConfig>) -> Result<fs::NamedFile> {
- let path = &req.state().path;
- Ok(fs::NamedFile::open(path)?)
+pub async fn file_handler(req: HttpRequest) -> Result<actix_files::NamedFile> {
+ let path = &req.app_data::<crate::MiniserveConfig>().unwrap().path;
+ actix_files::NamedFile::open(path).map_err(Into::into)
}
/// List a directory and renders a HTML file accordingly
/// Adapted from https://docs.rs/actix-web/0.7.13/src/actix_web/fs.rs.html#564
#[allow(clippy::too_many_arguments)]
-pub fn directory_listing<S>(
- dir: &fs::Directory,
- req: &HttpRequest<S>,
+pub fn directory_listing(
+ dir: &actix_files::Directory,
+ req: &HttpRequest,
skip_symlinks: bool,
file_upload: bool,
random_route: Option<String>,
@@ -141,7 +143,8 @@ pub fn directory_listing<S>(
upload_route: String,
tar_enabled: bool,
zip_enabled: bool,
-) -> Result<HttpResponse, io::Error> {
+) -> Result<ServiceResponse, io::Error> {
+ use actix_web::dev::BodyEncoding;
let serve_path = req.path();
// In case the current path is a directory, we want to make sure that the current URL ends
@@ -151,9 +154,12 @@ pub fn directory_listing<S>(
"" => String::new(),
_ => format!("?{}", req.query_string()),
};
- return Ok(HttpResponse::MovedPermanenty()
- .header("Location", format!("{}/{}", serve_path, query))
- .body("301"));
+ return Ok(ServiceResponse::new(
+ req.clone(),
+ HttpResponse::MovedPermanently()
+ .header("Location", format!("{}/{}", serve_path, query))
+ .body("301"),
+ ));
}
let base = Path::new(serve_path);
@@ -177,7 +183,7 @@ pub fn directory_listing<S>(
HttpResponse::UriTooLong().body(Body::Empty)
}
};
- return Ok(res);
+ return Ok(ServiceResponse::new(req.clone(), res));
}
let mut entries: Vec<Entry> = Vec::new();
@@ -269,22 +275,25 @@ pub fn directory_listing<S>(
if let Some(compression_method) = query_params.download {
if !compression_method.is_enabled(tar_enabled, zip_enabled) {
- return Ok(HttpResponse::Forbidden()
- .content_type("text/html; charset=utf-8")
- .body(
- renderer::render_error(
- "Archive creation is disabled.",
- StatusCode::FORBIDDEN,
- "/",
- None,
- None,
- color_scheme,
- default_color_scheme,
- false,
- false,
- )
- .into_string(),
- ));
+ return Ok(ServiceResponse::new(
+ req.clone(),
+ HttpResponse::Forbidden()
+ .content_type("text/html; charset=utf-8")
+ .body(
+ renderer::render_error(
+ "Archive creation is disabled.",
+ StatusCode::FORBIDDEN,
+ "/",
+ None,
+ None,
+ color_scheme,
+ default_color_scheme,
+ false,
+ false,
+ )
+ .into_string(),
+ ),
+ ));
}
log::info!(
"Creating an archive ({extension}) of {path}...",
@@ -301,7 +310,7 @@ pub fn directory_listing<S>(
// We will create the archive in a separate thread, and stream the content using a pipe.
// The pipe is made of a futures channel, and an adapter to implement the `Write` trait.
// Include 10 messages of buffer for erratic connection speeds.
- let (tx, rx) = futures::sync::mpsc::channel(10);
+ let (tx, rx) = futures::channel::mpsc::channel::<Result<actix_web::web::Bytes, ()>>(10);
let pipe = crate::pipe::Pipe::new(tx);
// Start the actual archive creation in a separate thread.
@@ -320,47 +329,52 @@ pub fn directory_listing<S>(
//
// That being said, `rx` will never fail because the `Stream` implementation for `Receiver`
// never returns an error - it simply cannot fail.
- let rx = rx.map_err(|_| unreachable!("pipes never fail"));
+ //let rx = rx.map_err(|_| unreachable!("pipes never fail"));
// At this point, `rx` implements everything actix want for a streamed response,
// so we can just give a `Box::new(rx)` as streaming body.
- Ok(HttpResponse::Ok()
- .content_type(compression_method.content_type())
- .content_encoding(compression_method.content_encoding())
- .header("Content-Transfer-Encoding", "binary")
- .header(
- "Content-Disposition",
- format!("attachment; filename={:?}", filename),
- )
- .chunked()
- .body(Body::Streaming(Box::new(rx))))
- } else {
- Ok(HttpResponse::Ok()
- .content_type("text/html; charset=utf-8")
- .body(
- renderer::page(
- serve_path,
- entries,
- is_root,
- query_params.sort,
- query_params.order,
- default_color_scheme,
- color_scheme,
- show_qrcode,
- file_upload,
- &upload_route,
- &current_dir.display().to_string(),
- tar_enabled,
- zip_enabled,
+ Ok(ServiceResponse::new(
+ req.clone(),
+ HttpResponse::Ok()
+ .content_type(compression_method.content_type())
+ .encoding(compression_method.content_encoding())
+ .header("Content-Transfer-Encoding", "binary")
+ .header(
+ "Content-Disposition",
+ format!("attachment; filename={:?}", filename),
)
- .into_string(),
- ))
+ .body(actix_web::body::BodyStream::new(rx)),
+ ))
+ } else {
+ Ok(ServiceResponse::new(
+ req.clone(),
+ HttpResponse::Ok()
+ .content_type("text/html; charset=utf-8")
+ .body(
+ renderer::page(
+ serve_path,
+ entries,
+ is_root,
+ query_params.sort,
+ query_params.order,
+ default_color_scheme,
+ color_scheme,
+ show_qrcode,
+ file_upload,
+ &upload_route,
+ &current_dir.display().to_string(),
+ tar_enabled,
+ zip_enabled,
+ )
+ .into_string(),
+ ),
+ ))
}
}
-pub fn extract_query_parameters<S>(req: &HttpRequest<S>) -> QueryParameters {
- match Query::<QueryParameters>::extract(req) {
+pub fn extract_query_parameters(req: &HttpRequest) -> QueryParameters {
+ match Query::<QueryParameters>::from_query(req.query_string()) {
Ok(query) => QueryParameters {
sort: query.sort,
order: query.order,
diff --git a/src/main.rs b/src/main.rs
index a9e7944..1477652 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,8 @@
#![feature(proc_macro_hygiene)]
-use actix_web::http::{Method, StatusCode};
-use actix_web::{fs, middleware, server, App, HttpRequest, HttpResponse};
+use actix_web::http::StatusCode;
+use actix_web::web;
+use actix_web::{middleware, App, HttpRequest, HttpResponse};
use std::io::{self, Write};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::thread;
@@ -80,12 +81,12 @@ fn main() {
}
}
-fn run() -> Result<(), ContextualError> {
+#[actix_rt::main(miniserve)]
+async fn run() -> Result<(), ContextualError> {
if cfg!(windows) && !Paint::enable_windows_ascii() {
Paint::disable();
}
- let sys = actix::System::new("miniserve");
let miniserve_config = args::parse_args();
let log_level = if miniserve_config.verbose {
@@ -229,16 +230,18 @@ fn run() -> Result<(), ContextualError> {
}
};
- server::new(move || {
- App::with_state(inside_config.clone())
- .middleware(auth::Auth)
- .middleware(middleware::Logger::default())
- .configure(configure_app)
+ let srv = actix_web::HttpServer::new(move || {
+ App::new()
+ .app_data(inside_config.clone())
+ .wrap(auth::Auth)
+ .wrap(middleware::Logger::default())
+ .configure(|c| configure_app(c, &inside_config))
+ .default_service(web::get().to(error_404))
})
.bind(socket_addresses.as_slice())
.map_err(|e| ContextualError::IOError("Failed to bind server".to_string(), e))?
.shutdown_timeout(0)
- .start();
+ .run();
println!(
"Serving path {path} at {addresses}",
@@ -248,41 +251,41 @@ fn run() -> Result<(), ContextualError> {
println!("\nQuit by pressing CTRL-C");
- let _ = sys.run();
-
- Ok(())
+ srv.await
+ .map_err(|e| ContextualError::IOError("".to_owned(), e))
}
/// Configures the Actix application
-fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
+fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) {
+ let random_route = conf.random_route.clone().unwrap_or_default();
+ let uses_random_route = conf.random_route.clone().is_some();
+ let full_route = format!("/{}", random_route);
+
let upload_route;
let s = {
- let path = &app.state().path;
- let no_symlinks = app.state().no_symlinks;
- let random_route = app.state().random_route.clone();
- let default_color_scheme = app.state().default_color_scheme;
- let show_qrcode = app.state().show_qrcode;
- let file_upload = app.state().file_upload;
- let tar_enabled = app.state().tar_enabled;
- let zip_enabled = app.state().zip_enabled;
- upload_route = if let Some(random_route) = app.state().random_route.clone() {
+ let path = &conf.path;
+ let no_symlinks = conf.no_symlinks;
+ let random_route = conf.random_route.clone();
+ let default_color_scheme = conf.default_color_scheme;
+ let show_qrcode = conf.show_qrcode;
+ let file_upload = conf.file_upload;
+ let tar_enabled = conf.tar_enabled;
+ let zip_enabled = conf.zip_enabled;
+ upload_route = if let Some(random_route) = conf.random_route.clone() {
format!("/{}/upload", random_route)
} else {
"/upload".to_string()
};
if path.is_file() {
None
- } else if let Some(index_file) = &app.state().index {
+ } else if let Some(index_file) = &conf.index {
Some(
- fs::StaticFiles::new(path)
- .expect("Failed to setup static file handler")
- .index_file(index_file.to_string_lossy()),
+ actix_files::Files::new(&full_route, path).index_file(index_file.to_string_lossy()),
)
} else {
let u_r = upload_route.clone();
Some(
- fs::StaticFiles::new(path)
- .expect("Failed to setup static file handler")
+ actix_files::Files::new(&full_route, path)
.show_files_listing()
.files_listing_renderer(move |dir, req| {
listing::directory_listing(
@@ -298,49 +301,43 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
zip_enabled,
)
})
- .default_handler(error_404),
+ .default_handler(web::to(error_404)),
)
}
};
- let random_route = app.state().random_route.clone().unwrap_or_default();
- let uses_random_route = app.state().random_route.clone().is_some();
- let full_route = format!("/{}", random_route);
-
if let Some(s) = s {
- if app.state().file_upload {
- let default_color_scheme = app.state().default_color_scheme;
+ if conf.file_upload {
+ let default_color_scheme = conf.default_color_scheme;
// Allow file upload
- app.resource(&upload_route, move |r| {
- r.method(Method::POST).f(move |file| {
- file_upload::upload_file(file, default_color_scheme, uses_random_route)
- })
- })
+ app.service(
+ web::resource(&upload_route).route(web::post().to(move |req, payload| {
+ file_upload::upload_file(req, payload, default_color_scheme, uses_random_route)
+ })),
+ )
// Handle directories
- .handler(&full_route, s)
- .default_resource(|r| r.method(Method::GET).f(error_404))
+ .service(s);
} else {
// Handle directories
- app.handler(&full_route, s)
- .default_resource(|r| r.method(Method::GET).f(error_404))
+ app.service(s);
}
} else {
// Handle single files
- app.resource(&full_route, |r| r.f(listing::file_handler))
- .default_resource(|r| r.method(Method::GET).f(error_404))
+ app.service(web::resource(&full_route).route(web::to(listing::file_handler)));
}
}
-fn error_404(req: &HttpRequest<crate::MiniserveConfig>) -> Result<HttpResponse, io::Error> {
+async fn error_404(req: HttpRequest) -> HttpResponse {
let err_404 = ContextualError::RouteNotFoundError(req.path().to_string());
- let default_color_scheme = req.state().default_color_scheme;
- let uses_random_route = req.state().random_route.is_some();
- let query_params = listing::extract_query_parameters(req);
+ let conf = req.app_data::<MiniserveConfig>().unwrap();
+ let default_color_scheme = conf.default_color_scheme;
+ let uses_random_route = conf.random_route.is_some();
+ let query_params = listing::extract_query_parameters(&req);
let color_scheme = query_params.theme.unwrap_or(default_color_scheme);
errors::log_error_chain(err_404.to_string());
- Ok(actix_web::HttpResponse::NotFound().body(
+ actix_web::HttpResponse::NotFound().body(
renderer::render_error(
&err_404.to_string(),
StatusCode::NOT_FOUND,
@@ -353,5 +350,5 @@ fn error_404(req: &HttpRequest<crate::MiniserveConfig>) -> Result<HttpResponse,
!uses_random_route,
)
.into_string(),
- ))
+ )
}
diff --git a/src/pipe.rs b/src/pipe.rs
index 09ca580..92f7019 100644
--- a/src/pipe.rs
+++ b/src/pipe.rs
@@ -1,7 +1,8 @@
//! Define an adapter to implement `std::io::Write` on `Sender<Bytes>`.
-use bytes::{Bytes, BytesMut};
-use futures::sink::{Sink, Wait};
-use futures::sync::mpsc::Sender;
+use actix_web::web::{Bytes, BytesMut};
+use futures::channel::mpsc::Sender;
+use futures::executor::block_on;
+use futures::sink::SinkExt;
use std::io::{Error, ErrorKind, Result, Write};
/// Adapter to implement the `std::io::Write` trait on a `Sender<Bytes>` from a futures channel.
@@ -10,15 +11,15 @@ use std::io::{Error, ErrorKind, Result, Write};
pub struct Pipe {
// Wrapping the sender in `Wait` makes it blocking, so we can implement blocking-style
// io::Write over the async-style Sender.
- dest: Wait<Sender<Bytes>>,
+ dest: Sender<std::result::Result<Bytes, ()>>,
bytes: BytesMut,
}
impl Pipe {
/// Wrap the given sender in a `Pipe`.
- pub fn new(destination: Sender<Bytes>) -> Self {
+ pub fn new(destination: Sender<std::result::Result<Bytes, ()>>) -> Self {
Pipe {
- dest: destination.wait(),
+ dest: destination,
bytes: BytesMut::new(),
}
}
@@ -28,7 +29,7 @@ impl Drop for Pipe {
fn drop(&mut self) {
// This is the correct thing to do, but is not super important since the `Sink`
// implementation of `Sender` just returns `Ok` without doing anything else.
- let _ = self.dest.close();
+ let _ = block_on(self.dest.close());
}
}
@@ -38,8 +39,7 @@ impl Write for Pipe {
self.bytes.extend_from_slice(buf);
// Then, take the buffer and send it in the channel.
- self.dest
- .send(self.bytes.take().into())
+ block_on(self.dest.send(Ok(self.bytes.split().into())))
.map_err(|e| Error::new(ErrorKind::UnexpectedEof, e))?;
// Return how much we sent - all of it.
@@ -47,8 +47,6 @@ impl Write for Pipe {
}
fn flush(&mut self) -> Result<()> {
- self.dest
- .flush()
- .map_err(|e| Error::new(ErrorKind::UnexpectedEof, e))
+ block_on(self.dest.flush()).map_err(|e| Error::new(ErrorKind::UnexpectedEof, e))
}
}