diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 218 |
1 files changed, 49 insertions, 169 deletions
diff --git a/src/main.rs b/src/main.rs index b6dd856..149f1ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use std::io; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::io::Write; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::thread; use std::time::Duration; -use std::{io::Write, path::PathBuf}; use actix_web::web; use actix_web::{ @@ -10,8 +10,7 @@ use actix_web::{ Responder, }; use actix_web::{middleware, App, HttpRequest, HttpResponse}; -use actix_web_httpauth::middleware::HttpAuthentication; -use http::header::HeaderMap; +use anyhow::Result; use log::{error, warn}; use structopt::clap::crate_version; use structopt::StructOpt; @@ -20,174 +19,31 @@ use yansi::{Color, Paint}; mod archive; mod args; mod auth; +mod config; mod errors; mod file_upload; mod listing; mod pipe; mod renderer; +use crate::config::MiniserveConfig; use crate::errors::ContextualError; -/// Possible characters for random routes -const ROUTE_ALPHABET: [char; 16] = [ - '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', -]; - -#[derive(Clone)] -/// Configuration of the Miniserve application -pub struct MiniserveConfig { - /// Enable verbose mode - pub verbose: bool, - - /// Path to be served by miniserve - pub path: std::path::PathBuf, - - /// Port on which miniserve will be listening - pub port: u16, - - /// IP address(es) on which miniserve will be available - pub interfaces: Vec<IpAddr>, - - /// Enable HTTP basic authentication - pub auth: Vec<auth::RequiredAuth>, - - /// If false, miniserve will serve the current working directory - pub path_explicitly_chosen: bool, - - /// Enable symlink resolution - pub no_symlinks: bool, - - /// Show hidden files - pub show_hidden: bool, - - /// Enable random route generation - pub random_route: Option<String>, - - /// Randomly generated favicon route - pub favicon_route: String, - - /// Randomly generated css route - pub css_route: String, - - /// Default color scheme - pub default_color_scheme: String, - - /// Default dark mode color scheme - pub default_color_scheme_dark: String, - - /// The name of a directory index file to serve, like "index.html" - /// - /// Normally, when miniserve serves a directory, it creates a listing for that directory. - /// However, if a directory contains this file, miniserve will serve that file instead. - pub index: Option<std::path::PathBuf>, - - /// Enable QR code display - pub show_qrcode: bool, - - /// Enable file upload - pub file_upload: bool, - - /// Enable upload to override existing files - pub overwrite_files: bool, - - /// If false, creation of uncompressed tar archives is disabled - pub tar_enabled: bool, - - /// If false, creation of gz-compressed tar archives is disabled - pub tar_gz_enabled: bool, - - /// If false, creation of zip archives is disabled - pub zip_enabled: bool, - - /// If enabled, directories are listed first - pub dirs_first: bool, - - /// Shown instead of host in page title and heading - pub title: Option<String>, - - /// If specified, header will be added - pub header: Vec<HeaderMap>, - - /// If enabled, version footer is hidden - pub hide_version_footer: bool, -} - -impl MiniserveConfig { - /// Parses the command line arguments - fn from_args(args: args::CliArgs) -> Self { - let interfaces = if !args.interfaces.is_empty() { - args.interfaces - } else { - vec![ - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), - IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), - ] - }; - - let random_route = if args.random_route { - Some(nanoid::nanoid!(6, &ROUTE_ALPHABET)) - } else { - None - }; - - // Generate some random routes for the favicon and css so that they are very unlikely to conflict with - // real files. - let favicon_route = nanoid::nanoid!(10, &ROUTE_ALPHABET); - let css_route = nanoid::nanoid!(10, &ROUTE_ALPHABET); - - let default_color_scheme = args.color_scheme; - let default_color_scheme_dark = args.color_scheme_dark; - - let path_explicitly_chosen = args.path.is_some() || args.index.is_some(); - - let port = match args.port { - 0 => port_check::free_local_port().expect("no free ports available"), - _ => args.port, - }; - - crate::MiniserveConfig { - verbose: args.verbose, - path: args.path.unwrap_or_else(|| PathBuf::from(".")), - port, - interfaces, - auth: args.auth, - path_explicitly_chosen, - no_symlinks: args.no_symlinks, - show_hidden: args.hidden, - random_route, - favicon_route, - css_route, - default_color_scheme, - default_color_scheme_dark, - index: args.index, - overwrite_files: args.overwrite_files, - show_qrcode: args.qrcode, - file_upload: args.file_upload, - tar_enabled: args.enable_tar, - tar_gz_enabled: args.enable_tar_gz, - zip_enabled: args.enable_zip, - dirs_first: args.dirs_first, - title: args.title, - header: args.header, - hide_version_footer: args.hide_version_footer, - } - } -} - -fn main() { +fn main() -> Result<()> { let args = args::CliArgs::from_args(); if let Some(shell) = args.print_completions { args::CliArgs::clap().gen_completions_to("miniserve", shell, &mut std::io::stdout()); - return; + return Ok(()); } - let miniserve_config = MiniserveConfig::from_args(args); + let miniserve_config = MiniserveConfig::try_from_args(args)?; match run(miniserve_config) { Ok(()) => (), Err(e) => errors::log_error_chain(e.to_string()), } + Ok(()) } #[actix_web::main(miniserve)] @@ -300,11 +156,17 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { if !addresses.is_empty() { addresses.push_str(", "); } + let protocol = if miniserve_config.tls_rustls_config.is_some() { + "https" + } else { + "http" + }; addresses.push_str(&format!( "{}", Color::Green .paint(format!( - "http://{interface}:{port}", + "{protocol}://{interface}:{port}", + protocol = protocol, interface = &interface, port = miniserve_config.port )) @@ -350,10 +212,11 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { App::new() .wrap(configure_header(&inside_config.clone())) .app_data(inside_config.clone()) - .wrap(middleware::Condition::new( - !inside_config.auth.is_empty(), - HttpAuthentication::basic(auth::handle_auth), - )) + // we should use `actix_web_httpauth::middleware::HttpAuthentication` + // but it is unfortuantrly broken + // see: https://github.com/actix/actix-extras/issues/127 + // TODO replace this when fixed upstream + .wrap_fn(auth::auth_middleware) .wrap(middleware::Logger::default()) .route( &format!("/{}", inside_config.favicon_route), @@ -362,11 +225,27 @@ async fn run(miniserve_config: MiniserveConfig) -> Result<(), ContextualError> { .route(&format!("/{}", inside_config.css_route), web::get().to(css)) .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) - .run(); + }); + + #[cfg(feature = "tls")] + let srv = if let Some(tls_config) = miniserve_config.tls_rustls_config { + srv.bind_rustls(socket_addresses.as_slice(), tls_config) + .map_err(|e| ContextualError::IoError("Failed to bind server".to_string(), e))? + .shutdown_timeout(0) + .run() + } else { + srv.bind(socket_addresses.as_slice()) + .map_err(|e| ContextualError::IoError("Failed to bind server".to_string(), e))? + .shutdown_timeout(0) + .run() + }; + + #[cfg(not(feature = "tls"))] + let srv = srv + .bind(socket_addresses.as_slice()) + .map_err(|e| ContextualError::IoError("Failed to bind server".to_string(), e))? + .shutdown_timeout(0) + .run(); println!( "Serving path {path} at {addresses}", @@ -466,6 +345,7 @@ fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) { ) }) .prefer_utf8(true) + .redirect_to_slash_directory() .default_handler(web::to(error_404)); Some(files) } @@ -489,8 +369,8 @@ fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) { uses_random_route, favicon_route.clone(), css_route.clone(), - &default_color_scheme, - &default_color_scheme_dark, + default_color_scheme.clone(), + default_color_scheme_dark.clone(), hide_version_footer, ) })), @@ -538,14 +418,14 @@ async fn error_404(req: HttpRequest) -> HttpResponse { async fn favicon() -> impl Responder { let logo = include_str!("../data/logo.svg"); - web::HttpResponse::Ok() - .set(ContentType(mime::IMAGE_SVG)) + HttpResponse::Ok() + .insert_header(ContentType(mime::IMAGE_SVG)) .message_body(logo.into()) } async fn css() -> impl Responder { let css = include_str!(concat!(env!("OUT_DIR"), "/style.css")); - web::HttpResponse::Ok() - .set(ContentType(mime::TEXT_CSS)) + HttpResponse::Ok() + .insert_header(ContentType(mime::TEXT_CSS)) .message_body(css.into()) } |