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