mirror of
https://github.com/ivabus/urouter
synced 2024-11-22 00:15:11 +03:00
0.5.0: Advanced agent matching
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
parent
b7c96c8b4e
commit
e84e1a3317
5 changed files with 137 additions and 105 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "urouter"
|
name = "urouter"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/ivabus/urouter"
|
repository = "https://github.com/ivabus/urouter"
|
||||||
|
@ -14,3 +14,4 @@ serde_json = "1.0.108"
|
||||||
url-escape = "0.1.1"
|
url-escape = "0.1.1"
|
||||||
smurf = "0.3.0"
|
smurf = "0.3.0"
|
||||||
clap = { version = "4.4.11", features = ["derive"] }
|
clap = { version = "4.4.11", features = ["derive"] }
|
||||||
|
regex = "1.10.2"
|
||||||
|
|
21
README.md
21
README.md
|
@ -16,28 +16,31 @@ Edit `alias.json` (or any other JSON file, check `--alias-file` option) and `car
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"uri": "uri",
|
"uri":"/",
|
||||||
"alias": {
|
"alias": {
|
||||||
"file": "somefile"
|
"url": "https://somecoolwebsite"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uri": "uri2",
|
"uri":"/",
|
||||||
"alias": {
|
"alias": {
|
||||||
"url": "http://example.com"
|
"file": "somecoolscript"
|
||||||
|
},
|
||||||
|
"agent": {
|
||||||
|
"regex": "^curl/[0-9].[0-9].[0-9]$",
|
||||||
|
"only_matching": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uri": "/",
|
"uri":"/text",
|
||||||
"alias": {
|
"alias": {
|
||||||
"url": "https://somecoolscript.sh"
|
"text": "sometext"
|
||||||
},
|
}
|
||||||
"curl_only": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
`"curl_only"` thing for `curl https://url | sh` like scripts.
|
Agent matching made for `curl https://url | sh` like scripts.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
[default]
|
|
||||||
address = "127.0.0.1"
|
|
||||||
port = 8080
|
|
||||||
keep_alive = 0
|
|
||||||
ident = "urouter"
|
|
||||||
ip_header = false
|
|
||||||
log_level = "normal"
|
|
||||||
temp_dir = "/tmp"
|
|
||||||
cli_colors = true
|
|
118
src/main.rs
118
src/main.rs
|
@ -22,93 +22,26 @@
|
||||||
* DEALINGS IN THE SOFTWARE.
|
* DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
mod structs;
|
||||||
|
|
||||||
|
use structs::*;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use std::cell::OnceCell;
|
use std::cell::{OnceCell, RefCell};
|
||||||
use std::ffi::OsStr;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rocket::request::{FromRequest, Outcome};
|
|
||||||
use rocket::response::content::RawText;
|
use rocket::response::content::RawText;
|
||||||
use rocket::response::{Redirect, Responder};
|
use rocket::response::Redirect;
|
||||||
use rocket::Request;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use regex::Regex;
|
||||||
use rocket::figment::Figment;
|
use rocket::figment::Figment;
|
||||||
|
|
||||||
static mut ALIAS: OnceCell<Vec<Alias>> = OnceCell::new();
|
static mut ALIAS: OnceCell<Vec<Alias>> = OnceCell::new();
|
||||||
|
static mut COMPILED_REGEXES: RefCell<Option<HashMap<String, Regex>>> = RefCell::new(None);
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(about, author)]
|
|
||||||
struct Args {
|
|
||||||
#[arg(long, default_value = "./alias.json")]
|
|
||||||
alias_file: PathBuf,
|
|
||||||
|
|
||||||
/// For internal usage
|
|
||||||
#[arg(long, default_value = "false")]
|
|
||||||
alias_file_is_set_not_a_list: bool,
|
|
||||||
|
|
||||||
/// Dir to lookup file alias
|
|
||||||
#[arg(long, default_value = ".")]
|
|
||||||
dir: PathBuf,
|
|
||||||
|
|
||||||
#[arg(short, long, default_value = "127.0.0.1")]
|
|
||||||
address: IpAddr,
|
|
||||||
|
|
||||||
#[arg(short, long, default_value = "8080")]
|
|
||||||
port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
// For better compatability with Nix (with set on the top of alias.json instead of a list)
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
|
||||||
struct NixJson {
|
|
||||||
alias: Vec<Alias>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
|
||||||
struct Alias {
|
|
||||||
uri: String,
|
|
||||||
alias: AliasType,
|
|
||||||
curl_only: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Debug)]
|
|
||||||
enum AliasType {
|
|
||||||
#[serde(alias = "url")]
|
|
||||||
Url(String),
|
|
||||||
#[serde(alias = "file")]
|
|
||||||
File(String),
|
|
||||||
#[serde(alias = "text")]
|
|
||||||
Text(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Responder)]
|
|
||||||
enum Response {
|
|
||||||
Text(RawText<String>),
|
|
||||||
Redirect(Redirect),
|
|
||||||
Status(Status),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UserAgent(String);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum UserAgentError {}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
|
||||||
impl<'r> FromRequest<'r> for UserAgent {
|
|
||||||
type Error = UserAgentError;
|
|
||||||
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
|
||||||
match req.headers().get_one("user-agent") {
|
|
||||||
Some(key) => Outcome::Success(UserAgent(key.to_string())),
|
|
||||||
_ => Outcome::Success(UserAgent("".to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_return(alias: &Alias) -> Response {
|
fn get_return(alias: &Alias) -> Response {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
@ -129,20 +62,40 @@ fn get_page(page: String, user_agent: UserAgent) -> Response {
|
||||||
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 alias = unsafe { ALIAS.get().unwrap() };
|
||||||
let mut pages = Vec::new();
|
let mut pages = Vec::new();
|
||||||
let curl_check = user_agent.0.contains("curl");
|
|
||||||
for i in alias {
|
for i in alias {
|
||||||
if i.uri == decoded_page {
|
if i.uri == decoded_page {
|
||||||
if (i.curl_only == Some(true)) == curl_check.clone() {
|
match &i.agent {
|
||||||
return get_return(i);
|
Some(agent) => unsafe {
|
||||||
|
let re = if let Some(regexes) = COMPILED_REGEXES.get_mut() {
|
||||||
|
match regexes.get(&agent.regex) {
|
||||||
|
Some(re) => re.clone(),
|
||||||
|
None => {
|
||||||
|
let re = Regex::new(&agent.regex).unwrap();
|
||||||
|
regexes.insert(agent.regex.clone(), re.clone());
|
||||||
|
re.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// guaranteed to be initialized at the beginning
|
||||||
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if re.is_match(&user_agent.0) {
|
||||||
|
return get_return(&i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(true) = agent.only_matching {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
pages.push(i);
|
pages.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Returning normal page (if found) to curl users.
|
// Returning normal page (if found) to curl users.
|
||||||
for i in pages {
|
if pages.len() != 0 {
|
||||||
if i.curl_only != Some(true) {
|
return get_return(pages[0]);
|
||||||
return get_return(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Response::Status(Status::NotFound)
|
Response::Status(Status::NotFound)
|
||||||
}
|
}
|
||||||
|
@ -164,6 +117,7 @@ async fn main() -> Result<(), rocket::Error> {
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
ALIAS.set(alias).unwrap();
|
ALIAS.set(alias).unwrap();
|
||||||
|
*COMPILED_REGEXES.get_mut() = Some(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let figment = Figment::from(rocket::Config::default())
|
let figment = Figment::from(rocket::Config::default())
|
||||||
|
|
83
src/structs.rs
Normal file
83
src/structs.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::request::{FromRequest, Outcome};
|
||||||
|
use rocket::response::content::RawText;
|
||||||
|
use rocket::response::Redirect;
|
||||||
|
use rocket::Request;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(about, author)]
|
||||||
|
pub struct Args {
|
||||||
|
#[arg(long, default_value = "./alias.json")]
|
||||||
|
pub alias_file: PathBuf,
|
||||||
|
|
||||||
|
/// For internal usage
|
||||||
|
#[arg(long, default_value = "false")]
|
||||||
|
pub alias_file_is_set_not_a_list: bool,
|
||||||
|
|
||||||
|
/// Dir to lookup file alias
|
||||||
|
#[arg(long, default_value = ".")]
|
||||||
|
pub dir: PathBuf,
|
||||||
|
|
||||||
|
#[arg(short, long, default_value = "127.0.0.1")]
|
||||||
|
pub address: IpAddr,
|
||||||
|
|
||||||
|
#[arg(short, long, default_value = "8080")]
|
||||||
|
pub port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For better compatability with Nix (with set on the top of alias.json instead of a list)
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct NixJson {
|
||||||
|
pub alias: Vec<Alias>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct Alias {
|
||||||
|
pub uri: String,
|
||||||
|
pub alias: AliasType,
|
||||||
|
pub agent: Option<Agent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub enum AliasType {
|
||||||
|
#[serde(alias = "url")]
|
||||||
|
Url(String),
|
||||||
|
#[serde(alias = "file")]
|
||||||
|
File(String),
|
||||||
|
#[serde(alias = "text")]
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
pub struct Agent {
|
||||||
|
pub regex: String,
|
||||||
|
pub only_matching: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Responder)]
|
||||||
|
pub enum Response {
|
||||||
|
Text(RawText<String>),
|
||||||
|
Redirect(Redirect),
|
||||||
|
Status(Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UserAgent(pub String);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UserAgentError {}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for UserAgent {
|
||||||
|
type Error = UserAgentError;
|
||||||
|
|
||||||
|
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
match req.headers().get_one("user-agent") {
|
||||||
|
Some(key) => Outcome::Success(UserAgent(key.to_string())),
|
||||||
|
_ => Outcome::Success(UserAgent("".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue