diff --git a/Cargo.toml b/Cargo.toml index 92276d5..0e503c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "urouter" -version = "0.5.2" +version = "0.5.3" edition = "2021" license = "MIT" repository = "https://github.com/ivabus/urouter" @@ -15,3 +15,4 @@ url-escape = "0.1.1" smurf = "0.3.0" clap = { version = "4.4.11", features = ["derive"] } regex = "1.10.2" +ureq = { version = "2.9.1", features = ["brotli", "native-certs"] } diff --git a/README.md b/README.md index 26304b3..b34d8b3 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,19 @@ JSON file with array of sets (or set with one field of arrays of sets with `--al Each set contains 2 necessary elements and 1 optional. -- Necessary - - `uri` (string) - of url after host (e.g., `/`, `some/cool/path`, should not start with `/` (only for root)) - - `alias` (set) - set of one field - - `url` (string) - redirect to url with HTTP 303 See Other - - `file` (string) - read file from path `--dir/file` where `--dir` is option (default: `.`, see `--help`) and respond with HTTP 200 OK with `content-type: text/plain` - - `text` (string) - plain text HTTP 200 OK with `content-type: text/plain` -- Optional - - `agent` (set) - set of one necessary field and one optional - - `regex` (string) - regular expression to match user-agent HTTP header - - `only_matching` (bool, optional, false by default) - if false whole alias will be visible for any user agent, if true only for regex matched +- `uri` (string) - of URL after host (e.g., `/`, `some/cool/path`, should not start with `/` (only for root)) +- `alias` (set) - set of one field + - `url` (string) - redirect to URL with HTTP 303 See Other + - `file` (string) - read file from path `--dir/file` where `--dir` is option (default: `.`, see `--help`) and respond with HTTP 200 OK with `content-type: text/plain` + - `text` (string) - plain text with HTTP 200 OK with `content-type: text/plain` + - `external` (set) - download (every time) file using `ureq` HTTP library and response with contents of downloaded resource with HTTP 200 OK and extracted `content-type` from response + - `url` (string) - URL to download + - `headers` (set, optional) - headers to include with request +- `agent` (set, optional) - set of one necessary field and one optional + - `regex` (string) - regular expression to match user-agent HTTP header + - `only_matching` (bool, optional, false by default) - if false whole alias will be visible for any user agent, if true only for regex matched -#### Set of array of sets +#### Set of array of sets (use only for very specific workarounds) ```json { @@ -67,6 +68,17 @@ Each set contains 2 necessary elements and 1 optional. "alias": { "text": "sometext" } + }, + { + "uri": "external", + "alias": { + "external": { + "url": "https://somecool.external.link", + "headers": { + "user-agent": "curl/8.6.0" + } + } + } } ] ``` diff --git a/alias.json b/alias.json index 1b5c280..a72ec4b 100644 --- a/alias.json +++ b/alias.json @@ -23,5 +23,16 @@ "regex": "^curl/[0-9].[0-9].[0-9]$", "only_matching": true } + }, + { + "uri": "external", + "alias": { + "external": { + "url": "https://iva.bz", + "headers": { + "user-agent": "curl/8.6.0" + } + } + } } ] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c5dde56..aee7fb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,10 @@ +/* SPDX-License-Identifier: MIT */ + /* - * MIT License - * - * Copyright (c) 2023 Ivan Bushchik - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ +Global comments: +- I'm ok with unwrapping because if unwrap fails Rocket will automatically return + 500 Internal Server Error +*/ mod structs; @@ -28,8 +12,8 @@ use structs::*; #[macro_use] extern crate rocket; -use rocket::http::Status; -use std::cell::{OnceCell, RefCell}; +use rocket::http::{ContentType, Status}; +use std::cell::OnceCell; use std::collections::HashMap; use std::hint::unreachable_unchecked; use std::path::PathBuf; @@ -43,7 +27,7 @@ use regex::Regex; use rocket::figment::Figment; static mut ALIAS: OnceCell> = OnceCell::new(); -static mut COMPILED_REGEXES: RefCell>> = RefCell::new(None); +static mut COMPILED_REGEXES: OnceCell> = OnceCell::new(); fn get_return(alias: &Alias) -> Response { let args = Args::parse(); @@ -52,9 +36,21 @@ fn get_return(alias: &Alias) -> Response { AliasType::Url(url) => Response::Redirect(Box::from(Redirect::to(url.clone()))), AliasType::File(path) => { dir.push(&PathBuf::from(&path)); - Response::Text(RawText(smurf::io::read_file_str(&dir).unwrap())) + Response::Text(Box::new(RawText(smurf::io::read_file_str(&dir).unwrap()))) + } + AliasType::Text(text) => Response::Text(Box::new(RawText(text.clone()))), + AliasType::External(source) => { + let mut request = ureq::get(&source.url); + for (header, value) in &source.headers { + request = request.set(header, value); + } + let result = request.call().unwrap(); + let ct = result.content_type(); + Response::Custom(Box::new(( + ContentType::parse_flexible(ct).unwrap(), + RawText(result.into_string().unwrap()), + ))) } - AliasType::Text(text) => Response::Text(RawText(text.clone())), } } @@ -129,7 +125,7 @@ async fn main() -> Result<(), rocket::Error> { (Instant::now() - compilation_start).as_secs_f64() * 1000.0 ); } - *COMPILED_REGEXES.get_mut() = Some(compiled_regexes); + COMPILED_REGEXES.set(compiled_regexes).unwrap(); } let figment = Figment::from(rocket::Config::default()) diff --git a/src/structs.rs b/src/structs.rs index 879ba0c..5f7989b 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,10 +1,13 @@ +/* SPDX-License-Identifier: MIT */ + use clap::Parser; -use rocket::http::Status; +use rocket::http::{ContentType, Status}; use rocket::request::{FromRequest, Outcome}; use rocket::response::content::RawText; -use rocket::response::Redirect; +use rocket::response::{Redirect, Responder}; use rocket::Request; use serde::Deserialize; +use std::collections::HashMap; use std::net::IpAddr; use std::path::PathBuf; @@ -50,6 +53,8 @@ pub enum AliasType { File(String), #[serde(alias = "text")] Text(String), + #[serde(alias = "external")] + External(External), } #[derive(Deserialize, Clone, Debug)] @@ -58,11 +63,19 @@ pub struct Agent { pub only_matching: Option, } +#[derive(Deserialize, Clone, Debug)] +pub struct External { + pub url: String, + #[serde(default)] + pub headers: HashMap, +} + #[derive(Responder)] pub enum Response { - Text(RawText), + Text(Box>), Redirect(Box), Status(Status), + Custom(Box<(ContentType, RawText)>), } pub struct UserAgent(pub String);