ddd9b914cdee26c44baf15e51526366fdecdfc7b
[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 async fn main() -> std::io::Result<()> {
95 let key = {
96 // If the key file doesn't exist then create a new one with a random key in it.
97 if !Path::new(consts::FILE_KEY).exists() {
98 let new_key = crypto::generate_key();
99 write_key(&new_key);
100 println!("A key has been generated here: {}", consts::FILE_KEY);
101 new_key
102 } else {
103 read_key()
104 }
105 };
106
107 if process_args(&key) { return Ok(()) }
108
109 println!("Starting RUP as web server...");
110
111 let config = load_config();
112
113 println!("Configuration: {:?}", config);
114
115 let mut listenfd = ListenFd::from_env();
116 let mut server =
117 HttpServer::new(
118 move || {
119 let key = key.clone(); // Is this neccessary??
120
121 App::new()
122 .wrap(middleware::Compress::default())
123 .wrap(middleware::Logger::default())
124 .service(web::resource("/").to(move |query| main_page(query, &key)))
125 .service(fs::Files::new("/static", "static").show_files_listing())
126 }
127 );
128
129 server =
130 if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
131 server.listen(l).unwrap()
132 } else {
133 server.bind(&format!("0.0.0.0:{}", config.port)).unwrap()
134 };
135
136 server.run().await
137 }
138
139 fn process_args(key: &str) -> bool {
140 fn print_usage() {
141 println!("Usage:");
142 println!(" {} [--help] [--encrypt <plain-text> | --decrypt <cipher-text>]", get_exe_name());
143 }
144
145 let args: Vec<String> = args().collect();
146
147 if args.iter().any(|arg| arg == "--help") {
148 print_usage();
149 return true
150 } else if let Some((position_arg_encrypt, _)) = args.iter().find_position(|arg| arg == &"--encrypt") {
151 match args.get(position_arg_encrypt + 1) {
152 Some(mess_to_encrypt) => {
153 // Encrypt to version 2 (version 1 is obsolete).
154 match crypto::encrypt(&key, mess_to_encrypt, 2) {
155 Ok(encrypted_mess) => {
156 let encrypted_mess_encoded = percent_encoding::utf8_percent_encode(&encrypted_mess, percent_encoding::NON_ALPHANUMERIC).to_string();
157 println!("Encrypted message percent-encoded: {}", encrypted_mess_encoded); },
158 Err(error) =>
159 println!("Unable to encrypt: {:?}", error)
160 }
161 }
162 None => print_usage()
163 }
164
165 return true
166 } else if let Some((position_arg_decrypt, _)) = args.iter().find_position(|arg| arg == &"--decrypt") {
167 match args.get(position_arg_decrypt + 1) {
168 Some(cipher_text) => {
169 let cipher_text_decoded = percent_encoding::percent_decode(cipher_text.as_bytes()).decode_utf8().expect("Unable to decode encoded cipher text");
170 match crypto::decrypt(&key, &cipher_text_decoded) {
171 Ok(plain_text) =>
172 println!("Decrypted message: {}", plain_text),
173 Err(error) =>
174 println!("Unable to decrypt: {:?}", error)
175 }
176 }
177 None => print_usage()
178 }
179
180 return true
181 }
182
183 false
184 }