Compare commits

..

6 commits

Author SHA1 Message Date
cb3e83cc37
Archive 2023-12-22 07:07:05 +03:00
d0a78839ca
Add tea.yaml
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
2023-07-01 18:04:20 +03:00
9c6d1a8190
0.3.3
- Add lock file
2023-06-09 09:01:47 +03:00
85eaa42ad8
0.3.2
- Code cleanup
- Index resolution without ad set by default
2023-06-08 16:02:08 +03:00
df4c1b4fa6
0.3.1: Generate random strings instead of UUIDs in create_alias, if alias is not provided 2023-06-08 09:11:05 +03:00
5042f009cb
0.3.0: Add more api requests 2023-06-05 12:04:39 +03:00
6 changed files with 334 additions and 84 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "aliurl"
version = "0.2.0"
version = "0.3.3"
edition = "2021"
license = "MIT"
repository = "https://github.com/ivabus/aliurl"
@ -12,4 +12,4 @@ 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"] }
rand = "0.8.5"

View file

@ -1,3 +1,5 @@
## Archived, see [ivabus/urouter](https://github.com/ivabus/urouter) or something else as a replacement
# aliurl
> ALIaser for URLs
@ -24,14 +26,14 @@ Edit `Rocket.toml` to set port and ip.
cargo run -r
```
## Usage
## API
### Create new alias
#### Request
```http request
POST /post HTTP/1.1
POST /api/create_alias HTTP/1.1
```
#### Request body
@ -39,14 +41,88 @@ POST /post HTTP/1.1
```json
{
"url": "<URL_TO_BE_ALIASED>",
"alias": "<ALIAS_URI>", // If not provided, UUID will be generated
"alias": "<ALIAS_URI>", // If not provided, random string will be generated
"access_key": "<ACCESS_KEY>" // May not be provided, if no ./access_keys file
"redirect_with_ad": "<BOOL>" //May not be provided, if provided will use ./redirect.html
}
```
### Use alias
### Get all aliases
#### Request
```http request
POST /api/create_alias HTTP/1.1
```
#### Request body
```json
{
"access_key": "<ACCESS_KEY>" // May not be provided, if no ./access_keys file
}
```
#### Response body
```json
[
{
"alias": "alias_without_ad",
"url": "https://example.com"
},
{
"alias": "alias_with_ad",
"redirect_with_ad": true,
"url": "https://example.com"
}
]
```
### Remove alias
Removes all alias with provided name.
#### Request
```http request
POST /api/remove_alias HTTP/1.1
```
#### Request body
```json
{
"alias": "<ALIAS>",
"access_key": "<ACCESS_KEY>" // May not be provided, if no ./access_keys file
}
```
#### Response
##### Alias(es) removed
```json
[
{
"alias": "alias",
"url": "https://example.com"
},
{
"alias": "alias",
"redirect_with_ad": true,
"url": "https://another.com"
}
]
```
##### No aliases found
```json
[]
```
### Use alias
#### Request
@ -70,4 +146,4 @@ See `./redirect.html.example` to understand what's going on.
## License
The project is licensed under the terms of the [MIT license](./LICENSE).
The project is licensed under the terms of the [MIT license](./LICENSE).

View file

@ -1,11 +1,9 @@
[release]
[default]
address = "127.0.0.1"
port = 8080
workers = 8
keep_alive = 5
ident = "Rocket"
ip_header = "X-Real-IP" # set to `false` to disable
keep_alive = 0
ident = "aliurl via Rocket"
ip_header = false
log_level = "normal"
temp_dir = "/tmp"
cli_colors = true
ctrlc = false

View file

@ -1,25 +1,46 @@
/*
* 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.
*/
mod post;
#[macro_use]
extern crate rocket;
use std::io::prelude::*;
use std::io::BufReader;
use rocket::http::RawStr;
use rocket::http::Status;
use rocket::response::content::RawHtml;
use rocket::response::Redirect;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct CreateAliasRequest {
url: String,
redirect_with_ad: Option<String>,
access_key: Option<String>,
alias: Option<String>,
}
static mut ACCESS_KEY_REQUIRED: bool = true;
const LEN_OF_GENERATIVE_ALIASES: usize = 6;
const INDEX_REDIRECT: &'static str = "https://ivabus.dev";
const INDEX_WITH_AD: bool = false;
#[derive(Deserialize, Serialize, Clone)]
struct Alias {
@ -29,7 +50,17 @@ struct Alias {
redirect_with_ad: Option<bool>,
}
fn read_alias() -> Vec<Alias> {
fn lock() {
while std::path::Path::new("./alias.json.lock").exists() {}
std::fs::File::create("./alias.json.lock").unwrap();
}
fn unlock() {
std::fs::remove_file("./alias.json.lock").unwrap()
}
fn read_aliases() -> Vec<Alias> {
lock();
if !std::path::Path::new("./alias.json").exists() {
let mut file = std::fs::File::create("./alias.json").unwrap();
file.write_all(b"[]").unwrap();
@ -44,63 +75,9 @@ fn read_alias() -> Vec<Alias> {
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 '?'"));
}
if let Some(s) = data.redirect_with_ad {
if s.to_lowercase() == "true" {
alias_list.push(Alias {
url: data.url.clone(),
alias: alias.clone(),
redirect_with_ad: Some(true),
});
}
} else {
alias_list.push(Alias {
url: data.url.clone(),
alias: alias.clone(),
redirect_with_ad: None,
});
}
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));
let aliases_list: Vec<Alias> = serde_json::from_str(&contents).unwrap();
unlock();
aliases_list
}
#[get("/404")]
@ -112,7 +89,7 @@ fn not_found() -> Status {
async fn get_page(page: String) -> Result<Redirect, RawHtml<String>> {
let mut decoded_page = String::new();
url_escape::decode_to_string(page, &mut decoded_page);
let alias_list = read_alias();
let alias_list = read_aliases();
for i in alias_list {
if i.alias == decoded_page {
if let Some(red) = i.redirect_with_ad {
@ -130,8 +107,15 @@ async fn get_page(page: String) -> Result<Redirect, RawHtml<String>> {
}
#[get("/")]
async fn get_index() -> Redirect {
Redirect::to(INDEX_REDIRECT)
async fn get_index() -> Result<Redirect, RawHtml<String>> {
if INDEX_WITH_AD {
let mut redirect = String::new();
let mut file = std::fs::File::open("./redirect.html").unwrap();
file.read_to_string(&mut redirect).unwrap();
Err(RawHtml(redirect.replace("#REDIRECT#", INDEX_REDIRECT)))
} else {
Ok(Redirect::to(INDEX_REDIRECT))
}
}
#[rocket::main]
@ -155,7 +139,17 @@ async fn main() -> Result<(), rocket::Error> {
}
let _rocket = rocket::build()
.mount("/", routes![not_found, create_alias, get_page, get_index])
.mount(
"/",
routes![
not_found,
post::create_alias,
post::get_aliases,
post::remove_alias,
get_page,
get_index
],
)
.launch()
.await?;

180
src/post.rs Normal file
View file

@ -0,0 +1,180 @@
/*
* 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.
*/
use crate::*;
use rand::distributions::{Alphanumeric, DistString};
use rocket::http::{RawStr, Status};
use rocket::response::content::RawJson;
use serde_json::json;
#[derive(Debug, Deserialize)]
struct CreateAliasRequest {
url: String,
redirect_with_ad: Option<String>,
access_key: Option<String>,
alias: Option<String>,
}
#[derive(Debug, Deserialize)]
struct GetAliasesRequest {
access_key: Option<String>,
}
#[derive(Debug, Deserialize)]
struct RemoveAliasRequest {
alias: String,
access_key: Option<String>,
}
fn check_access_key(key: Option<String>) -> Result<String, (Status, RawJson<String>)> {
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();
return if let Some(key) = key {
if !access_keys.contains(&key.as_str()) {
Err((Status::Forbidden, RawJson(json!({"Error": "Invalid access key"}).to_string())))
} else {
Ok(key)
}
} else {
unsafe {
if ACCESS_KEY_REQUIRED {
Err((Status::Forbidden, RawJson(json!({"Error": "No access key"}).to_string())))
} else {
Ok("".to_string())
}
}
};
}
#[post("/api/create_alias", data = "<data>")]
pub async fn create_alias(data: &RawStr) -> (Status, RawJson<String>) {
let data: CreateAliasRequest = match serde_json::from_str(&data.to_string()) {
Ok(req) => req,
Err(e) => {
return (Status::BadRequest, RawJson(json!({"Error": e.to_string()}).to_string()))
}
};
if let Err(e) = check_access_key(data.access_key) {
return e;
}
let mut aliases_list = read_aliases();
lock();
let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap();
let alias = match data.alias {
None => {
let mut gen: String;
'gen: loop {
gen =
Alphanumeric.sample_string(&mut rand::thread_rng(), LEN_OF_GENERATIVE_ALIASES);
for i in &aliases_list {
if i.alias == gen {
continue 'gen;
}
}
break 'gen;
}
gen
}
Some(alias) => alias,
};
if alias.contains("?") {
return (
Status::BadRequest,
RawJson(json!({"Error": "Alias should not contain \"?\""}).to_string()),
);
}
let alias = Alias {
url: data.url,
alias,
redirect_with_ad: match data.redirect_with_ad {
Some(s) => {
if s.to_ascii_lowercase() == "true" {
Some(true)
} else {
None
}
}
None => None,
},
};
aliases_list.push(alias.clone());
aliases_list.dedup_by(|a, b| a.alias == b.alias);
file.write_all(serde_json::to_string(&aliases_list).unwrap().as_bytes()).unwrap();
file.sync_all().unwrap();
unlock();
return (Status::Ok, RawJson(serde_json::to_string(&alias).unwrap()));
}
#[post("/api/get_aliases", data = "<data>")]
pub async fn get_aliases(data: &RawStr) -> (Status, RawJson<String>) {
let data: GetAliasesRequest = match serde_json::from_str(&data.to_string()) {
Ok(req) => req,
Err(e) => {
return (Status::BadRequest, RawJson(json!({"Error": format!("{e}")}).to_string()))
}
};
if let Err(e) = check_access_key(data.access_key) {
return e;
}
return (Status::Ok, RawJson(serde_json::to_string(&read_aliases()).unwrap()));
}
#[post("/api/remove_alias", data = "<data>")]
pub async fn remove_alias(data: &RawStr) -> (Status, RawJson<String>) {
let data: RemoveAliasRequest = match serde_json::from_str(&data.to_string()) {
Ok(req) => req,
Err(e) => {
return (Status::BadRequest, RawJson(json!({"Error": format!("{e}")}).to_string()))
}
};
if let Err(e) = check_access_key(data.access_key) {
return e;
}
let mut aliases_list = read_aliases();
let mut removed_aliases: Vec<Alias> = vec![];
lock();
let mut file = std::fs::File::options().write(true).open("./alias.json").unwrap();
for i in (0..aliases_list.len()).rev() {
if aliases_list[i].alias == data.alias {
removed_aliases.push(aliases_list.remove(i));
}
}
let aliases_list = serde_json::to_string(&aliases_list).unwrap();
file.write_all(&aliases_list.as_bytes()).unwrap();
file.set_len(aliases_list.as_bytes().len() as u64).unwrap();
file.sync_all().unwrap();
unlock();
return (Status::Ok, RawJson(serde_json::to_string(&removed_aliases).unwrap()));
}

2
tea.yaml Normal file
View file

@ -0,0 +1,2 @@
dependencies:
rust-lang.org/cargo: '*'