2021-08-24 16:05:33 +00:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2. 0. If a copy of the MPL was not distributed with this
* file , You can obtain one at https ://mozilla.org/MPL/2.0/. */
2021-08-25 12:41:59 +00:00
2022-07-19 14:58:03 +00:00
mod filetype ;
2021-08-01 11:57:26 +00:00
2021-08-21 12:10:41 +00:00
use actix_web ::{
2022-07-19 14:58:03 +00:00
dev ::{ Response , Service , ServiceRequest , ServiceResponse } ,
http , middleware , App , HttpRequest , HttpResponse , HttpServer ,
2021-08-21 12:10:41 +00:00
} ;
2022-07-19 14:58:03 +00:00
use actix_web_httpauth ::{
extractors ::{ basic ::BasicAuth , AuthenticationError } ,
headers ::www_authenticate ::basic ::Basic ,
middleware ::HttpAuthentication ,
} ;
use askama_actix ::TemplateToResponse ;
2021-10-24 13:46:08 +00:00
use clap ::Arg ;
2021-08-21 12:10:41 +00:00
use env_logger ::fmt ::Color ;
2021-08-22 07:49:00 +00:00
use log ::{ error , info } ;
2022-06-06 05:01:22 +00:00
use serde ::{ Deserialize , Serialize } ;
2021-08-21 12:10:41 +00:00
use sha2 ::Digest ;
use std ::{
2022-07-19 14:58:03 +00:00
borrow ::Cow ,
2021-08-21 12:10:41 +00:00
env ::{ set_var , var } ,
2022-07-27 17:39:51 +00:00
fs ::{ self , metadata , read_dir , read_to_string } ,
2022-07-19 14:58:03 +00:00
io ::{ self , BufReader , Read , Write } ,
2021-08-21 12:10:41 +00:00
net ::IpAddr ,
path ::{ Path , PathBuf } ,
2022-07-19 14:58:03 +00:00
process ::Command ,
2021-08-21 12:10:41 +00:00
str ::FromStr ,
} ;
2022-07-19 14:58:03 +00:00
use time ::OffsetDateTime ;
2021-08-01 11:57:26 +00:00
2022-06-06 05:01:22 +00:00
#[ derive(Deserialize) ]
2021-08-23 04:48:35 +00:00
struct Package {
name : String ,
}
2022-06-06 05:01:22 +00:00
#[ derive(Deserialize) ]
2021-08-23 04:48:35 +00:00
struct CargoToml {
package : Package ,
}
2022-06-06 05:01:22 +00:00
#[ derive(Eq, Ord, PartialEq, PartialOrd, Serialize) ]
2021-08-12 16:56:54 +00:00
struct Dir {
name : String ,
modified : String ,
}
2022-06-06 05:01:22 +00:00
#[ derive(Eq, Ord, PartialEq, PartialOrd, Serialize) ]
2021-08-12 16:56:54 +00:00
struct File {
name : String ,
size : u64 ,
2021-08-15 17:23:54 +00:00
filetype : String ,
2021-08-12 16:56:54 +00:00
modified : String ,
}
2022-07-19 14:58:03 +00:00
#[ derive(askama_actix::Template) ]
#[ template(path = " index.html " ) ]
2022-06-06 05:01:22 +00:00
#[ derive(Serialize) ]
2021-09-03 10:41:58 +00:00
struct IndexContext {
title : String ,
2022-07-27 17:39:51 +00:00
readme : String ,
2021-09-03 10:41:58 +00:00
paths : Vec < String > ,
2021-08-12 16:56:54 +00:00
dirs : Vec < Dir > ,
files : Vec < File > ,
2021-08-04 14:04:26 +00:00
}
2021-08-21 12:10:41 +00:00
fn render_index (
dir : & actix_files ::Directory ,
2022-07-19 14:58:03 +00:00
req : & HttpRequest ,
) -> Result < ServiceResponse , io ::Error > {
2021-08-21 12:10:41 +00:00
let mut index = dir . path . clone ( ) ;
index . push ( " index.html " ) ;
if index . exists ( ) & & index . is_file ( ) {
2022-06-06 05:01:22 +00:00
let res = actix_files ::NamedFile ::open ( index ) ?
2021-08-21 12:10:41 +00:00
. set_content_type ( mime_guess ::mime ::TEXT_HTML_UTF_8 )
2022-06-06 05:01:22 +00:00
. into_response ( req ) ;
2022-06-07 09:59:04 +00:00
return Ok ( ServiceResponse ::new ( req . to_owned ( ) , res ) ) ;
2021-08-12 16:56:54 +00:00
}
2021-09-05 03:52:26 +00:00
if var ( " NOINDEX " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) = = " true " {
2021-08-21 12:10:41 +00:00
return Ok ( ServiceResponse ::new (
2022-06-07 09:59:04 +00:00
req . to_owned ( ) ,
2021-08-21 12:10:41 +00:00
HttpResponse ::NotFound ( ) . body ( " " ) ,
) ) ;
2021-08-04 14:04:26 +00:00
}
2021-09-05 03:52:26 +00:00
let show_dot_files = var ( " DOTFILES " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) = = " true " ;
2021-08-14 12:15:58 +00:00
let mut context = IndexContext {
2021-09-03 10:41:58 +00:00
title : " " . to_string ( ) ,
2022-07-27 17:39:51 +00:00
readme : " " . to_string ( ) ,
2021-08-14 12:15:58 +00:00
paths : vec ! [ ] ,
dirs : vec ! [ ] ,
files : vec ! [ ] ,
2021-08-09 17:09:34 +00:00
} ;
2021-08-21 12:10:41 +00:00
for path in req . path ( ) . split ( '/' ) {
2021-09-05 03:52:26 +00:00
if path . is_empty ( ) {
2021-08-14 12:15:58 +00:00
continue ;
}
2022-07-19 14:58:03 +00:00
let path = urlencoding ::decode ( path ) . unwrap_or ( Cow ::Borrowed ( " [Parse URL Error] " ) ) ;
2021-09-03 10:41:58 +00:00
let path = path . into_owned ( ) ;
2021-08-18 09:48:59 +00:00
context . paths . push ( path ) ;
2021-08-14 12:15:58 +00:00
}
2022-07-27 17:39:51 +00:00
let mut readme_str = " " . to_string ( ) ;
2021-08-21 12:10:41 +00:00
match read_dir ( & dir . path ) {
Err ( e ) = > {
error! ( target : " read_dir " , " [ERROR] Read dir error: {} " , e . to_string ( ) ) ;
}
2021-08-14 12:15:58 +00:00
Ok ( paths ) = > {
for path in paths {
let path = match path {
2021-08-21 12:10:41 +00:00
Ok ( path ) = > path ,
2021-08-14 12:15:58 +00:00
Err ( e ) = > {
2021-08-21 12:10:41 +00:00
error! ( target : " read_dir " , " [ERROR] Read path error: {} " , e . to_string ( ) ) ;
2021-08-14 12:15:58 +00:00
continue ;
}
} ;
2021-08-15 17:23:54 +00:00
let name = match path . file_name ( ) . to_str ( ) {
2021-08-14 12:15:58 +00:00
Some ( str ) = > str . to_string ( ) ,
None = > {
2021-08-21 12:10:41 +00:00
error! ( target : " read_dir " , " [ERROR] Read filename error " ) ;
2021-08-14 12:15:58 +00:00
continue ;
}
} ;
2021-09-05 03:52:26 +00:00
if ! show_dot_files & & name . starts_with ( '.' ) {
2021-08-14 12:15:58 +00:00
continue ;
}
let metadata = match path . metadata ( ) {
Ok ( data ) = > data ,
Err ( e ) = > {
2021-08-21 12:10:41 +00:00
error! ( target : " read_dir " , " [ERROR] Read metadata error: {} " , e . to_string ( ) ) ;
2021-08-14 12:15:58 +00:00
continue ;
}
} ;
let modified = match metadata . modified ( ) {
2022-07-19 14:58:03 +00:00
Ok ( time ) = > OffsetDateTime ::from ( time )
2022-07-18 16:42:08 +00:00
. format ( time ::macros ::format_description! (
" [year]/[month]/[day] [hour]:[minute]:[second] "
) )
. unwrap_or_else ( | _ | " " . to_string ( ) ) ,
2021-08-14 12:15:58 +00:00
Err ( e ) = > {
2021-08-21 12:10:41 +00:00
error! ( target : " read_dir " , " [ERROR] Read modified time error: {} " , e . to_string ( ) ) ;
2021-08-14 12:15:58 +00:00
continue ;
}
} ;
if metadata . is_dir ( ) {
2021-08-15 17:23:54 +00:00
context . dirs . push ( Dir { name , modified } ) ;
2021-08-14 12:15:58 +00:00
} else if metadata . is_file ( ) {
2021-08-15 17:23:54 +00:00
let size = metadata . len ( ) ;
2022-07-19 14:58:03 +00:00
let filetype = filetype ::get_file_type ( & path . path ( ) ) ;
2021-08-14 12:15:58 +00:00
context . files . push ( File {
2021-08-15 17:23:54 +00:00
name ,
size ,
filetype ,
modified ,
} ) ;
2022-07-27 17:39:51 +00:00
if path . file_name ( ) . to_ascii_lowercase ( ) = = " readme.md " {
readme_str = read_to_string ( path . path ( ) ) . unwrap_or_else ( | _ | " " . to_string ( ) ) ;
}
2021-08-14 12:15:58 +00:00
}
}
}
}
2022-07-27 17:39:51 +00:00
if var ( " NOINDEX " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) ! = " true " {
context . readme = comrak ::markdown_to_html (
& readme_str ,
& comrak ::ComrakOptions {
extension : comrak ::ComrakExtensionOptions {
strikethrough : true ,
tagfilter : true ,
table : true ,
autolink : true ,
tasklist : true ,
superscript : true ,
header_ids : None ,
footnotes : true ,
description_lists : true ,
front_matter_delimiter : None ,
} ,
parse : comrak ::ComrakParseOptions {
smart : false ,
default_info_string : None ,
} ,
render : comrak ::ComrakRenderOptions {
hardbreaks : false ,
github_pre_lang : false ,
width : 1000 ,
unsafe_ : true ,
escape : false ,
list_style : comrak ::ListStyleType ::default ( ) ,
} ,
} ,
) ;
}
2021-09-03 10:41:58 +00:00
context . title = context . paths . last ( ) . unwrap_or ( & " / " . to_string ( ) ) . to_string ( ) ;
2021-08-15 04:07:57 +00:00
context . dirs . sort ( ) ;
context . files . sort ( ) ;
2022-07-19 14:58:03 +00:00
Ok ( ServiceResponse ::new ( req . to_owned ( ) , context . to_response ( ) ) )
2021-08-15 04:07:57 +00:00
}
2021-08-18 09:48:59 +00:00
#[ inline ]
2021-08-21 12:10:41 +00:00
fn display_path ( path : & Path ) -> String {
2021-08-04 14:04:26 +00:00
let root = Path ::canonicalize ( path ) . unwrap ( ) . display ( ) . to_string ( ) ;
if root . starts_with ( " \\ \\ ? \\ " ) {
root [ 4 .. root . len ( ) ] . to_string ( )
} else {
2021-09-05 03:52:26 +00:00
root
2021-08-03 17:09:59 +00:00
}
}
2021-08-21 12:10:41 +00:00
#[ inline ]
fn hash ( from : & str ) -> String {
let mut hasher = sha2 ::Sha512 ::new ( ) ;
hasher . update ( from ) ;
format! ( " {:?} " , hasher . finalize ( ) )
}
#[ inline ]
async fn validator (
2022-07-19 14:58:03 +00:00
req : ServiceRequest ,
auth : BasicAuth ,
) -> Result < ServiceRequest , ( actix_web ::Error , ServiceRequest ) > {
2021-09-05 03:52:26 +00:00
if auth . user_id ( )
= = var ( " AUTH_USERNAME " )
. unwrap_or_else ( | _ | " " . to_string ( ) )
. as_str ( )
2022-07-19 14:58:03 +00:00
& & hash ( auth . password ( ) . unwrap_or ( & Cow ::from ( " " ) ) )
2021-09-05 03:52:26 +00:00
= = var ( " AUTH_PASSWORD " )
. unwrap_or_else ( | _ | " " . to_string ( ) )
. as_str ( )
2021-08-21 12:10:41 +00:00
{
return Ok ( req ) ;
}
2022-07-19 14:58:03 +00:00
let err = AuthenticationError ::new ( Basic ::with_realm ( " Incorrect username or password " ) ) ;
Err ( ( actix_web ::Error ::from ( err ) , req ) )
2021-08-21 12:10:41 +00:00
}
#[ actix_web::main ]
2022-07-19 14:58:03 +00:00
async fn main ( ) -> io ::Result < ( ) > {
let check_does_dir_exits = | path : & str | match metadata ( path ) {
2021-10-24 13:46:08 +00:00
Ok ( meta ) = > {
if meta . is_dir ( ) {
Ok ( ( ) )
} else {
Err ( " Parameter is not a directory " . to_owned ( ) )
2021-08-01 11:57:26 +00:00
}
2021-10-24 13:46:08 +00:00
}
Err ( e ) = > Err ( e . to_string ( ) ) ,
} ;
2022-07-19 14:58:03 +00:00
let check_does_file_exits = | path : & str | match metadata ( path ) {
2021-10-24 13:46:08 +00:00
Ok ( metadata ) = > {
if metadata . is_file ( ) {
Ok ( ( ) )
} else {
Err ( " Parameter is not a file " . to_owned ( ) )
2021-08-01 11:57:26 +00:00
}
2021-10-24 13:46:08 +00:00
}
Err ( e ) = > Err ( e . to_string ( ) ) ,
} ;
let check_is_ip_addr = | s : & str | match IpAddr ::from_str ( s ) {
Ok ( _ ) = > Ok ( ( ) ) ,
Err ( e ) = > Err ( e . to_string ( ) ) ,
} ;
let check_is_port_num = | s : & str | match s . parse ::< u16 > ( ) {
Ok ( _ ) = > Ok ( ( ) ) ,
Err ( e ) = > Err ( e . to_string ( ) ) ,
} ;
let check_is_auth = | s : & str | {
let parts = s . splitn ( 2 , ':' ) . collect ::< Vec < & str > > ( ) ;
if parts . len ( ) < 2 | | parts . len ( ) > = 2 & & parts [ 1 ] . is_empty ( ) {
Err ( " Password not found " . to_owned ( ) )
} else if parts [ 0 ] . is_empty ( ) {
Err ( " Username not found " . to_owned ( ) )
} else {
Ok ( ( ) )
}
} ;
2022-06-06 05:01:22 +00:00
let matches = clap ::command! ( )
2022-01-02 14:12:19 +00:00
. arg ( Arg ::new ( " noindex " ) . long ( " noindex " ) . help ( " Disable automatic index page generation " ) )
2022-07-27 17:39:51 +00:00
. arg ( Arg ::new ( " noreadme " ) . long ( " noreadme " ) . help ( " Disable automatic readme rendering " ) )
2022-01-02 14:12:19 +00:00
. arg ( Arg ::new ( " nocache " ) . long ( " nocache " ) . help ( " Disable HTTP cache " ) )
. arg ( Arg ::new ( " nocolor " ) . long ( " nocolor " ) . help ( " Disable cli colors " ) )
. arg ( Arg ::new ( " cors " ) . long ( " cors " ) . takes_value ( true ) . min_values ( 0 ) . max_values ( 1 ) . help ( " Enable CORS [with custom value] " ) )
. arg ( Arg ::new ( " spa " ) . long ( " spa " ) . help ( " Enable Single-Page Application mode (always serve /index.html when the file is not found) " ) )
. arg ( Arg ::new ( " dotfiles " ) . short ( 'd' ) . long ( " dotfiles " ) . help ( " Show dotfiles " ) )
. arg ( Arg ::new ( " open " ) . short ( 'o' ) . long ( " open " ) . help ( " Open the page in the default browser " ) )
. arg ( Arg ::new ( " quiet " ) . short ( 'q' ) . long ( " quiet " ) . help ( " Disable access log output " ) )
. arg ( Arg ::new ( " quietall " ) . long ( " quietall " ) . help ( " Disable all output " ) )
. arg ( Arg ::new ( " ROOT " ) . default_value ( " . " ) . validator ( check_does_dir_exits ) . help ( " Root directory " ) )
. arg ( Arg ::new ( " address " ) . short ( 'a' ) . long ( " address " ) . default_value ( " 0.0.0.0 " ) . takes_value ( true ) . validator ( check_is_ip_addr ) . help ( " IP address to serve on " ) )
. arg ( Arg ::new ( " port " ) . short ( 'p' ) . long ( " port " ) . default_value ( " 8000 " ) . takes_value ( true ) . validator ( check_is_port_num ) . help ( " Port to serve on " ) )
. arg ( Arg ::new ( " auth " ) . long ( " auth " ) . takes_value ( true ) . validator ( check_is_auth ) . help ( " HTTP Auth (username:password) " ) )
. arg ( Arg ::new ( " cert " ) . long ( " cert " ) . takes_value ( true ) . validator ( check_does_file_exits ) . help ( " Path of TLS/SSL public key (certificate) " ) )
. arg ( Arg ::new ( " key " ) . long ( " key " ) . takes_value ( true ) . validator ( check_does_file_exits ) . help ( " Path of TLS/SSL private key " ) )
2022-06-06 05:01:22 +00:00
. subcommand ( clap ::Command ::new ( " doc " )
2021-10-24 13:46:08 +00:00
. about ( " Open cargo doc via local server (Need cargo installation) " )
2022-01-02 14:12:19 +00:00
. arg ( Arg ::new ( " nocolor " ) . long ( " nocolor " ) . help ( " Disable cli colors " ) )
. arg ( Arg ::new ( " noopen " ) . long ( " noopen " ) . help ( " Do not open the page in the default browser " ) )
. arg ( Arg ::new ( " log " ) . long ( " log " ) . help ( " Enable access log output [default: disabled] " ) )
. arg ( Arg ::new ( " quietall " ) . long ( " quietall " ) . help ( " Disable all output " ) )
. arg ( Arg ::new ( " address " ) . short ( 'a' ) . long ( " address " ) . default_value ( " 0.0.0.0 " ) . takes_value ( true ) . validator ( check_is_ip_addr ) . help ( " IP address to serve on " ) )
. arg ( Arg ::new ( " port " ) . short ( 'p' ) . long ( " port " ) . default_value ( " 8000 " ) . takes_value ( true ) . validator ( check_is_port_num ) . help ( " Port to serve on " ) )
2021-08-22 07:49:00 +00:00
)
2021-10-24 13:46:08 +00:00
. get_matches ( ) ;
2021-08-03 17:09:59 +00:00
2021-08-21 12:10:41 +00:00
set_var (
2021-08-04 14:04:26 +00:00
" ROOT " ,
2021-08-12 16:56:54 +00:00
display_path ( Path ::new ( matches . value_of ( " ROOT " ) . unwrap_or ( " . " ) ) ) ,
2021-08-04 14:04:26 +00:00
) ;
2021-08-03 17:09:59 +00:00
2021-08-21 12:10:41 +00:00
set_var ( " NOINDEX " , matches . is_present ( " noindex " ) . to_string ( ) ) ;
2022-07-27 17:39:51 +00:00
set_var ( " NOREADME " , matches . is_present ( " noreadme " ) . to_string ( ) ) ;
2021-08-21 12:10:41 +00:00
set_var ( " SPA " , matches . is_present ( " spa " ) . to_string ( ) ) ;
set_var ( " DOTFILES " , matches . is_present ( " dotfiles " ) . to_string ( ) ) ;
set_var ( " NOCACHE " , matches . is_present ( " nocache " ) . to_string ( ) ) ;
2021-08-12 07:02:03 +00:00
2021-08-24 04:11:15 +00:00
if matches . is_present ( " quiet " ) {
set_var ( " RUST_LOG " , " info,actix_web::middleware::logger=off " ) ;
}
if matches . is_present ( " quietall " ) {
set_var ( " RUST_LOG " , " off " ) ;
}
2021-08-03 17:09:59 +00:00
if matches . is_present ( " nocolor " ) {
2021-08-21 12:10:41 +00:00
set_var ( " RUST_LOG_STYLE " , " never " ) ;
2021-08-03 17:09:59 +00:00
}
2021-09-05 03:52:26 +00:00
if let Some ( s ) = matches . value_of ( " auth " ) {
set_var ( " ENABLE_AUTH " , matches . is_present ( " auth " ) . to_string ( ) ) ;
let parts = s . splitn ( 2 , ':' ) . collect ::< Vec < & str > > ( ) ;
set_var ( " AUTH_USERNAME " , parts [ 0 ] ) ;
set_var ( " AUTH_PASSWORD " , hash ( parts [ 1 ] ) ) ;
2021-08-18 09:48:59 +00:00
}
2021-08-15 04:07:57 +00:00
if matches . is_present ( " cors " ) {
2021-08-21 12:10:41 +00:00
set_var ( " ENABLE_CORS " , matches . is_present ( " cors " ) . to_string ( ) ) ;
2021-08-15 04:07:57 +00:00
match matches . value_of ( " cors " ) {
Some ( str ) = > {
2022-06-06 05:01:22 +00:00
set_var ( " CORS " , str ) ;
2021-08-15 04:07:57 +00:00
}
None = > {
2021-08-21 12:10:41 +00:00
set_var ( " CORS " , " * " ) ;
2021-08-15 04:07:57 +00:00
}
}
}
2021-08-03 17:09:59 +00:00
let enable_tls = matches . is_present ( " cert " ) & & matches . is_present ( " key " ) ;
2021-08-21 12:10:41 +00:00
let ip = matches
. value_of ( " address " )
. unwrap_or ( " 127.0.0.1 " )
. to_string ( ) ;
2022-06-06 06:27:56 +00:00
let addr = format! ( " {} : {} " , ip , matches . value_of ( " port " ) . unwrap_or ( " 8000 " ) ) ;
2021-08-21 12:10:41 +00:00
let url = format! (
" {}{}:{} " ,
if enable_tls {
" https:// " . to_string ( )
} else {
" http:// " . to_string ( )
} ,
2021-08-23 04:48:35 +00:00
if ip = = " 0.0.0.0 " { " 127.0.0.1 " } else { & ip } ,
2022-06-06 05:01:22 +00:00
matches . value_of ( " port " ) . unwrap_or ( " 8000 " )
2021-08-21 12:10:41 +00:00
) ;
2021-08-03 17:09:59 +00:00
2021-08-22 07:49:00 +00:00
let open_in_browser = | url : & str | {
2021-08-03 17:09:59 +00:00
if cfg! ( target_os = " windows " ) {
2022-07-19 14:58:03 +00:00
Command ::new ( " explorer " ) . arg ( url ) . spawn ( ) . ok ( ) ;
2021-08-03 17:09:59 +00:00
} else if cfg! ( target_os = " macos " ) {
2022-07-19 14:58:03 +00:00
Command ::new ( " open " ) . arg ( url ) . spawn ( ) . ok ( ) ;
2021-08-23 09:47:56 +00:00
} else if cfg! ( target_os = " linux " )
| | cfg! ( target_os = " android " )
| | cfg! ( target_os = " freebsd " )
| | cfg! ( target_os = " dragonfly " )
| | cfg! ( target_os = " openbsd " )
| | cfg! ( target_os = " netbsd " )
{
2022-07-19 14:58:03 +00:00
Command ::new ( " xdg-open " ) . arg ( url ) . spawn ( ) . ok ( ) ;
2021-08-03 17:09:59 +00:00
}
2021-08-22 07:49:00 +00:00
} ;
if matches . is_present ( " open " ) {
open_in_browser ( & url ) ;
2021-08-03 17:09:59 +00:00
}
2022-06-07 09:59:04 +00:00
if let Some ( matches ) = matches . subcommand_matches ( " doc " ) {
2021-08-22 07:49:00 +00:00
if ! matches . is_present ( " log " ) {
2021-08-24 04:11:15 +00:00
set_var ( " RUST_LOG " , " info,actix_web::middleware::logger=off " ) ;
}
if matches . is_present ( " quietall " ) {
set_var ( " RUST_LOG " , " off " ) ;
2021-08-22 07:49:00 +00:00
}
if matches . is_present ( " nocolor " ) {
set_var ( " RUST_LOG_STYLE " , " never " ) ;
}
2022-06-07 09:59:04 +00:00
}
2021-08-22 07:49:00 +00:00
2021-08-25 12:41:59 +00:00
env_logger ::Builder ::from_env ( env_logger ::Env ::default ( ) . default_filter_or ( " info " ) )
2022-06-06 06:27:56 +00:00
. format ( move | buf , record | {
2021-08-25 12:41:59 +00:00
let data = record . args ( ) . to_string ( ) ;
let mut style = buf . style ( ) ;
2021-10-24 13:46:08 +00:00
let blue = style . set_color ( Color ::Cyan ) ;
2021-08-25 12:41:59 +00:00
let mut style = buf . style ( ) ;
2021-10-24 13:46:08 +00:00
let red = style . set_color ( Color ::Red ) ;
2021-08-25 12:41:59 +00:00
let mut style = buf . style ( ) ;
2021-10-24 13:46:08 +00:00
let green = style . set_color ( Color ::Green ) ;
2021-08-25 12:41:59 +00:00
if record . target ( ) = = " actix_web::middleware::logger " {
2021-09-05 03:52:26 +00:00
let data : Vec < & str > = data . splitn ( 5 , '^' ) . collect ( ) ;
2021-08-25 12:41:59 +00:00
let time = blue . value (
2022-07-19 14:58:03 +00:00
OffsetDateTime ::parse ( data [ 0 ] , & time ::format_description ::well_known ::Rfc3339 )
. unwrap_or ( OffsetDateTime ::UNIX_EPOCH )
. format ( time ::macros ::format_description! (
" [year]/[month]/[day] [hour]:[minute]:[second] "
) )
. unwrap_or_else ( | _ | " " . to_string ( ) ) ,
2021-08-25 12:41:59 +00:00
) ;
let ipaddr = blue . value ( data [ 1 ] ) ;
let status_code = data [ 2 ] . parse ( ) . unwrap_or ( 500 ) ;
let status_code = if status_code < 400 {
green . value ( status_code )
} else {
red . value ( status_code )
} ;
2021-09-05 03:52:26 +00:00
let process_time : Vec < & str > = data [ 3 ] . splitn ( 2 , '.' ) . collect ( ) ;
2021-08-25 12:41:59 +00:00
let process_time = process_time [ 0 ] . to_string ( ) + " ms " ;
let process_time = blue . value ( if process_time . len ( ) = = 3 {
" " . to_string ( ) + & process_time
} else if process_time . len ( ) = = 4 {
" " . to_string ( ) + & process_time
} else {
process_time
} ) ;
2021-09-03 10:41:58 +00:00
let content = blue . value (
urlencoding ::decode ( data [ 4 ] )
2022-07-19 14:58:03 +00:00
. unwrap_or ( Cow ::Borrowed ( " [Parse URL Error] " ) )
2021-09-03 10:41:58 +00:00
. into_owned ( ) ,
) ;
2021-08-25 12:41:59 +00:00
return writeln! (
buf ,
" [{}] {} | {} | {} | {} " ,
time , ipaddr , status_code , process_time , content
) ;
} else if record . target ( ) = = " actix_server::builder " {
2022-06-06 06:27:56 +00:00
if data . starts_with ( " Starting " ) & & data . ends_with ( " workers " ) {
return Ok ( ( ) ) ;
}
} else if record . target ( ) = = " actix_server::server " {
if data = = " Actix runtime found; starting in Actix runtime " {
let data = format! (
" [INFO] Serving {} on {} " ,
var ( " ROOT " ) . unwrap_or_else ( | _ | " . " . to_string ( ) ) ,
2022-06-07 09:59:04 +00:00
var ( " LISTEN_ADDRESS " ) . unwrap_or_else ( | _ | " 0.0.0.0:8000 " . to_string ( ) )
2022-06-06 06:27:56 +00:00
) ;
return writeln! ( buf , " \r {} " , green . value ( data ) ) ;
}
if data = = " SIGINT received; starting forced shutdown " {
2022-07-18 16:42:08 +00:00
return writeln! (
buf ,
" \r {} " ,
green . value ( " [INFO] SIGINT received; starting forced shutdown " )
) ;
2021-08-25 12:41:59 +00:00
// Add '\r' to remove the input ^C
2021-08-24 04:11:15 +00:00
}
2022-06-06 06:27:56 +00:00
return Ok ( ( ) ) ;
} else if record . target ( ) = = " actix_server::worker "
| | record . target ( ) = = " actix_server::accept "
{
return Ok ( ( ) ) ;
2021-08-24 04:11:15 +00:00
}
2021-08-25 12:41:59 +00:00
if data . starts_with ( " [ERROR] " )
| | data . starts_with ( " TLS alert " )
| | data . starts_with ( " Failed " )
{
writeln! ( buf , " \r {} " , red . value ( data ) )
} else {
writeln! ( buf , " \r {} " , green . value ( data ) )
}
} )
. init ( ) ;
2021-08-24 04:11:15 +00:00
2022-07-18 16:42:08 +00:00
let addr = if let Some ( matches ) = matches . subcommand_matches ( " doc " ) {
2022-07-19 14:58:03 +00:00
let mut cargo_toml = match fs ::File ::open ( " ./Cargo.toml " ) {
2022-07-18 16:42:08 +00:00
Ok ( file ) = > file ,
Err ( e ) = > {
error! ( " [ERROR] {} " , e . to_string ( ) ) ;
return Ok ( ( ) ) ;
2022-06-07 09:59:04 +00:00
}
2022-07-18 16:42:08 +00:00
} ;
let mut contents = String ::new ( ) ;
match cargo_toml . read_to_string ( & mut contents ) {
Ok ( _ ) = > { }
Err ( e ) = > {
error! ( " [ERROR] {} " , e . to_string ( ) ) ;
return Ok ( ( ) ) ;
}
}
let contents : CargoToml = match toml ::from_str ( & contents ) {
Ok ( t ) = > t ,
Err ( e ) = > {
error! ( " [ERROR] {} " , e . to_string ( ) ) ;
return Ok ( ( ) ) ;
}
} ;
let crate_name = contents . package . name ;
info! ( " [INFO] Generating document (may take a while) " ) ;
2022-07-19 14:58:03 +00:00
match Command ::new ( " cargo " ) . arg ( " doc " ) . output ( ) {
2022-07-18 16:42:08 +00:00
Ok ( output ) = > {
let output = std ::str ::from_utf8 ( & output . stderr ) . unwrap_or ( " " ) ;
if output . starts_with ( " error: could not find `Cargo.toml` in " ) {
error! ( " [ERROR] Cargo.toml Not Found " ) ;
2022-06-07 09:59:04 +00:00
return Ok ( ( ) ) ;
2022-07-18 16:42:08 +00:00
} else if output . starts_with ( " error: " ) {
error! (
" [ERROR] {} " ,
output . strip_prefix ( " error: " ) . unwrap_or ( output )
) ;
2022-06-07 09:59:04 +00:00
return Ok ( ( ) ) ;
}
}
2022-07-18 16:42:08 +00:00
Err ( e ) = > {
error! ( " [ERROR] Cargo Error: {} " , e . to_string ( ) ) ;
2022-06-07 09:59:04 +00:00
return Ok ( ( ) ) ;
}
2022-07-18 16:42:08 +00:00
}
let path = Path ::new ( " ./target/doc/ " ) ;
let mut index_path = path . to_path_buf ( ) ;
index_path . push ( crate_name . to_string ( ) + " /index.html " ) ;
if ! index_path . exists ( ) | | ! index_path . is_file ( ) {
error! ( " [ERROR] Cargo Error: doc path not found " ) ;
return Ok ( ( ) ) ;
}
set_var ( " ROOT " , display_path ( path ) ) ;
let ip = matches
. value_of ( " address " )
. unwrap_or ( " 127.0.0.1 " )
. to_string ( ) ;
let addr = format! ( " {} : {} " , ip , matches . value_of ( " port " ) . unwrap_or ( " 8000 " ) ) ;
let url = format! (
" http://{}:{}/{}/index.html " ,
if ip = = " 0.0.0.0 " { " 127.0.0.1 " } else { & ip } ,
matches . value_of ( " port " ) . unwrap_or ( " 8000 " ) ,
crate_name ,
) ;
if ! matches . is_present ( " noopen " ) {
open_in_browser ( & url ) ;
}
addr
} else {
addr
} ;
set_var ( " LISTEN_ADDRESS " , addr ) ;
2021-08-21 12:10:41 +00:00
let server = HttpServer ::new ( move | | {
let app = App ::new ( )
. wrap_fn ( | req , srv | {
2021-09-05 03:52:26 +00:00
let paths = PathBuf ::from_str ( req . path ( ) ) . unwrap_or_default ( ) ;
2021-08-21 12:10:41 +00:00
let mut isdotfile = false ;
for path in paths . iter ( ) {
if path . to_string_lossy ( ) . starts_with ( '.' ) {
isdotfile = true ;
}
}
let fut = srv . call ( req ) ;
async move {
2021-08-25 12:41:59 +00:00
Ok ( fut . await ? . map_body ( | head , body | {
2021-09-05 03:52:26 +00:00
if var ( " NOCACHE " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) = = " true " {
2021-08-21 12:10:41 +00:00
head . headers_mut ( ) . insert (
http ::header ::CACHE_CONTROL ,
2022-06-06 05:01:22 +00:00
http ::header ::HeaderValue ::from_static ( " no-store " ) ,
2021-08-21 12:10:41 +00:00
) ;
}
2021-09-05 03:52:26 +00:00
if var ( " ENABLE_CORS " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) = = " true " {
let cors = var ( " CORS " ) . unwrap_or_else ( | _ | " * " . to_string ( ) ) ;
2022-06-06 05:01:22 +00:00
let cors = http ::header ::HeaderValue ::from_str ( & cors )
. unwrap_or_else ( | _ | http ::header ::HeaderValue ::from_static ( " * " ) ) ;
2021-08-21 12:10:41 +00:00
head . headers_mut ( )
. insert ( http ::header ::ACCESS_CONTROL_ALLOW_ORIGIN , cors ) ;
}
2021-09-05 03:52:26 +00:00
if isdotfile
& & var ( " DOTFILES " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) ! = " true "
2021-08-21 12:10:41 +00:00
{
2022-07-19 14:58:03 +00:00
return Response ::new ( http ::StatusCode ::FORBIDDEN ) . into_body ( ) ;
2021-08-21 12:10:41 +00:00
}
body
2021-08-22 07:49:00 +00:00
} ) )
2021-08-21 12:10:41 +00:00
}
} )
2022-06-06 05:01:22 +00:00
. wrap ( middleware ::Compress ::default ( ) )
2021-08-25 12:41:59 +00:00
. wrap ( middleware ::Condition ::new (
2021-09-05 03:52:26 +00:00
var ( " ENABLE_AUTH " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) = = " true " ,
2022-07-19 14:58:03 +00:00
HttpAuthentication ::basic ( validator ) ,
2021-08-25 12:41:59 +00:00
) )
2021-08-21 12:10:41 +00:00
. wrap ( middleware ::Logger ::new ( " %t^%a^%s^%D^%r " ) ) ;
2022-07-19 14:58:03 +00:00
let files = actix_files ::Files ::new ( " / " , var ( " ROOT " ) . unwrap_or_else ( | _ | " . " . to_string ( ) ) )
2021-08-21 12:10:41 +00:00
. use_hidden_files ( )
. prefer_utf8 ( true )
. show_files_listing ( )
2021-08-22 07:49:00 +00:00
. files_listing_renderer ( render_index )
2022-07-19 14:58:03 +00:00
. default_handler ( | req : ServiceRequest | {
2021-08-22 07:49:00 +00:00
let ( http_req , _payload ) = req . into_parts ( ) ;
async {
2021-09-05 03:52:26 +00:00
let path = var ( " ROOT " ) . unwrap_or_else ( | _ | " . " . to_string ( ) ) ;
2021-08-22 07:49:00 +00:00
let mut path = Path ::new ( & path ) . to_path_buf ( ) ;
path . push ( " index.html " ) ;
if path . exists ( )
& & path . is_file ( )
2021-09-05 03:52:26 +00:00
& & var ( " SPA " ) . unwrap_or_else ( | _ | " false " . to_string ( ) ) = = " true "
2021-08-22 07:49:00 +00:00
{
2022-07-19 14:58:03 +00:00
let res = actix_files ::NamedFile ::open ( path ) ? . into_response ( & http_req ) ;
2021-08-22 07:49:00 +00:00
return Ok ( ServiceResponse ::new ( http_req , res ) ) ;
}
2021-09-05 03:52:26 +00:00
Ok ( ServiceResponse ::new (
2021-08-22 07:49:00 +00:00
http_req ,
HttpResponse ::NotFound ( ) . body ( " " ) ,
2021-09-05 03:52:26 +00:00
) )
2021-08-22 07:49:00 +00:00
}
} ) ;
2021-09-05 03:52:26 +00:00
app . service ( files )
2021-08-21 12:10:41 +00:00
} ) ;
let server = if enable_tls {
2021-08-23 06:08:20 +00:00
let cert = & mut BufReader ::new (
2022-07-19 14:58:03 +00:00
fs ::File ::open ( Path ::new ( matches . value_of ( " cert " ) . unwrap ( ) ) ) . unwrap ( ) ,
2021-08-23 06:08:20 +00:00
) ;
let key = & mut BufReader ::new (
2022-07-19 14:58:03 +00:00
fs ::File ::open ( Path ::new ( matches . value_of ( " key " ) . unwrap ( ) ) ) . unwrap ( ) ,
2021-08-23 06:08:20 +00:00
) ;
2022-06-06 06:27:56 +00:00
let cert = rustls_pemfile ::certs ( cert )
. unwrap ( )
. iter ( )
. map ( | x | rustls ::Certificate ( x . to_vec ( ) ) )
. collect ::< Vec < _ > > ( ) ;
let key = rustls ::PrivateKey (
rustls_pemfile ::pkcs8_private_keys ( key )
. unwrap ( )
. first ( )
. expect ( " no private key found " )
. to_owned ( ) ,
) ;
2022-06-06 05:01:22 +00:00
let config = rustls ::ServerConfig ::builder ( )
. with_safe_defaults ( )
. with_no_client_auth ( )
. with_single_cert ( cert , key )
. expect ( " bad certificate/key " ) ;
2022-07-18 16:42:08 +00:00
server . bind_rustls (
var ( " LISTEN_ADDRESS " ) . unwrap_or_else ( | _ | " 0.0.0.0:8000 " . to_string ( ) ) ,
config ,
)
2021-08-21 12:10:41 +00:00
} else {
2022-06-07 09:59:04 +00:00
server . bind ( var ( " LISTEN_ADDRESS " ) . unwrap_or_else ( | _ | " 0.0.0.0:8000 " . to_string ( ) ) )
2021-08-04 14:04:26 +00:00
} ;
2021-08-21 12:10:41 +00:00
server ? . run ( ) . await
2021-08-01 11:57:26 +00:00
}