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
2021-08-03 17:09:59 +00:00
#[ macro_use ]
2021-08-21 12:10:41 +00:00
extern crate lazy_static ;
2021-08-01 11:57:26 +00:00
2021-08-21 12:10:41 +00:00
use actix_files as fs ;
use actix_web ::{
2021-08-22 07:49:00 +00:00
dev ::{ self , Service , ServiceResponse } ,
2021-08-21 12:10:41 +00:00
http , middleware , App , HttpResponse , HttpServer ,
} ;
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 ::{
env ::{ set_var , var } ,
fs ::read_dir ,
2021-08-23 06:08:20 +00:00
io ::{ BufReader , Error , ErrorKind , Read , Write } ,
2021-08-21 12:10:41 +00:00
net ::IpAddr ,
path ::{ Path , PathBuf } ,
str ::FromStr ,
} ;
2021-08-01 11:57:26 +00:00
2021-08-21 12:10:41 +00:00
lazy_static! {
pub static ref TEMPLATE : tera ::Tera = {
let mut tera = tera ::Tera ::default ( ) ;
2021-08-22 07:49:00 +00:00
tera . add_raw_template ( " index " , include_str! ( " ../templates/index.html.tera " ) )
2021-08-21 12:10:41 +00:00
. unwrap ( ) ;
tera
} ;
2021-08-18 09:48:59 +00:00
}
2021-08-21 12:10:41 +00:00
#[ inline ]
fn get_file_type ( from : & Path ) -> String {
match from . extension ( ) {
Some ( os_str ) = > match os_str . to_str ( ) . unwrap_or ( " " ) {
" 7z " = > " archive " ,
" bz " = > " archive " ,
" bz2 " = > " archive " ,
" cab " = > " archive " ,
" gz " = > " archive " ,
" iso " = > " archive " ,
" rar " = > " archive " ,
" xz " = > " archive " ,
" zip " = > " archive " ,
" zst " = > " archive " ,
" zstd " = > " archive " ,
" doc " = > " word " ,
" docx " = > " word " ,
" ppt " = > " powerpoint " ,
" pptx " = > " powerpoint " ,
" xls " = > " excel " ,
" xlsx " = > " excel " ,
" heic " = > " image " ,
" pdf " = > " pdf " ,
// JavaScript / TypeScript
" js " = > " code " ,
" cjs " = > " code " ,
" mjs " = > " code " ,
" jsx " = > " code " ,
" ts " = > " code " ,
" tsx " = > " code " ,
" json " = > " code " ,
" coffee " = > " code " ,
// HTML / CSS
" html " = > " code " ,
" htm " = > " code " ,
" xml " = > " code " ,
" xhtml " = > " code " ,
" vue " = > " code " ,
" ejs " = > " code " ,
" template " = > " code " ,
" tmpl " = > " code " ,
" pug " = > " code " ,
" art " = > " code " ,
" hbs " = > " code " ,
" tera " = > " code " ,
" css " = > " code " ,
" scss " = > " code " ,
" sass " = > " code " ,
" less " = > " code " ,
// Python
" py " = > " code " ,
" pyc " = > " code " ,
// JVM
" java " = > " code " ,
" kt " = > " code " ,
" kts " = > " code " ,
" gradle " = > " code " ,
" groovy " = > " code " ,
" scala " = > " code " ,
" jsp " = > " code " ,
// Shell
" sh " = > " code " ,
// Php
" php " = > " code " ,
// C / C++
" c " = > " code " ,
" cc " = > " code " ,
" cpp " = > " code " ,
" h " = > " code " ,
" cmake " = > " code " ,
// C#
" cs " = > " code " ,
" xaml " = > " code " ,
" sln " = > " code " ,
" csproj " = > " code " ,
// Golang
" go " = > " code " ,
" mod " = > " code " ,
" sum " = > " code " ,
// Swift
" swift " = > " code " ,
" plist " = > " code " ,
" xib " = > " code " ,
" xcconfig " = > " code " ,
" entitlements " = > " code " ,
" xcworkspacedata " = > " code " ,
" pbxproj " = > " code " ,
// Ruby
" rb " = > " code " ,
// Rust
" rs " = > " code " ,
// Objective-C
" m " = > " code " ,
// Dart
" dart " = > " code " ,
// Microsoft
" manifest " = > " code " ,
" rc " = > " code " ,
" cmd " = > " code " ,
" bat " = > " code " ,
" ps1 " = > " code " ,
// Config
" ini " = > " code " ,
" yaml " = > " code " ,
" toml " = > " code " ,
" conf " = > " code " ,
" properties " = > " code " ,
" lock " = > " alt " ,
_ = > match mime_guess ::from_path ( from ) . first_or_octet_stream ( ) . type_ ( ) {
mime_guess ::mime ::AUDIO = > " audio " ,
mime_guess ::mime ::IMAGE = > " image " ,
mime_guess ::mime ::PDF = > " pdf " ,
mime_guess ::mime ::VIDEO = > " video " ,
mime_guess ::mime ::TEXT = > " alt " ,
_ = > " file " ,
} ,
} ,
None = > " file " ,
2021-08-04 14:04:26 +00:00
}
2021-08-21 12:10:41 +00:00
. to_string ( )
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-06-06 05:01:22 +00:00
#[ derive(Serialize) ]
2021-09-03 10:41:58 +00:00
struct IndexContext {
title : String ,
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 ,
req : & actix_web ::HttpRequest ,
) -> Result < ServiceResponse , std ::io ::Error > {
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 ) ;
2021-08-21 12:10:41 +00:00
return Ok ( ServiceResponse ::new ( req . clone ( ) , 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 (
req . clone ( ) ,
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 ( ) ,
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 ;
}
2021-09-03 10:41:58 +00:00
let path =
urlencoding ::decode ( path ) . unwrap_or ( std ::borrow ::Cow ::Borrowed ( " [Parse URL Error] " ) ) ;
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
}
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 ( ) {
2021-08-15 17:23:54 +00:00
Ok ( time ) = > chrono ::DateTime ::< chrono ::Local > ::from ( time )
2021-08-21 12:10:41 +00:00
. format ( " %Y/%m/%d %H:%M:%S " )
2021-08-15 17:23:54 +00:00
. 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 ( ) ;
2021-08-21 12:10:41 +00:00
let 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 ,
} ) ;
2021-08-14 12:15:58 +00:00
}
}
}
}
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 ( ) ;
2021-08-21 12:10:41 +00:00
let content = tera ::Context ::from_serialize ( & context ) ;
let content = match content {
Ok ( ctx ) = > ctx ,
Err ( e ) = > {
error! ( target : " tera::Context::from_serialize " , " [ERROR] Read modified time error: {} " , e . to_string ( ) ) ;
return Err ( Error ::new ( ErrorKind ::Other , e . to_string ( ) ) ) ;
2021-08-15 04:07:57 +00:00
}
2021-08-21 12:10:41 +00:00
} ;
let index = TEMPLATE
. render ( " index " , & content )
2021-09-05 03:52:26 +00:00
. unwrap_or_else ( | _ | " TEMPLATE RENDER ERROR " . to_string ( ) ) ;
2021-08-21 12:10:41 +00:00
let res = HttpResponse ::Ok ( )
. content_type ( " text/html; charset=utf-8 " )
. body ( index ) ;
Ok ( ServiceResponse ::new ( req . clone ( ) , res ) )
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 (
2021-08-22 07:49:00 +00:00
req : dev ::ServiceRequest ,
2021-08-21 12:10:41 +00:00
auth : actix_web_httpauth ::extractors ::basic ::BasicAuth ,
2021-08-22 07:49:00 +00:00
) -> Result < dev ::ServiceRequest , actix_web ::Error > {
2021-09-05 03:52:26 +00:00
if auth . user_id ( )
= = var ( " AUTH_USERNAME " )
. unwrap_or_else ( | _ | " " . to_string ( ) )
. as_str ( )
2021-08-21 12:10:41 +00:00
& & hash ( auth . password ( ) . unwrap_or ( & std ::borrow ::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 ) ;
}
let err = actix_web_httpauth ::extractors ::AuthenticationError ::new (
actix_web_httpauth ::headers ::www_authenticate ::basic ::Basic ::with_realm (
" Incorrect username or password " ,
) ,
) ;
Err ( actix_web ::Error ::from ( err ) )
}
#[ actix_web::main ]
async fn main ( ) -> std ::io ::Result < ( ) > {
2021-10-24 13:46:08 +00:00
let check_does_dir_exits = | path : & str | match std ::fs ::metadata ( path ) {
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 ( ) ) ,
} ;
let check_does_file_exits = | path : & str | match std ::fs ::metadata ( path ) {
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 " ) )
. 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 ( ) ) ;
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 ( ) ) ;
set_var ( " COMPRESS " , matches . is_present ( " compress " ) . 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 ( ) ;
let addr = format! (
" {}:{} " ,
ip ,
2022-06-06 05:01:22 +00:00
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 " ) {
std ::process ::Command ::new ( " explorer " ) . arg ( url ) . spawn ( ) . ok ( ) ;
} else if cfg! ( target_os = " macos " ) {
std ::process ::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 " )
{
2021-08-03 17:09:59 +00:00
std ::process ::Command ::new ( " xdg-open " ) . arg ( url ) . spawn ( ) . ok ( ) ;
}
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
}
2021-08-22 07:49:00 +00:00
let addr = if let Some ( matches ) = matches . subcommand_matches ( " doc " ) {
2021-08-23 04:48:35 +00:00
let mut cargo_toml = match std ::fs ::File ::open ( " ./Cargo.toml " ) {
Ok ( file ) = > file ,
Err ( e ) = > {
error! ( " [ERROR] {} " , e . to_string ( ) ) ;
return Ok ( ( ) ) ;
}
} ;
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 ;
2021-08-23 09:47:56 +00:00
info! ( " [INFO] Generating document (may take a while) " ) ;
2021-08-22 07:49:00 +00:00
match std ::process ::Command ::new ( " cargo " ) . arg ( " doc " ) . output ( ) {
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 " ) ;
return Ok ( ( ) ) ;
} else if output . starts_with ( " error: " ) {
error! (
" [ERROR] {} " ,
output . strip_prefix ( " error: " ) . unwrap_or ( output )
) ;
2021-08-23 04:48:35 +00:00
return Ok ( ( ) ) ;
2021-08-22 07:49:00 +00:00
}
}
Err ( e ) = > {
error! ( " [ERROR] Cargo Error: {} " , e . to_string ( ) ) ;
return Ok ( ( ) ) ;
}
}
let path = Path ::new ( " ./target/doc/ " ) ;
let mut index_path = path . to_path_buf ( ) ;
2021-08-23 04:48:35 +00:00
index_path . push ( crate_name . to_string ( ) + " /index.html " ) ;
2021-08-22 07:49:00 +00:00
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 ,
2022-06-06 05:01:22 +00:00
matches . value_of ( " port " ) . unwrap_or ( " 8000 " )
2021-08-22 07:49:00 +00:00
) ;
let url = format! (
" http://{}:{}/{}/index.html " ,
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-23 04:48:35 +00:00
crate_name ,
2021-08-22 07:49:00 +00:00
) ;
if ! matches . is_present ( " noopen " ) {
open_in_browser ( & url ) ;
}
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 " ) ;
}
addr
} else {
addr
} ;
2021-08-25 12:41:59 +00:00
env_logger ::Builder ::from_env ( env_logger ::Env ::default ( ) . default_filter_or ( " info " ) )
. format ( | buf , record | {
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 (
chrono ::NaiveDateTime ::from_str ( data [ 0 ] )
2021-08-24 04:11:15 +00:00
. unwrap ( )
2021-08-25 12:41:59 +00:00
. format ( " %Y/%m/%d %H:%M:%S " )
. to_string ( ) ,
) ;
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 ] )
. unwrap_or ( std ::borrow ::Cow ::Borrowed ( " [Parse URL Error] " ) )
. 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 " {
if data . starts_with ( " SIGINT received, exiting " ) {
return writeln! ( buf , " \r {} " , green . value ( " [INFO] SIGINT received, exiting " ) ) ;
// Add '\r' to remove the input ^C
} else {
let data = data . replace ( " actix-web-service- " , " " ) ;
let re1 = regex ::Regex ::new ( " Starting (.*) workers " ) . unwrap ( ) ;
if re1 . is_match ( & data ) {
return Ok ( ( ) ) ;
}
let re2 = regex ::Regex ::new ( " Starting \" (.*) \" service on (.*) " ) . unwrap ( ) ;
if re2 . is_match ( & data ) {
let addr = re2
. captures ( & data )
. unwrap ( )
. get ( 1 )
. map_or ( " " , | m | m . as_str ( ) ) ;
let data = format! (
" [INFO] Serving {} on {} " ,
2021-09-05 03:52:26 +00:00
var ( " ROOT " ) . unwrap_or_else ( | _ | " . " . to_string ( ) ) ,
2021-08-25 12:41:59 +00:00
addr
) ;
return writeln! ( buf , " \r {} " , green . value ( data ) ) ;
}
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
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-06-06 05:01:22 +00:00
return dev ::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 " ,
2021-08-25 12:41:59 +00:00
actix_web_httpauth ::middleware ::HttpAuthentication ::basic ( validator ) ,
) )
2021-08-21 12:10:41 +00:00
. wrap ( middleware ::Logger ::new ( " %t^%a^%s^%D^%r " ) ) ;
2021-09-05 03:52:26 +00:00
let files = fs ::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 )
. default_handler ( | req : dev ::ServiceRequest | {
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-06-06 05:01:22 +00:00
let res = fs ::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 (
std ::fs ::File ::open ( Path ::new ( matches . value_of ( " cert " ) . unwrap ( ) ) ) . unwrap ( ) ,
) ;
let key = & mut BufReader ::new (
std ::fs ::File ::open ( Path ::new ( matches . value_of ( " key " ) . unwrap ( ) ) ) . unwrap ( ) ,
) ;
2022-06-06 05:01:22 +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 ( ) ) ;
let config = rustls ::ServerConfig ::builder ( )
. with_safe_defaults ( )
. with_no_client_auth ( )
. with_single_cert ( cert , key )
. expect ( " bad certificate/key " ) ;
2021-08-23 06:08:20 +00:00
server . bind_rustls ( addr , config )
2021-08-21 12:10:41 +00:00
} else {
server . bind ( addr )
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
}