11#![ allow( clippy:: cast_sign_loss, clippy:: cast_possible_truncation) ]
22
3+ use core:: fmt;
34use std:: {
45 env, fs,
56 path:: { Path , PathBuf } ,
67 process,
78 str:: FromStr ,
9+ sync:: Arc ,
810 time:: Duration ,
911} ;
1012
@@ -17,13 +19,32 @@ use crate::{
1719 log_info, log_warn,
1820} ;
1921
20- #[ derive( Clone , Debug ) ]
22+ #[ derive( Clone ) ]
2123pub struct ServerConfig {
2224 pub port : u16 ,
2325 pub pool_conf : PoolConfig ,
2426 pub keep_alive_timeout : Duration ,
2527 pub keep_alive_requests : u16 ,
2628 pub log_file : Option < String > ,
29+
30+ #[ cfg( feature = "tls" ) ]
31+ pub tls_config : Option < Arc < rustls:: ServerConfig > > ,
32+ }
33+
34+ impl fmt:: Debug for ServerConfig {
35+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
36+ let mut deb = f. debug_struct ( "ServerConfig" ) ;
37+ deb. field ( "port" , & self . port )
38+ . field ( "pool_conf" , & self . pool_conf )
39+ . field ( "keep_alive_timeout" , & self . keep_alive_timeout )
40+ . field ( "keep_alive_requests" , & self . keep_alive_requests )
41+ . field ( "log_file" , & self . log_file ) ;
42+
43+ #[ cfg( feature = "tls" ) ]
44+ deb. field ( "tls" , & self . tls_config . is_some ( ) ) ;
45+
46+ deb. finish ( )
47+ }
2748}
2849
2950#[ cfg( not( test) ) ]
@@ -51,6 +72,31 @@ fn get_default_conf_file() -> Option<PathBuf> {
5172 None
5273}
5374
75+ #[ cfg( feature = "tls" ) ]
76+ #[ allow( clippy:: unwrap_used) ]
77+ fn get_tls_config ( cert : Option < String > , pkey : Option < String > ) -> Result < Arc < rustls:: ServerConfig > > {
78+ use rustls:: pki_types:: { CertificateDer , PrivateKeyDer , pem:: PemObject } ;
79+
80+ let Some ( cert) = cert else {
81+ return Err ( "Missing certificate file" . into ( ) ) ;
82+ } ;
83+ let Some ( pkey) = pkey else {
84+ return Err ( "Missing private key file" . into ( ) ) ;
85+ } ;
86+
87+ let certs = CertificateDer :: pem_file_iter ( cert)
88+ . unwrap ( )
89+ . map ( |cert| cert. unwrap ( ) )
90+ . collect ( ) ;
91+ let private_key = PrivateKeyDer :: from_pem_file ( pkey) . unwrap ( ) ;
92+ let config = rustls:: ServerConfig :: builder ( )
93+ . with_no_client_auth ( )
94+ . with_single_cert ( certs, private_key)
95+ . map_err ( |err| format ! ( "rustls: {err}" ) ) ?;
96+
97+ Ok ( Arc :: new ( config) )
98+ }
99+
54100/// [`crate::HttpServer`] configuration
55101///
56102/// # Example
@@ -98,6 +144,15 @@ impl ServerConfig {
98144 conf. parse_conf_file ( & cfile) ?;
99145 }
100146
147+ let mut pool_conf_builder = PoolConfig :: builder ( ) ;
148+
149+ #[ cfg( feature = "tls" ) ]
150+ let mut tls = false ;
151+ #[ cfg( feature = "tls" ) ]
152+ let mut cert: Option < String > = None ;
153+ #[ cfg( feature = "tls" ) ]
154+ let mut privkey: Option < String > = None ;
155+
101156 let mut args = args. iter ( ) ;
102157 while let Some ( arg) = args. next ( ) {
103158 macro_rules! parse_next {
@@ -112,8 +167,6 @@ impl ServerConfig {
112167 } } ;
113168 }
114169
115- let mut pool_conf_builder = PoolConfig :: builder ( ) ;
116-
117170 match arg. as_ref ( ) {
118171 "-p" | "--port" => conf. port = parse_next ! ( ) ,
119172 "-n" | "-n-workers" => {
@@ -134,19 +187,34 @@ impl ServerConfig {
134187 let n: u8 = parse_next ! ( ) ;
135188 log:: set_level ( n. try_into ( ) ?) ;
136189 }
190+ #[ cfg( feature = "tls" ) ]
191+ "--tls" => tls = true ,
192+
193+ #[ cfg( feature = "tls" ) ]
194+ "--cert-file" => cert = Some ( parse_next ! ( ) ) ,
195+
196+ #[ cfg( feature = "tls" ) ]
197+ "--private-key" => privkey = Some ( parse_next ! ( ) ) ,
198+
137199 CONFIG_FILE_ARG => {
138200 let _ = args. next ( ) ;
139201 }
140202 "-h" | "--help" => help ( ) ,
141203 unknown => return Err ( format ! ( "Unknow argument: {unknown}" ) . into ( ) ) ,
142204 }
205+ }
206+
207+ conf. pool_conf = pool_conf_builder. build ( ) ;
143208
144- conf. pool_conf = pool_conf_builder. build ( ) ;
209+ #[ cfg( feature = "tls" ) ]
210+ if conf. tls_config . is_none ( ) && tls {
211+ conf. tls_config = Some ( get_tls_config ( cert, privkey) ?) ;
145212 }
146213
147214 log_info ! ( "{conf:#?}" ) ;
148215 Ok ( conf)
149216 }
217+ #[ allow( clippy:: too_many_lines) ]
150218 fn parse_conf_file ( & mut self , conf_file : & Path ) -> crate :: Result < ( ) > {
151219 if !conf_file. exists ( ) {
152220 return Ok ( ( ) ) ;
@@ -164,6 +232,16 @@ impl ServerConfig {
164232 let Json :: Object ( obj) = json else {
165233 return Err ( "Expected json object" . into ( ) ) ;
166234 } ;
235+
236+ #[ cfg( feature = "tls" ) ]
237+ let mut tls = false ;
238+
239+ #[ cfg( feature = "tls" ) ]
240+ let mut cert: Option < String > = None ;
241+
242+ #[ cfg( feature = "tls" ) ]
243+ let mut privkey: Option < String > = None ;
244+
167245 for ( k, v) in obj {
168246 macro_rules! num {
169247 ( ) => {
@@ -179,14 +257,24 @@ impl ServerConfig {
179257 _n as $t
180258 } } ;
181259 }
260+ macro_rules! bool {
261+ ( $v: ident) => {
262+ $v. boolean( ) . ok_or_else( || {
263+ format!( "Parsing config file ({conf_str}): Expected boolean for \" {k}\" " )
264+ } ) ?
265+ } ;
266+ }
182267 macro_rules! string {
183- ( ) => {
184- v. string( )
268+ ( $v : ident ) => {
269+ $ v. string( )
185270 . ok_or_else( || {
186271 format!( "Parsing config file ({conf_str}): Expected string for \" {k}\" " )
187272 } ) ?
188273 . to_string( )
189274 } ;
275+ ( ) => {
276+ string!( v)
277+ } ;
190278 }
191279 macro_rules! obj {
192280 ( ) => {
@@ -196,15 +284,25 @@ impl ServerConfig {
196284 } ;
197285 }
198286
199- match & * k {
200- "port" => self . port = num ! ( ) as u16 ,
201- "root_dir" => {
202- let path: String = string ! ( ) ;
203- let path = path. replacen (
287+ macro_rules! path {
288+ ( $v: ident) => { {
289+ let path: String = string!( $v) ;
290+ path. replacen(
204291 '~' ,
205292 env:: var( "HOME" ) . as_ref( ) . map( String :: as_str) . unwrap_or( "~" ) ,
206293 1 ,
207- ) ;
294+ )
295+ } } ;
296+
297+ ( ) => {
298+ path!( v)
299+ } ;
300+ }
301+
302+ match & * k {
303+ "port" => self . port = num ! ( ) as u16 ,
304+ "root_dir" => {
305+ let path = path ! ( ) ;
208306 env:: set_current_dir ( Path :: new ( & path) ) ?;
209307 }
210308 "keep_alive_timeout" => self . keep_alive_timeout = Duration :: from_secs_f64 ( num ! ( ) ) ,
@@ -214,6 +312,19 @@ impl ServerConfig {
214312 let n = num ! ( v as u8 ) ;
215313 log:: set_level ( n. try_into ( ) ?) ;
216314 }
315+ #[ cfg( feature = "tls" ) ]
316+ "tls" => {
317+ for ( k, v) in obj ! ( ) {
318+ match & * * k {
319+ "enabled" => tls = bool !( v) ,
320+ "cert_file" => cert = Some ( path ! ( v) ) ,
321+ "private_key" => privkey = Some ( path ! ( v) ) ,
322+ _ => log_warn ! (
323+ "Parsing config file ({conf_str}): Unexpected key: \" {k}\" "
324+ ) ,
325+ }
326+ }
327+ }
217328 "pool_config" => {
218329 for ( k, v) in obj ! ( ) {
219330 match & * * k {
@@ -231,6 +342,12 @@ impl ServerConfig {
231342 _ => log_warn ! ( "Parsing config file ({conf_str}): Unexpected key: \" {k}\" " ) ,
232343 }
233344 }
345+
346+ #[ cfg( feature = "tls" ) ]
347+ if tls {
348+ self . tls_config = Some ( get_tls_config ( cert, privkey) ?) ;
349+ }
350+
234351 Ok ( ( ) )
235352 }
236353 #[ inline]
@@ -260,7 +377,8 @@ impl ServerConfig {
260377}
261378
262379fn help ( ) -> ! {
263- println ! (
380+ /* FIXME: Don't output tls options if the tls feature is disabled */
381+ println ! ( concat!(
264382 "\
265383 http-srv: Copyright (C) 2025 Saúl Valdelvira
266384
@@ -281,11 +399,15 @@ PARAMETERS:
281399 --log-level <n> Set log level
282400 --conf <file> Use the given config file instead of the default one
283401 --license Output the license of this program
402+
403+ --tls Enable TLS
404+ --cert-file Certificate file for TLS
405+ --private-key Private key for TLS
284406EXAMPLES:
285407 http-srv -p 8080 -d /var/html
286408 http-srv -d ~/desktop -n 1024 --keep-alive 120
287409 http-srv --log /var/log/http-srv.log"
288- ) ;
410+ ) ) ;
289411 process:: exit ( 0 ) ;
290412}
291413
@@ -322,6 +444,9 @@ impl Default for ServerConfig {
322444 keep_alive_timeout : Duration :: from_secs ( 0 ) ,
323445 keep_alive_requests : 10000 ,
324446 log_file : None ,
447+
448+ #[ cfg( feature = "tls" ) ]
449+ tls_config : None ,
325450 }
326451 }
327452}
0 commit comments