0.7.0: Ununsafe urouter

Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
Ivan Bushchik 2024-03-17 15:45:50 +03:00
parent 653b41a67f
commit 0451d494c5
No known key found for this signature in database
GPG key ID: 2F16FBF3262E090C
2 changed files with 62 additions and 67 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "urouter" name = "urouter"
version = "0.6.1" version = "0.7.0"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/ivabus/urouter" repository = "https://github.com/ivabus/urouter"
@ -9,11 +9,11 @@ description = "Small HTTP router"
[dependencies] [dependencies]
rocket = "0.5.0" rocket = "0.5.0"
serde = { version = "1.0.193", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.108" serde_json = "1.0.114"
url-escape = "0.1.1" url-escape = "0.1.1"
smurf = "0.3.0" once_cell = "1.19"
clap = { version = "4.4.11", features = ["derive"] } clap = { version = "4.5.3", features = ["derive"] }
regex = "1.10.2" regex = "1.10.3"
ureq = { version = "2.9.1", features = ["brotli", "native-certs"] } ureq = { version = "2.9.6", features = ["brotli", "native-certs"] }
users = "0.11.0" users = "0.11.0"

View file

@ -6,29 +6,62 @@ Global comments:
500 Internal Server Error 500 Internal Server Error
*/ */
#![forbid(unsafe_code)]
mod structs; mod structs;
use structs::*; use structs::*;
#[macro_use] #[macro_use]
extern crate rocket; extern crate rocket;
use rocket::http::{ContentType, Status};
use std::io::Write; use std::io::Write;
use std::{ use std::sync::Arc;
cell::OnceCell, collections::HashMap, hint::unreachable_unchecked, path::PathBuf, time::Instant, use std::{collections::HashMap, path::PathBuf, time::Instant};
};
use rocket::http::{ContentType, Status};
use rocket::{ use rocket::{
figment::Figment, figment::Figment,
response::{content::RawText, Redirect}, response::{content::RawHtml, content::RawText, Redirect},
}; };
use clap::Parser; use clap::Parser;
use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use rocket::response::content::RawHtml;
static mut ALIAS: OnceCell<Vec<Alias>> = OnceCell::new(); static ALIAS: Lazy<Arc<Vec<Alias>>> = Lazy::new(|| {
static mut COMPILED_REGEXES: OnceCell<HashMap<String, Regex>> = OnceCell::new(); let mut args = Args::parse();
if args.alias_file.is_none() {
args.alias_file = Some(get_config_file_location());
}
let file = std::fs::File::open(args.alias_file.unwrap()).unwrap();
let alias: Vec<Alias> = if args.alias_file_is_set_not_a_list {
serde_json::from_reader::<std::fs::File, NixJson>(file).unwrap().alias
} else {
serde_json::from_reader::<std::fs::File, Vec<Alias>>(file).unwrap()
};
Arc::new(alias)
});
static COMPILED_REGEXES: Lazy<Arc<HashMap<String, Regex>>> = Lazy::new(|| {
let compilation_start = Instant::now();
let mut regexes_len = 0;
// Precompile all regexes
let mut compiled_regexes: HashMap<String, Regex> = HashMap::new();
for i in &**ALIAS {
if let Some(agent) = &i.agent {
compiled_regexes.insert(agent.regex.clone(), Regex::new(&agent.regex).unwrap());
regexes_len += 1;
}
}
if regexes_len != 0 {
println!(
"Compiled {} regexes in {} ms",
regexes_len,
(Instant::now() - compilation_start).as_secs_f64() * 1000.0
);
}
Arc::new(compiled_regexes)
});
fn get_return(alias: &Alias) -> Response { fn get_return(alias: &Alias) -> Response {
let args = Args::parse(); let args = Args::parse();
@ -37,7 +70,7 @@ fn get_return(alias: &Alias) -> Response {
AliasType::Url(url) => Response::Redirect(Box::from(Redirect::to(url.clone()))), AliasType::Url(url) => Response::Redirect(Box::from(Redirect::to(url.clone()))),
AliasType::File(path) => { AliasType::File(path) => {
dir.push(&PathBuf::from(&path)); dir.push(&PathBuf::from(&path));
Response::Text(Box::new(RawText(smurf::io::read_file_str(&dir).unwrap()))) Response::Text(Box::new(RawText(std::fs::read_to_string(&dir).unwrap())))
} }
AliasType::Text(text) => Response::Text(Box::new(RawText(text.clone()))), AliasType::Text(text) => Response::Text(Box::new(RawText(text.clone()))),
AliasType::Html(html) => Response::Html(Box::new(RawHtml(html.clone()))), AliasType::Html(html) => Response::Html(Box::new(RawHtml(html.clone()))),
@ -60,19 +93,12 @@ fn get_return(alias: &Alias) -> Response {
fn get_page(page: &str, user_agent: UserAgent) -> Response { fn get_page(page: &str, user_agent: UserAgent) -> Response {
let mut decoded_page = String::new(); let mut decoded_page = String::new();
url_escape::decode_to_string(page, &mut decoded_page); url_escape::decode_to_string(page, &mut decoded_page);
let alias = unsafe { ALIAS.get().unwrap() };
let mut pages = Vec::new(); let mut pages = Vec::new();
for i in alias { for i in &**ALIAS {
if i.uri == decoded_page { if i.uri == decoded_page {
if let Some(agent) = &i.agent { if let Some(agent) = &i.agent {
unsafe { let regexes = &COMPILED_REGEXES;
let regexes = COMPILED_REGEXES.get_mut(); let re = regexes.get(&agent.regex).unwrap();
let re = if let Some(r) = regexes {
// Unwrapping safely, guaranteed to be generated during initialization
r.get(&agent.regex).unwrap()
} else {
unreachable_unchecked()
};
if re.is_match(&user_agent.0) { if re.is_match(&user_agent.0) {
return get_return(i); return get_return(i);
@ -82,7 +108,7 @@ fn get_page(page: &str, user_agent: UserAgent) -> Response {
continue; continue;
} }
} }
}
pages.push(i); pages.push(i);
} }
} }
@ -120,40 +146,9 @@ fn get_config_file_location() -> PathBuf {
#[rocket::main] #[rocket::main]
async fn main() -> Result<(), rocket::Error> { async fn main() -> Result<(), rocket::Error> {
let mut args = Args::parse(); let args = Args::parse();
let _alias = &**ALIAS;
if args.alias_file.is_none() { let _regex = &**COMPILED_REGEXES;
args.alias_file = Some(get_config_file_location());
}
let file = std::fs::File::open(args.alias_file.unwrap()).unwrap();
let alias: Vec<Alias> = if args.alias_file_is_set_not_a_list {
serde_json::from_reader::<std::fs::File, NixJson>(file).unwrap().alias
} else {
serde_json::from_reader::<std::fs::File, Vec<Alias>>(file).unwrap()
};
unsafe {
ALIAS.set(alias).unwrap();
let compilation_start = Instant::now();
let mut regexes_len = 0;
// Precompile all regexes
let mut compiled_regexes: HashMap<String, Regex> = HashMap::new();
for i in ALIAS.get().unwrap() {
if let Some(agent) = &i.agent {
compiled_regexes.insert(agent.regex.clone(), Regex::new(&agent.regex).unwrap());
regexes_len += 1;
}
}
if regexes_len != 0 {
println!(
"Compiled {} regexes in {} ms",
regexes_len,
(Instant::now() - compilation_start).as_secs_f64() * 1000.0
);
}
COMPILED_REGEXES.set(compiled_regexes).unwrap();
}
let figment = Figment::from(rocket::Config::default()) let figment = Figment::from(rocket::Config::default())
.merge(("ident", format!("urouter/{}", env!("CARGO_PKG_VERSION")))) .merge(("ident", format!("urouter/{}", env!("CARGO_PKG_VERSION"))))