Initial commit

This commit is contained in:
Ivan Bushchik 2023-06-03 17:18:57 +03:00
commit 7a847fe028
No known key found for this signature in database
GPG key ID: 9F6DDABE11A2674D
7 changed files with 264 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/target
access_keys
alias.json
.idea
*.DS_Store
Cargo.lock

9
.rustfmt.toml Normal file
View 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
View 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
View 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
View 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
View 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
View 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(())
}