Automatically create a default file config if it doesn't exist.
[rup.git] / src / main.rs
1
2 extern crate listenfd;
3 extern crate askama;
4 extern crate percent_encoding;
5
6 use listenfd::ListenFd;
7 use actix_files as fs;
8 use actix_web::{ web, middleware, App, HttpServer, HttpResponse, web::Query };
9 use askama::Template;
10
11 use std::{ fs::File, path::Path, env::args, io::prelude::* };
12 use ron::{ de::from_reader, ser::{ to_string_pretty, PrettyConfig } };
13 use serde::{ Deserialize, Serialize };
14
15 use itertools::Itertools;
16
17 mod consts;
18 mod crypto;
19
20 #[derive(Template)]
21 #[template(path = "main.html")]
22 struct MainTemplate<'a> {
23 sentence: &'a str,
24 }
25
26 #[derive(Deserialize)]
27 pub struct Request {
28 m: Option<String>
29 }
30
31 fn main_page(query: Query<Request>, key: &str) -> HttpResponse {
32 let m =
33 match &query.m {
34 Some(b) =>
35 match crypto::decrypt(key, b) {
36 Ok(m) => m,
37 Err(_e) => String::from(consts::DEFAULT_MESSAGE) // TODO: log error.
38 },
39 None => String::from(consts::DEFAULT_MESSAGE)
40 };
41
42 let hello = MainTemplate { sentence: &m };
43
44 let s = hello.render().unwrap();
45 HttpResponse::Ok().content_type("text/html").body(s)
46 }
47
48 #[derive(Debug, Deserialize, Serialize)]
49 struct Config {
50 port: u16
51 }
52
53 const DEFAULT_CONFIG: Config = Config { port: 8082 };
54
55 fn get_exe_name() -> String {
56 let first_arg = std::env::args().next().unwrap();
57 let sep: &[_] = &['\\', '/'];
58 first_arg[first_arg.rfind(sep).unwrap()+1..].to_string()
59 }
60
61 fn load_config() -> Config {
62 // unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF));
63 match File::open(consts::FILE_CONF) {
64 Ok(file) => from_reader(file).unwrap_or_else(|_| panic!("Failed to open configuration file {}", consts::FILE_CONF)),
65 Err(_) => {
66 let mut file = File::create(consts::FILE_CONF) .unwrap();
67 file.write_all(to_string_pretty(&DEFAULT_CONFIG, PrettyConfig::new()).unwrap().as_bytes()).unwrap(); // We do not use 'to_writer' because it can't pretty format the output.
68 DEFAULT_CONFIG
69 }
70 }
71 }
72
73 fn read_key() -> String {
74 let mut key = String::new();
75 File::open(consts::FILE_KEY)
76 .unwrap_or_else(|_| panic!("Failed to open key file: {}", consts::FILE_KEY))
77 .read_to_string(&mut key)
78 .unwrap_or_else(|_| panic!("Failed to read key file: {}", consts::FILE_KEY));
79
80 String::from(
81 percent_encoding::percent_decode(key.replace('\n', "").as_bytes())
82 .decode_utf8()
83 .unwrap_or_else(|_| panic!("Failed to decode key file: {}", consts::FILE_KEY))
84 )
85 }
86
87 fn write_key(key : &str) {
88 let percent_encoded = percent_encoding::utf8_percent_encode(key, percent_encoding::NON_ALPHANUMERIC).to_string();
89 let mut file = File::create(consts::FILE_KEY).unwrap();
90 file.write_all(percent_encoded.as_bytes()).unwrap();
91 }
92
93 #[actix_rt::main]
94
95 async fn main() -> std::io::Result<()> {
96 let key = {
97 // If the key file doesn't exist then create a new one with a random key in it.
98 if !Path::new(consts::FILE_KEY).exists() {
99 let new_key = crypto::generate_key();
100 write_key(&new_key);
101 println!("A key has been generated here: {}", consts::FILE_KEY);
102 new_key
103 } else {
104 read_key()
105 }
106 };
107
108 if process_args(&key) { return Ok(()) }
109
110 println!("Starting RUP as web server...");
111
112 let config = load_config();
113
114 println!("Configuration: {:?}", config);
115
116 let mut listenfd = ListenFd::from_env();
117 let mut server =
118 HttpServer::new(
119 move || {
120 let key = key.clone(); // Is this neccessary??
121
122 App::new()
123 .wrap(middleware::Compress::default())
124 .wrap(middleware::Logger::default())
125 .service(web::resource("/").to(move |query| main_page(query, &key)))
126 .service(fs::Files::new("/static", "static").show_files_listing())
127 }
128 );
129
130 server =
131 if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
132 server.listen(l).unwrap()
133 } else {
134 server.bind(&format!("0.0.0.0:{}", config.port)).unwrap()
135 };
136
137 server.run().await
138 }
139
140 fn process_args(key: &str) -> bool {
141 fn print_usage() {
142 println!("Usage:");
143 println!(" {} [--help] [--encrypt <plain-text> | --decrypt <cipher-text>]", get_exe_name());
144 }
145
146 let args: Vec<String> = args().collect();
147
148 if args.iter().any(|arg| arg == "--help") {
149 print_usage();
150 return true
151 } else if let Some((position_arg_encrypt, _)) = args.iter().find_position(|arg| arg == &"--encrypt") {
152 match args.get(position_arg_encrypt + 1) {
153 Some(mess_to_encrypt) => {
154 // Encrypt to version 2 (version 1 is obsolete).
155 match crypto::encrypt(&key, mess_to_encrypt, 2) {
156 Ok(encrypted_mess) => {
157 let encrypted_mess_encoded = percent_encoding::utf8_percent_encode(&encrypted_mess, percent_encoding::NON_ALPHANUMERIC).to_string();
158 println!("Encrypted message percent-encoded: {}", encrypted_mess_encoded); },
159 Err(error) =>
160 println!("Unable to encrypt: {:?}", error)
161 }
162 }
163 None => print_usage()
164 }
165
166 return true
167 } else if let Some((position_arg_decrypt, _)) = args.iter().find_position(|arg| arg == &"--decrypt") {
168 match args.get(position_arg_decrypt + 1) {
169 Some(cipher_text) => {
170 let cipher_text_decoded = percent_encoding::percent_decode(cipher_text.as_bytes()).decode_utf8().expect("Unable to decode encoded cipher text");
171 match crypto::decrypt(&key, &cipher_text_decoded) {
172 Ok(plain_text) =>
173 println!("Decrypted message: {}", plain_text),
174 Err(error) =>
175 println!("Unable to decrypt: {:?}", error)
176 }
177 }
178 None => print_usage()
179 }
180
181 return true
182 }
183
184 false
185 }