mirror of
https://github.com/ivabus/aliurl
synced 2024-11-21 14:25:05 +03:00
Initial commit
This commit is contained in:
commit
7a847fe028
7 changed files with 264 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/target
|
||||
access_keys
|
||||
alias.json
|
||||
.idea
|
||||
*.DS_Store
|
||||
Cargo.lock
|
9
.rustfmt.toml
Normal file
9
.rustfmt.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
edition = "2021"
|
||||
hard_tabs = true
|
||||
merge_derives = true
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
use_field_init_shorthand = true
|
||||
use_small_heuristics = "Off"
|
||||
wrap_comments = true
|
||||
comment_width = 80
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "aliurl"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/ivabus/aliurl"
|
||||
description = "Small aliaser for URLs"
|
||||
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.5.0-rc.3"
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
url-escape = "0.1.1"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
20
LICENSE
Normal file
20
LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
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.
|
64
README.md
Normal file
64
README.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# aliurl
|
||||
|
||||
> ALIaser for URLs
|
||||
|
||||
Small http service to create aliases for URLs.
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
git clone https://github.com/ivabus/aliurl
|
||||
cd aliurl
|
||||
cargo b -r
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Add your access_keys to `./access_keys` or don't add any, if you don't want to use authorization.
|
||||
|
||||
Edit `Rocket.toml` to set port and ip.
|
||||
|
||||
### Running
|
||||
|
||||
```shell
|
||||
cargo run -r
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Create new alias
|
||||
|
||||
#### Request
|
||||
|
||||
```http request
|
||||
POST /post HTTP/1.1
|
||||
```
|
||||
|
||||
#### Request body
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "<URL_TO_BE_ALIASED>",
|
||||
"alias": "<ALIAS_URI>", // If not provided, UUID will be generated
|
||||
"access_key": "<ACCESS_KEY>" // May not be provided, if no ./access_keys file
|
||||
}
|
||||
```
|
||||
|
||||
### Use alias
|
||||
|
||||
```http request
|
||||
GET /<ALIAS> HTTP/1.1
|
||||
```
|
||||
|
||||
```http request
|
||||
HTTP/1.1 303 See Other
|
||||
location: <URL>
|
||||
```
|
||||
|
||||
### Alias for `/`
|
||||
|
||||
Aliases for root is declared in `src/main.rs` file in `INDEX_REDIRECT` const.
|
||||
|
||||
## License
|
||||
|
||||
The project is licensed under the terms of the [MIT license](./LICENSE).
|
11
Rocket.toml
Normal file
11
Rocket.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[release]
|
||||
address = "127.0.0.1"
|
||||
port = 8080
|
||||
workers = 8
|
||||
keep_alive = 5
|
||||
ident = "Rocket"
|
||||
ip_header = "X-Real-IP" # set to `false` to disable
|
||||
log_level = "normal"
|
||||
temp_dir = "/tmp"
|
||||
cli_colors = true
|
||||
ctrlc = false
|
139
src/main.rs
Normal file
139
src/main.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
|
||||
use rocket::http::RawStr;
|
||||
use rocket::http::Status;
|
||||
use rocket::response::Redirect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateAliasRequest {
|
||||
url: String,
|
||||
access_key: Option<String>,
|
||||
alias: Option<String>,
|
||||
}
|
||||
|
||||
static mut ACCESS_KEY_REQUIRED: bool = true;
|
||||
const INDEX_REDIRECT: &'static str = "https://ivabus.dev";
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
struct Alias {
|
||||
url: String,
|
||||
alias: String,
|
||||
}
|
||||
|
||||
fn read_alias() -> Vec<Alias> {
|
||||
if !std::path::Path::new("./alias.json").exists() {
|
||||
let mut file = std::fs::File::create("./alias.json").unwrap();
|
||||
file.write_all(b"[]").unwrap();
|
||||
return vec![];
|
||||
}
|
||||
if std::fs::File::open("./alias.json").unwrap().metadata().unwrap().len() == 0 {
|
||||
let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap();
|
||||
file.write_all(b"[]").unwrap();
|
||||
return vec![];
|
||||
}
|
||||
let file = std::fs::File::open("./alias.json").unwrap();
|
||||
let mut buf_reader = BufReader::new(file);
|
||||
let mut contents = String::new();
|
||||
buf_reader.read_to_string(&mut contents).unwrap();
|
||||
let alias_list: Vec<Alias> = serde_json::from_str(&contents).unwrap();
|
||||
alias_list
|
||||
}
|
||||
|
||||
#[post("/post", data = "<data>")]
|
||||
fn create_alias(data: &RawStr) -> (Status, String) {
|
||||
let data: CreateAliasRequest = match serde_json::from_str(&data.to_string()) {
|
||||
Ok(req) => req,
|
||||
Err(e) => return (Status::BadRequest, format!("Error: {e}")),
|
||||
};
|
||||
let mut file = std::fs::File::open("./access_keys").unwrap();
|
||||
let mut buffer: String = String::new();
|
||||
file.read_to_string(&mut buffer).unwrap();
|
||||
let access_keys: Vec<&str> = buffer.split("\n").collect();
|
||||
if let Some(key) = data.access_key {
|
||||
if !access_keys.contains(&key.as_str()) {
|
||||
return (Status::Forbidden, "Access key is invalid".to_string());
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
if ACCESS_KEY_REQUIRED {
|
||||
return (Status::Forbidden, "Access key needs to be provided".to_string());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut alias_list = read_alias();
|
||||
let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap();
|
||||
let alias = match data.alias {
|
||||
None => uuid::Uuid::new_v4().to_string(),
|
||||
Some(alias) => alias,
|
||||
};
|
||||
if alias.contains("?") {
|
||||
return (Status::BadRequest, format!("Error: alias should not contain '?'"));
|
||||
}
|
||||
alias_list.push(Alias {
|
||||
url: data.url.clone(),
|
||||
alias: alias.clone(),
|
||||
});
|
||||
alias_list.dedup_by(|a, b| a.alias == b.alias);
|
||||
|
||||
file.write_all(serde_json::to_string(&alias_list).unwrap().as_bytes()).unwrap();
|
||||
|
||||
file.sync_all().unwrap();
|
||||
return (Status::Ok, format!("Created {} at {}", data.url, alias));
|
||||
}
|
||||
|
||||
#[get("/404")]
|
||||
fn not_found() -> Status {
|
||||
Status::NotFound
|
||||
}
|
||||
|
||||
#[get("/<page>")]
|
||||
async fn get_page(page: String) -> Redirect {
|
||||
let mut decoded_page = String::new();
|
||||
url_escape::decode_to_string(page, &mut decoded_page);
|
||||
let alias_list = read_alias();
|
||||
for i in alias_list {
|
||||
if i.alias == decoded_page {
|
||||
return Redirect::to(i.url);
|
||||
}
|
||||
}
|
||||
Redirect::to("/404")
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn get_index() -> Redirect {
|
||||
Redirect::to(INDEX_REDIRECT)
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> Result<(), rocket::Error> {
|
||||
if !std::path::Path::new("./access_keys").exists() {
|
||||
eprintln!("No ./access_keys found. Falling back to no authorization");
|
||||
eprintln!("Continue? (press enter or ctrl-c to exit)");
|
||||
let mut s = String::new();
|
||||
std::io::stdin().read_line(&mut s).unwrap();
|
||||
unsafe {
|
||||
ACCESS_KEY_REQUIRED = false;
|
||||
}
|
||||
} else if std::fs::File::open("./access_keys").unwrap().metadata().unwrap().len() == 0 {
|
||||
eprintln!("No keys in ./access_keys found. Falling back to no authorization");
|
||||
eprintln!("Continue? (press enter or ctrl-c to exit)");
|
||||
let mut s = String::new();
|
||||
std::io::stdin().read_line(&mut s).unwrap();
|
||||
unsafe {
|
||||
ACCESS_KEY_REQUIRED = false;
|
||||
}
|
||||
}
|
||||
|
||||
let _rocket = rocket::build()
|
||||
.mount("/", routes![not_found, create_alias, get_page, get_index])
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue