2021-04-17 22:43:13 +00:00
//! # PTTH Server
//!
2021-04-18 17:51:43 +00:00
//! The PTTH server is an HTTP server that can serve files from
//! behind a firewall, because it only makes outgoing HTTP connections
//! to a PTTH relay.
//!
2021-04-27 19:31:32 +00:00
//! ```text
2021-04-18 17:51:43 +00:00
//! View from outside the PTTH tunnel:
//!
//! * HTTP client
//! |
//! | HTTP(S) requests
//! V
//! * ptth_relay
//! ^
//! | HTTP(S) requests
//! |
//! * ptth_server
//!
//! View from inside the PTTH tunnel:
//!
//! * HTTP client
//! |
//! | HTTP(S) requests
//! V
//! * ptth_relay
//! |
//! | HTTP(S) requests
//! V
//! * ptth_server
//! ```
2021-04-17 22:43:13 +00:00
2020-11-29 19:05:28 +00:00
#![ warn (clippy::pedantic) ]
// I don't see the point in documenting the errors outside of where the
// error type is defined.
#![ allow (clippy::missing_errors_doc) ]
// False positive on futures::select! macro
#![ allow (clippy::mut_mut) ]
2021-10-02 17:26:56 +00:00
pub mod errors ;
/// In-process file server module with byte range and ETag support
pub mod file_server ;
/// Load and de-serialize structures from TOML, with a size limit
/// and checking permissions (On Unix)
pub mod load_toml ;
pub mod prelude ;
2020-10-30 23:02:57 +00:00
use std ::{
2021-08-30 01:17:25 +00:00
collections ::* ,
2021-05-09 18:32:24 +00:00
future ::Future ,
2020-10-30 23:36:32 +00:00
path ::PathBuf ,
2020-10-30 23:02:57 +00:00
sync ::Arc ,
time ::Duration ,
} ;
2020-11-06 05:03:33 +00:00
use futures ::FutureExt ;
2020-10-30 23:02:57 +00:00
use reqwest ::Client ;
use tokio ::{
2020-12-20 17:41:00 +00:00
sync ::{
2021-03-21 15:43:15 +00:00
mpsc ,
2020-12-20 17:41:00 +00:00
oneshot ,
} ,
2020-10-30 23:02:57 +00:00
} ;
2021-03-06 21:15:41 +00:00
use tokio_stream ::wrappers ::ReceiverStream ;
2020-10-30 23:02:57 +00:00
2020-11-27 00:03:11 +00:00
use ptth_core ::{
2020-11-02 04:07:55 +00:00
http_serde ,
} ;
2021-08-30 01:17:25 +00:00
// use crate::key_validity::BlakeHashWrapper;
2020-10-30 23:02:57 +00:00
2020-11-29 19:19:59 +00:00
use errors ::ServerError ;
2021-10-02 17:26:56 +00:00
use prelude ::* ;
2020-11-29 19:19:59 +00:00
2021-05-09 19:29:15 +00:00
pub struct State {
2021-05-09 19:54:09 +00:00
// file_server: file_server::FileServer,
2020-11-02 14:41:22 +00:00
config : Config ,
2020-11-02 04:07:55 +00:00
client : Client ,
}
2020-12-18 20:43:34 +00:00
// Unwrap a request from PTTH format and pass it into file_server.
// When file_server responds, wrap it back up and stream it to the relay.
2020-12-15 05:15:17 +00:00
async fn handle_one_req (
2021-05-09 17:54:29 +00:00
state : & State ,
req_id : String ,
response : http_serde ::Response ,
2020-12-15 05:15:17 +00:00
) -> Result < ( ) , ServerError >
{
2022-02-05 15:57:40 +00:00
let url = format! ( " {} /http_response/ {} " , state . config . relay_url , req_id ) ;
2020-12-15 05:15:17 +00:00
let mut resp_req = state . client
2022-02-05 15:57:40 +00:00
. post ( & url )
2021-07-10 22:17:32 +00:00
. header ( ptth_core ::PTTH_MAGIC_HEADER , base64 ::encode ( rmp_serde ::to_vec ( & response . parts ) . map_err ( ServerError ::MessagePackEncodeResponse ) ? ) )
. header ( " X-PTTH-SERVER-NAME " , & state . config . name ) ;
2020-12-15 05:15:17 +00:00
2022-02-05 16:59:54 +00:00
if response . parts . status_code ! = ptth_core ::http_serde ::StatusCode ::NotModified {
if let Some ( length ) = response . content_length {
resp_req = resp_req . header ( " Content-Length " , length . to_string ( ) ) ;
}
2020-12-15 05:15:17 +00:00
}
2022-02-05 16:59:54 +00:00
2021-03-21 15:43:15 +00:00
if let Some ( mut body ) = response . body {
if state . config . throttle_upload {
// Spawn another task to throttle the chunks
let ( tx , rx ) = mpsc ::channel ( 1 ) ;
tokio ::spawn ( async move {
while let Some ( chunk ) = body . recv ( ) . await {
2021-04-03 15:30:32 +00:00
let len = chunk . as_ref ( ) . map ( Vec ::len ) . ok ( ) ;
2021-03-21 15:43:15 +00:00
tx . send ( chunk ) . await ? ;
2021-04-03 15:30:32 +00:00
if let Some ( _len ) = len {
2021-03-21 15:43:15 +00:00
// debug! ("Throttling {} byte chunk", len);
}
tokio ::time ::sleep ( Duration ::from_millis ( 1000 ) ) . await ;
}
Ok ::< _ , anyhow ::Error > ( ( ) )
} ) ;
resp_req = resp_req . body ( reqwest ::Body ::wrap_stream ( ReceiverStream ::new ( rx ) ) ) ;
}
else {
resp_req = resp_req . body ( reqwest ::Body ::wrap_stream ( ReceiverStream ::new ( body ) ) ) ;
}
2020-12-15 05:15:17 +00:00
}
let req = resp_req . build ( ) . map_err ( ServerError ::Step5Responding ) ? ;
2022-02-05 15:57:40 +00:00
trace! ( " {:?} " , req . headers ( ) ) ;
2020-12-15 05:15:17 +00:00
//println! ("Step 6");
match state . client . execute ( req ) . await {
Ok ( r ) = > {
let status = r . status ( ) ;
let text = r . text ( ) . await . map_err ( ServerError ::Step7AfterResponse ) ? ;
2022-02-05 15:57:40 +00:00
debug! ( " http_response {} {:?} {:?} " , req_id , status , text ) ;
2020-12-15 05:15:17 +00:00
} ,
Err ( e ) = > {
if e . is_request ( ) {
warn! ( " Error while POSTing response. Client probably hung up. " ) ;
}
2021-03-06 22:58:23 +00:00
error! ( " Err: {:?} " , e ) ;
2020-12-15 05:15:17 +00:00
} ,
}
Ok ::< ( ) , ServerError > ( ( ) )
}
2021-05-09 19:29:15 +00:00
async fn handle_requests < F , H , SH > (
2020-12-20 18:23:17 +00:00
state : & Arc < State > ,
2022-02-05 16:52:56 +00:00
wrapped_reqs : Vec < http_serde ::WrappedRequest > ,
2021-05-09 19:29:15 +00:00
spawn_handler : & mut SH ,
2021-05-09 18:32:24 +00:00
) -> Result < ( ) , ServerError >
where
F : Send + Future < Output = anyhow ::Result < http_serde ::Response > > ,
2021-05-09 19:29:15 +00:00
H : Send + 'static + FnOnce ( http_serde ::RequestParts ) -> F ,
SH : Send + FnMut ( ) -> H
2020-12-15 05:15:17 +00:00
{
2020-10-30 23:02:57 +00:00
//println! ("Step 1");
2020-11-29 19:05:28 +00:00
for wrapped_req in wrapped_reqs {
2021-05-09 17:54:29 +00:00
let state = Arc ::clone ( & state ) ;
2021-05-09 18:32:24 +00:00
let handler = spawn_handler ( ) ;
2020-11-02 02:07:46 +00:00
2020-11-29 21:38:23 +00:00
// These have to detach, so we won't be able to catch the join errors.
2020-11-02 02:07:46 +00:00
tokio ::spawn ( async move {
2021-05-09 17:54:29 +00:00
let ( req_id , parts ) = ( wrapped_req . id , wrapped_req . req ) ;
2022-02-05 15:57:40 +00:00
info! ( " Req {} {} " , req_id , parts . uri ) ;
2021-05-09 18:32:24 +00:00
let f = handler ( parts ) ;
let response = f . await ? ;
2021-05-09 17:54:29 +00:00
handle_one_req ( & state , req_id , response ) . await
2020-11-02 02:07:46 +00:00
} ) ;
2020-10-30 23:02:57 +00:00
}
2020-11-08 03:16:13 +00:00
Ok ( ( ) )
2020-10-30 23:02:57 +00:00
}
2021-05-02 22:32:24 +00:00
/// Config for `ptth_server` and the file server module
2021-04-17 23:59:59 +00:00
///
/// This is a complete config.
/// The bin frontend is allowed to load an incomplete config from
/// the TOML file, fill it out with command-line options, and put
/// the completed config in this struct.
2021-03-15 19:55:12 +00:00
2021-03-21 02:49:44 +00:00
#[ derive (Clone) ]
2020-11-02 03:34:50 +00:00
pub struct ConfigFile {
2021-04-17 23:59:59 +00:00
/// A name that uniquely identifies this server on the relay.
/// May be human-readable.
2020-11-02 03:34:50 +00:00
pub name : String ,
2021-04-17 23:59:59 +00:00
/// Secret API key used to authenticate the server with the relay
2020-11-02 03:34:50 +00:00
pub api_key : String ,
2021-04-17 23:59:59 +00:00
/// URL of the PTTH relay server that ptth_server should connect to
2020-11-02 14:41:22 +00:00
pub relay_url : String ,
2021-04-17 23:59:59 +00:00
/// Directory that the file server module will expose to clients
2022-03-25 20:30:38 +00:00
/// over the relay, under `/files`. If None, the current working dir is used.
2021-08-30 00:49:32 +00:00
pub file_server_root : PathBuf ,
2021-04-17 23:59:59 +00:00
2022-03-25 20:30:38 +00:00
/// The file server module will expose these directories to clients under
/// `/dirs`. If symlinks can't be used (like on Windows), this allows PTTH
/// to serve multiple directories easily.
pub file_server_roots : BTreeMap < String , PathBuf > ,
2021-04-17 23:59:59 +00:00
/// For debugging.
2021-03-21 15:43:15 +00:00
pub throttle_upload : bool ,
2021-08-30 01:17:25 +00:00
2021-08-30 01:56:32 +00:00
pub client_keys : HashSet < String > ,
pub allow_any_client : bool ,
2020-11-02 14:41:22 +00:00
}
2020-11-24 23:56:43 +00:00
impl ConfigFile {
2021-03-21 02:49:44 +00:00
#[ must_use ]
pub fn new ( name : String , api_key : String , relay_url : String ) -> Self {
Self {
name ,
api_key ,
relay_url ,
2021-08-30 00:49:32 +00:00
file_server_root : PathBuf ::from ( " . " ) ,
2022-03-25 20:30:38 +00:00
file_server_roots : Default ::default ( ) ,
2021-03-21 15:43:15 +00:00
throttle_upload : false ,
2021-08-30 01:56:32 +00:00
client_keys : Default ::default ( ) ,
allow_any_client : true ,
2021-03-21 02:49:44 +00:00
}
}
2020-11-29 19:05:28 +00:00
#[ must_use ]
2020-11-24 23:56:43 +00:00
pub fn tripcode ( & self ) -> String {
base64 ::encode ( blake3 ::hash ( self . api_key . as_bytes ( ) ) . as_bytes ( ) )
}
}
2021-05-02 22:32:24 +00:00
/// Config for `ptth_server` itself
2021-04-17 23:59:59 +00:00
2020-11-02 14:41:22 +00:00
#[ derive (Default) ]
pub struct Config {
2021-05-09 19:54:09 +00:00
/// The name of our `ptth_server` instance
pub name : String ,
2021-04-17 23:59:59 +00:00
/// URL of the PTTH relay server that ptth_server should connect to
2020-11-02 14:41:22 +00:00
pub relay_url : String ,
2021-04-17 23:59:59 +00:00
/// For debugging.
2021-03-21 15:43:15 +00:00
pub throttle_upload : bool ,
2020-11-02 03:34:50 +00:00
}
2021-05-09 19:29:15 +00:00
pub struct Builder {
config_file : ConfigFile ,
hidden_path : Option < PathBuf > ,
asset_root : Option < PathBuf > ,
}
impl Builder {
pub fn new (
name : String ,
relay_url : String ,
) -> Self {
let config_file = ConfigFile {
name ,
api_key : ptth_core ::gen_key ( ) ,
relay_url ,
2021-08-30 00:49:32 +00:00
file_server_root : PathBuf ::from ( " . " ) ,
2022-03-25 20:30:38 +00:00
file_server_roots : Default ::default ( ) ,
2021-05-09 19:29:15 +00:00
throttle_upload : false ,
2021-08-30 01:56:32 +00:00
client_keys : Default ::default ( ) ,
allow_any_client : true ,
2021-05-09 19:29:15 +00:00
} ;
Self {
config_file ,
hidden_path : None ,
asset_root : None ,
}
}
pub fn build ( self ) -> Result < State , ServerError >
{
State ::new (
self . config_file ,
self . hidden_path ,
self . asset_root
)
}
pub fn api_key ( mut self , key : String ) -> Self {
self . config_file . api_key = key ;
self
}
}
2021-04-17 23:59:59 +00:00
2021-05-09 19:54:09 +00:00
/// Runs a `ptth_server` instance with the built-in file server module
2020-11-06 03:44:32 +00:00
pub async fn run_server (
2020-11-06 03:35:55 +00:00
config_file : ConfigFile ,
2020-11-08 15:01:15 +00:00
shutdown_oneshot : oneshot ::Receiver < ( ) > ,
2020-11-18 23:24:47 +00:00
hidden_path : Option < PathBuf > ,
asset_root : Option < PathBuf >
2020-11-06 03:35:55 +00:00
)
2021-05-09 19:29:15 +00:00
-> Result < ( ) , ServerError >
2020-11-02 03:34:50 +00:00
{
2021-05-09 19:54:09 +00:00
let metrics_interval = Arc ::new ( arc_swap ::ArcSwap ::default ( ) ) ;
let interval_writer = Arc ::clone ( & metrics_interval ) ;
tokio ::spawn ( async move {
file_server ::metrics ::Interval ::monitor ( interval_writer ) . await ;
} ) ;
let file_server = file_server ::FileServer ::new (
2022-03-25 20:40:36 +00:00
file_server ::Config {
file_server_root : config_file . file_server_root . clone ( ) ,
file_server_roots : config_file . file_server_roots . clone ( ) ,
} ,
2021-10-14 23:51:12 +00:00
& asset_root . clone ( ) . unwrap_or_else ( | | PathBuf ::from ( " . " ) ) ,
2021-05-09 19:54:09 +00:00
config_file . name . clone ( ) ,
metrics_interval ,
hidden_path . clone ( ) ,
) ? ;
let file_server = Arc ::new ( file_server ) ;
2021-05-09 19:29:15 +00:00
let state = Arc ::new ( State ::new (
config_file ,
hidden_path ,
asset_root ,
) ? ) ;
2020-12-15 05:15:17 +00:00
2021-05-09 19:54:09 +00:00
let file_server_2 = Arc ::clone ( & file_server ) ;
2020-11-29 19:05:28 +00:00
2021-05-09 19:29:15 +00:00
let mut spawn_handler = | | {
2021-05-09 19:54:09 +00:00
let file_server = Arc ::clone ( & file_server ) ;
2021-05-09 19:29:15 +00:00
| req : http_serde ::RequestParts | async move {
2021-05-09 19:54:09 +00:00
Ok ( file_server . serve_all ( req . method , & req . uri , & req . headers ) . await ? )
2021-05-09 19:29:15 +00:00
}
} ;
2020-10-31 02:31:03 +00:00
2021-05-09 19:29:15 +00:00
State ::run (
& state ,
shutdown_oneshot ,
& mut spawn_handler ,
) . await
2021-03-21 03:40:45 +00:00
}
2021-05-09 19:29:15 +00:00
impl State {
pub fn new (
config_file : ConfigFile ,
hidden_path : Option < PathBuf > ,
asset_root : Option < PathBuf >
)
-> Result < Self , ServerError >
{
use std ::convert ::TryInto ;
2020-11-06 23:43:52 +00:00
2021-05-09 19:29:15 +00:00
let asset_root = asset_root . unwrap_or_else ( PathBuf ::new ) ;
info! ( " Server name is {} " , config_file . name ) ;
info! ( " Tripcode is {} " , config_file . tripcode ( ) ) ;
2020-11-06 03:35:55 +00:00
2021-05-09 19:29:15 +00:00
let mut headers = reqwest ::header ::HeaderMap ::new ( ) ;
headers . insert ( " X-ApiKey " , config_file . api_key . try_into ( ) . map_err ( ServerError ::ApiKeyInvalid ) ? ) ;
2020-11-06 03:35:55 +00:00
2022-03-24 23:24:23 +00:00
// Cookie 01FYZ3W64SM6KYNP48J6EWSCEF
// Try to keep the Clients similar here
2021-05-09 19:29:15 +00:00
let client = Client ::builder ( )
. default_headers ( headers )
. connect_timeout ( Duration ::from_secs ( 30 ) )
. build ( ) . map_err ( ServerError ::CantBuildHttpClient ) ? ;
2020-11-02 03:34:50 +00:00
2021-05-09 19:29:15 +00:00
let state = State {
config : Config {
2021-05-09 19:54:09 +00:00
name : config_file . name ,
2021-05-09 19:29:15 +00:00
relay_url : config_file . relay_url ,
throttle_upload : config_file . throttle_upload ,
2020-11-06 05:03:33 +00:00
} ,
2021-05-09 19:29:15 +00:00
client ,
2020-11-06 05:03:33 +00:00
} ;
2021-05-09 19:29:15 +00:00
Ok ( state )
}
2022-02-05 16:52:56 +00:00
async fn http_listen (
state : & Arc < Self > ,
) -> Result < Vec < http_serde ::WrappedRequest > , ServerError >
{
use http ::status ::StatusCode ;
let req_resp = state . client . get ( & format! ( " {} /http_listen/ {} " , state . config . relay_url , state . config . name ) )
. timeout ( Duration ::from_secs ( 30 ) )
. send ( ) . await . map_err ( ServerError ::Step3Response ) ? ;
if req_resp . status ( ) = = StatusCode ::NO_CONTENT {
return Ok ( Vec ::new ( ) ) ;
}
if req_resp . status ( ) ! = StatusCode ::OK {
error! ( " {} " , req_resp . status ( ) ) ;
let body = req_resp . bytes ( ) . await . map_err ( ServerError ::Step3CollectBody ) ? ;
let body = String ::from_utf8 ( body . to_vec ( ) ) . map_err ( ServerError ::Step3ErrorResponseNotUtf8 ) ? ;
error! ( " {} " , body ) ;
return Err ( ServerError ::Step3Unknown ) ;
}
let body = req_resp . bytes ( ) . await . map_err ( ServerError ::CantCollectWrappedRequests ) ? ;
let wrapped_reqs : Vec < http_serde ::WrappedRequest > = rmp_serde ::from_read_ref ( & body )
. map_err ( ServerError ::CantParseWrappedRequests ) ? ;
Ok ( wrapped_reqs )
}
2021-05-09 19:29:15 +00:00
pub async fn run < F , H , SH > (
state : & Arc < Self > ,
shutdown_oneshot : oneshot ::Receiver < ( ) > ,
spawn_handler : & mut SH ,
) -> Result < ( ) , ServerError >
where
F : Send + Future < Output = anyhow ::Result < http_serde ::Response > > ,
H : Send + 'static + FnOnce ( http_serde ::RequestParts ) -> F ,
SH : Send + FnMut ( ) -> H
{
let mut backoff_delay = 0 ;
let mut shutdown_oneshot = shutdown_oneshot . fuse ( ) ;
2022-02-05 16:52:56 +00:00
for i in 0 u64 .. {
2021-05-09 19:29:15 +00:00
// TODO: Extract loop body to function?
if backoff_delay > 0 {
let sleep = tokio ::time ::sleep ( Duration ::from_millis ( backoff_delay ) ) ;
tokio ::pin! ( sleep ) ;
tokio ::select! {
_ = & mut sleep = > { } ,
_ = & mut shutdown_oneshot = > {
info! ( " Received graceful shutdown " ) ;
break ;
} ,
2020-11-06 23:43:52 +00:00
}
2021-05-09 19:29:15 +00:00
}
2022-02-05 16:52:56 +00:00
debug! ( " http_listen {}... " , i ) ;
2021-05-09 19:29:15 +00:00
2022-02-05 16:52:56 +00:00
let http_listen_fut = Self ::http_listen ( state ) ;
2021-05-09 19:29:15 +00:00
2022-02-05 16:52:56 +00:00
let http_listen = futures ::select! {
r = http_listen_fut . fuse ( ) = > r ,
2021-05-09 19:29:15 +00:00
_ = shutdown_oneshot = > {
info! ( " Received graceful shutdown " ) ;
break ;
} ,
} ;
2022-02-05 16:52:56 +00:00
let err_backoff_delay = std ::cmp ::min ( 30_000 , backoff_delay * 2 + 500 ) ;
let reqs = match http_listen {
2021-05-09 19:29:15 +00:00
Err ( e ) = > {
2022-02-05 16:52:56 +00:00
backoff_delay = err_backoff_delay ;
error! ( " http_listen {} error, backing off... {:?} " , i , e ) ;
2021-05-09 19:29:15 +00:00
continue ;
} ,
Ok ( x ) = > x ,
} ;
2022-02-05 16:52:56 +00:00
debug! ( " http_listen {} unwrapped {} requests " , i , reqs . len ( ) ) ;
2021-05-09 19:29:15 +00:00
// Unpack the requests, spawn them into new tasks, then loop back
// around.
if handle_requests (
& state ,
2022-02-05 16:52:56 +00:00
reqs ,
2021-05-09 19:29:15 +00:00
spawn_handler ,
) . await . is_err ( ) {
2020-11-06 23:43:52 +00:00
backoff_delay = err_backoff_delay ;
2021-05-09 19:29:15 +00:00
continue ;
2020-11-06 23:43:52 +00:00
}
2021-05-09 18:32:24 +00:00
2021-05-09 19:29:15 +00:00
if backoff_delay ! = 0 {
debug! ( " backoff_delay = 0 " ) ;
backoff_delay = 0 ;
2021-05-09 18:32:24 +00:00
}
2020-11-08 03:16:13 +00:00
}
2021-05-09 19:29:15 +00:00
info! ( " Exiting " ) ;
Ok ( ( ) )
2020-10-30 23:02:57 +00:00
}
}
2020-11-24 23:56:43 +00:00
2021-10-02 17:26:56 +00:00
pub mod executable {
use std ::{
path ::{ Path , PathBuf } ,
} ;
use structopt ::StructOpt ;
use super ::{
load_toml ,
prelude ::* ,
} ;
2021-10-02 18:13:14 +00:00
pub async fn main ( args : & [ OsString ] ) -> anyhow ::Result < ( ) > {
2021-10-02 17:31:06 +00:00
let opt = Opt ::from_iter ( args ) ;
2021-10-02 17:26:56 +00:00
let asset_root = opt . asset_root ;
let path = opt . config_path . clone ( ) . unwrap_or_else ( | | PathBuf ::from ( " ./config/ptth_server.toml " ) ) ;
let config_file : ConfigFile = if opt . auto_gen_key {
// If we're in autonomous mode, try harder to fix things
match load_toml ::load ( & path ) {
Err ( _ ) = > {
gen_and_save_key ( & path ) ? ;
load_toml ::load ( & path ) ?
} ,
Ok ( x ) = > x ,
}
}
else {
match load_toml ::load ( & path ) {
Err ( super ::errors ::LoadTomlError ::Io ( _ ) ) = > bail! ( " API key not provided in config file and auto-gen-key not provided " ) ,
Ok ( x ) = > x ,
Err ( e ) = > return Err ( e . into ( ) ) ,
}
} ;
let config_file = super ::ConfigFile {
name : opt . name . or ( config_file . name ) . ok_or ( anyhow ::anyhow! ( " `name` must be provided in command line or config file " ) ) ? ,
api_key : config_file . api_key ,
relay_url : opt . relay_url . or ( config_file . relay_url ) . ok_or ( anyhow ::anyhow! ( " `--relay-url` must be provided in command line or `relay_url` in config file " ) ) ? ,
2021-10-14 23:35:31 +00:00
file_server_root : opt . file_server_root . or ( config_file . file_server_root ) . unwrap_or_else ( PathBuf ::new ) ,
2022-03-25 20:30:38 +00:00
file_server_roots : vec ! [
( " c " , " C:/ " ) ,
( " d " , " D:/ " ) ,
] . into_iter ( )
. map ( | ( k , v ) | ( String ::from ( k ) , PathBuf ::from ( v ) ) )
. collect ( ) ,
2021-10-02 17:26:56 +00:00
throttle_upload : opt . throttle_upload ,
2021-10-14 23:35:31 +00:00
allow_any_client : true ,
client_keys : Default ::default ( ) ,
2021-10-02 17:26:56 +00:00
} ;
if opt . print_tripcode {
println! ( r # "name = "{}""# , config_file . name ) ;
println! ( r # "tripcode = "{}""# , config_file . tripcode ( ) ) ;
return Ok ( ( ) ) ;
}
super ::run_server (
config_file ,
ptth_core ::graceful_shutdown ::init ( ) ,
Some ( path ) ,
asset_root
) . await ? ;
Ok ( ( ) )
}
#[ derive (Debug, StructOpt) ]
struct Opt {
#[ structopt (long) ]
auto_gen_key : bool ,
#[ structopt (long) ]
throttle_upload : bool ,
#[ structopt (long) ]
file_server_root : Option < PathBuf > ,
#[ structopt (long) ]
asset_root : Option < PathBuf > ,
#[ structopt (long) ]
config_path : Option < PathBuf > ,
#[ structopt (long) ]
name : Option < String > ,
#[ structopt (long) ]
print_tripcode : bool ,
#[ structopt (long) ]
relay_url : Option < String > ,
}
#[ derive (Default, serde::Deserialize) ]
struct ConfigFile {
pub name : Option < String > ,
pub api_key : String ,
pub relay_url : Option < String > ,
pub file_server_root : Option < PathBuf > ,
}
fn gen_and_save_key ( path : & Path ) -> anyhow ::Result < ( ) > {
use std ::fs ::File ;
let api_key = ptth_core ::gen_key ( ) ;
let mut f = File ::create ( path ) . with_context ( | | format! ( " Can't create config file ` {:?} ` " , path ) ) ? ;
#[ cfg (unix) ]
{
use std ::os ::unix ::fs ::PermissionsExt ;
let metadata = f . metadata ( ) ? ;
let mut permissions = metadata . permissions ( ) ;
permissions . set_mode ( 0o600 ) ;
f . set_permissions ( permissions ) ? ;
}
#[ cfg (not (unix)) ]
{
tracing ::warn! ( " Error VR6VW5QT: API keys aren't protected from clients on non-Unix OSes yet " ) ;
}
f . write_all ( format! ( " api_key = \" {} \" \n " , api_key ) . as_bytes ( ) ) ? ;
Ok ( ( ) )
}
}
2020-11-24 23:56:43 +00:00
#[ cfg (test) ]
mod tests {
use super ::* ;
#[ test ]
fn tripcode_algo ( ) {
2021-03-21 02:49:44 +00:00
let config = ConfigFile ::new (
" TestName " . into ( ) ,
" PlaypenCausalPlatformCommodeImproveCatalyze " . into ( ) ,
" " . into ( ) ,
) ;
2020-11-24 23:56:43 +00:00
assert_eq! ( config . tripcode ( ) , " A9rPwZyY89Ag4TJjMoyYA2NeGOm99Je6rq1s0rg8PfY= " . to_string ( ) ) ;
}
}