1use std;
2use std::io::Write;
3
4use chrono::{DateTime, Local, Utc};
5use walkdir::WalkDir;
6
7use crate::{constant, squire};
8use crate::squire::settings;
9
10pub fn init_logger(debug: bool, utc: bool, crate_name: &String) {
17 if debug {
18 std::env::set_var("RUST_LOG", format!(
19 "actix_web=debug,actix_server=info,{}=debug", crate_name
20 ));
21 std::env::set_var("RUST_BACKTRACE", "1");
22 } else {
23 std::env::set_var("RUST_LOG", format!(
25 "actix_web=warn,actix_server=warn,{}=info", crate_name
26 ));
27 std::env::set_var("RUST_BACKTRACE", "0");
28 }
29 if utc {
30 env_logger::init();
31 } else {
32 env_logger::Builder::from_default_env()
33 .format(|buf, record| {
34 let local_time: DateTime<Local> = Local::now();
35 writeln!(
36 buf,
37 "[{} {} {}] - {}",
38 local_time.format("%Y-%m-%dT%H:%M:%SZ"),
39 record.level(),
40 record.target(),
41 record.args()
42 )
43 })
44 .init();
45 }
46}
47
48fn mandatory_vars() -> (std::collections::HashMap<String, String>, std::path::PathBuf) {
58 let authorization_str = match std::env::var("authorization") {
59 Ok(val) => val,
60 Err(_) => {
61 panic!(
62 "\nauthorization\n\texpected a HashMap, received null [value=missing]\n",
63 );
64 }
65 };
66 let authorization: std::collections::HashMap<String, String> =
67 match serde_json::from_str(&authorization_str) {
68 Ok(val) => val,
69 Err(_) => {
70 panic!(
71 "\nauthorization\n\terror parsing JSON [value=invalid]\n",
72 );
73 }
74 };
75 let media_source_str = match std::env::var("media_source") {
76 Ok(val) => val,
77 Err(_) => {
78 panic!(
79 "\nmedia_source\n\texpected a directory path, received null [value=missing]\n",
80 );
81 }
82 };
83 (authorization, std::path::PathBuf::from(media_source_str))
84}
85
86fn parse_bool(key: &str) -> Option<bool> {
100 match std::env::var(key) {
101 Ok(val) => match val.parse() {
102 Ok(parsed) => Some(parsed),
103 Err(_) => {
104 panic!("\n{}\n\texpected bool, received '{}' [value=invalid]\n", key, val);
105 }
106 },
107 Err(_) => None,
108 }
109}
110
111fn parse_i64(key: &str) -> Option<i64> {
125 match std::env::var(key) {
126 Ok(val) => match val.parse() {
127 Ok(parsed) => Some(parsed),
128 Err(_) => {
129 panic!("\n{}\n\texpected i64, received '{}' [value=invalid]\n", key, val);
130 }
131 },
132 Err(_) => None,
133 }
134}
135
136fn parse_u16(key: &str) -> Option<u16> {
150 match std::env::var(key) {
151 Ok(val) => match val.parse() {
152 Ok(parsed) => Some(parsed),
153 Err(_) => {
154 panic!("\n{}\n\texpected u16, received '{}' [value=invalid]\n", key, val);
155 }
156 },
157 Err(_) => None,
158 }
159}
160
161fn parse_usize(key: &str) -> Option<usize> {
175 match std::env::var(key) {
176 Ok(val) => match val.parse() {
177 Ok(parsed) => Some(parsed),
178 Err(_) => {
179 panic!("\n{}\n\texpected usize, received '{}' [value=invalid]\n", key, val);
180 }
181 },
182 Err(_) => None,
183 }
184}
185
186fn parse_vec(key: &str) -> Option<Vec<String>> {
200 match std::env::var(key) {
201 Ok(val) => match serde_json::from_str::<Vec<String>>(&val) {
202 Ok(parsed) => Some(parsed),
203 Err(_) => {
204 panic!("\n{}\n\texpected vec, received '{}' [value=invalid]\n", key, val);
205 }
206 },
207 Err(_) => None,
208 }
209}
210
211fn parse_path(key: &str) -> Option<std::path::PathBuf> {
221 match std::env::var(key) {
222 Ok(value) => {
223 Some(std::path::PathBuf::from(value))
224 }
225 Err(_) => {
226 None
227 }
228 }
229}
230
231fn parse_max_payload(key: &str) -> Option<usize> {
247 match std::env::var(key) {
248 Ok(value) => {
249
250 let custom_hook = std::panic::take_hook();
251 std::panic::set_hook(Box::new(|_panic_info| {}));
252 let result = std::panic::catch_unwind(|| parse_memory(&value));
253 std::panic::set_hook(custom_hook);
254
255 match result {
256 Ok(output) => {
257 if let Some(value) = output {
258 Some(value)
259 } else {
260 panic!("\n{}\n\texpected format: '100 MB', received '{}' [value=invalid]\n",
261 key, value);
262 }
263 }
264 Err(panic_payload) => {
265 if let Some(&error) = panic_payload.downcast_ref::<&str>() {
266 panic!("\n{}\n\t{} [value=invalid]\n", key, error);
267 } else if let Some(error) = panic_payload.downcast_ref::<String>() {
268 panic!("\n{}\n\t{} [value=invalid]\n", key, error);
269 } else if let Some(error) = panic_payload.downcast_ref::<Box<dyn std::fmt::Debug + Send + 'static>>() {
270 panic!("\n{}\n\t{:?} [value=invalid]\n", key, error);
271 } else {
272 panic!("\n{}\n\tinvalid memory format! unable to parse panic payload [value=invalid]\n", key);
273 }
274 }
275 }
276 }
277 Err(_) => {
278 None
279 }
280 }
281}
282
283fn parse_memory(memory: &str) -> Option<usize> {
284 let value = memory.trim();
285 let (size_str, unit) = value.split_at(value.len() - 2);
286 let size: usize = match size_str.strip_suffix(' ').unwrap_or_default().parse() {
287 Ok(num) => num,
288 Err(_) => return None,
289 };
290
291 match unit.to_lowercase().as_str() {
292 "zb" => Some(size * 1024 * 1024 * 1024 * 1024 * 1024),
293 "tb" => Some(size * 1024 * 1024 * 1024 * 1024),
294 "gb" => Some(size * 1024 * 1024 * 1024),
295 "mb" => Some(size * 1024 * 1024),
296 "kb" => Some(size * 1024),
297 _ => None,
298 }
299}
300
301fn load_env_vars() -> settings::Config {
307 let (authorization, media_source) = mandatory_vars();
308 let debug = parse_bool("debug").unwrap_or(settings::default_debug());
309 let utc_logging = parse_bool("utc_logging").unwrap_or(settings::default_utc_logging());
310 let media_host = std::env::var("media_host").unwrap_or(settings::default_media_host());
311 let media_port = parse_u16("media_port").unwrap_or(settings::default_media_port());
312 let session_duration = parse_i64("session_duration").unwrap_or(settings::default_session_duration());
313 let file_formats = parse_vec("file_formats").unwrap_or(settings::default_file_formats());
314 let workers = parse_usize("workers").unwrap_or(settings::default_workers());
315 let max_connections = parse_usize("max_connections").unwrap_or(settings::default_max_connections());
316 let websites = parse_vec("websites").unwrap_or(settings::default_websites());
317 let secure_session = parse_bool("secure_session").unwrap_or(settings::default_secure_session());
318 let key_file = parse_path("key_file").unwrap_or(settings::default_ssl());
319 let cert_file = parse_path("cert_file").unwrap_or(settings::default_ssl());
320 let max_payload_size = parse_max_payload("max_payload_size").unwrap_or(settings::default_max_payload_size());
321 settings::Config {
322 authorization,
323 media_source,
324 debug,
325 utc_logging,
326 media_host,
327 media_port,
328 session_duration,
329 file_formats,
330 workers,
331 max_connections,
332 max_payload_size,
333 websites,
334 secure_session,
335 key_file,
336 cert_file,
337 }
338}
339
340fn get_time(utc: bool) -> String {
350 if utc {
351 Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string()
352 } else {
353 Local::now().format("%Y-%m-%dT%H:%M:%SZ").to_string()
354 }
355}
356
357fn validate_dir_structure(config: &settings::Config, metadata: &constant::MetaData) {
364 let source = &config.media_source.to_string_lossy().to_string();
365 let mut errors = String::new();
366 for entry in WalkDir::new(&config.media_source).into_iter().filter_map(|e| e.ok()) {
367 let entry_path = entry.path();
368 if entry_path.is_dir() && entry_path.to_str().unwrap().ends_with(constant::SECURE_INDEX) {
369 let secure_index = entry_path.strip_prefix(source).unwrap();
370 let depth = secure_index.iter().count();
371 if depth != 1usize {
372 let index_vec = secure_index.iter().collect::<Vec<_>>();
373 let secure_dir = index_vec.last().unwrap();
374 let secure_parent_path = &index_vec[0..index_vec.len() - 1]
376 .join(std::ffi::OsStr::new(std::path::MAIN_SEPARATOR_STR));
377 errors.push_str(&format!(
378 "\n{:?}\n\tSecure index directory [{:?}] should be at the root [{:?}] [depth={}, valid=1]\n\
379 \t> Hint: Either move {:?} within {:?}, [OR] set the 'media_source' to {:?}\n",
380 secure_index,
381 secure_dir,
382 config.media_source,
383 depth,
384 secure_dir,
385 config.media_source,
386 config.media_source.join(secure_parent_path)
387 ));
388 }
389 }
390 }
391 if errors.is_empty() {
392 for username in config.authorization.keys() {
393 let secure_path = &config.media_source.join(format!("{}_{}", &username, constant::SECURE_INDEX));
394 if !secure_path.exists() {
395 match std::fs::create_dir(secure_path) {
396 Ok(_) => {
397 if config.utc_logging {
399 println!("[{}\x1b[32m INFO\x1b[0m {}] '{}' has been created",
400 get_time(config.utc_logging), metadata.crate_name,
401 &secure_path.to_str().unwrap())
402 } else {
403 println!("[{} INFO {}] '{}' has been created",
404 get_time(config.utc_logging), metadata.crate_name,
405 &secure_path.to_str().unwrap())
406 }
407 }
408 Err(err) => panic!("{}", err)
409 }
410 }
411 }
412 } else {
413 panic!("{}", errors)
414 }
415}
416
417fn validate_vars(metadata: &constant::MetaData) -> settings::Config {
427 let config = load_env_vars();
428 let mut errors = "".to_owned();
429 if !config.media_source.exists() || !config.media_source.is_dir() {
430 let err1 = format!(
431 "\nmedia_source\n\tInput [{}] is not a valid directory [value=invalid]\n",
432 config.media_source.to_string_lossy()
433 );
434 errors.push_str(&err1);
435 }
436 for (username, password) in &config.authorization {
437 if username.len() < 4 {
438 let err2 = format!(
439 "\nauthorization\n\t[{}: {}] username should be at least 4 or more characters [value=invalid]\n",
440 username, "*".repeat(password.len())
441 );
442 errors.push_str(&err2);
443 }
444 if password.len() < 8 {
445 let err3 = format!(
446 "\nauthorization\n\t[{}: {}] password should be at least 8 or more characters [value=invalid]\n",
447 username, "*".repeat(password.len())
448 );
449 errors.push_str(&err3);
450 }
451 }
452 if !errors.is_empty() {
453 panic!("{}", errors);
454 }
455 validate_dir_structure(&config, metadata);
456 config
457}
458
459pub fn get_config(metadata: &constant::MetaData) -> std::sync::Arc<settings::Config> {
469 let mut env_file = squire::parser::arguments(metadata);
470 if env_file.is_empty() {
471 env_file = std::env::var("env_file")
472 .unwrap_or(std::env::var("ENV_FILE")
473 .unwrap_or(".env".to_string()));
474 }
475 let env_file_path = std::env::current_dir()
476 .unwrap_or_default()
477 .join(env_file);
478 let _ = dotenv::from_path(env_file_path.as_path());
479 std::sync::Arc::new(validate_vars(metadata))
480}