Skip to content

Commit

Permalink
Upgrade to clap 4
Browse files Browse the repository at this point in the history
  • Loading branch information
HitomiTenshi committed Oct 23, 2022
1 parent 571d7c5 commit f7a64ef
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 288 deletions.
448 changes: 245 additions & 203 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ authors = ["Johann Rekowski <[email protected]>"]
repository = "https://github.com/HitomiTenshi/dekinai.git"
license = "MIT"
edition = "2021"
rust-version = "1.59"
rust-version = "1.64"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-multipart = "0.4"
actix-web = { version = "4", default-features = false, features = ["macros"] }
async-recursion = "1"
clap = { version = "3", features = ["cargo", "env", "wrap_help"] }
clap = { version = "4", features = ["cargo", "env", "wrap_help"] }
derive_more = { version = "0.99", default-features = false, features = ["display", "error"] }
futures-util = { version = "0.3", default-features = false }
num_cpus = "1"
Expand Down
33 changes: 15 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,24 @@ For more information try `--help`:
```
$ dekinai.exe --help
dekinai 1.1.0
Johann Rekowski <[email protected]>
Dekinai is a simple and minimalistic file uploading API.
It provides the bare minimum feature set for file hosting services, while also being fast and portable.
USAGE:
dekinai.exe [OPTIONS] <OUTPUT_DIR>
ARGS:
<OUTPUT_DIR> Sets the directory for uploaded files
OPTIONS:
-b, --blacklist <FILE_EXTENSIONS>... Sets a list of disallowed file extensions
Usage: --blacklist asp html php
-d, --database <DATABASE_DIR> Sets the directory for the SQLite database [default: ./]
--disable-port Disables port listening (Unix only)
-h, --help Print help information
-p, --port <NUMBER> Sets the port number to listen on [default: 54298]
--password <TEXT> Sets a password for API requests [env: DEKINAI_PASSWORD=]
-u, --unix <FILE> Sets the unix socket to listen on (Unix only)
-V, --version Print version information
Usage: dekinai.exe [OPTIONS] <OUTPUT_DIRECTORY>
Arguments:
<OUTPUT_DIRECTORY> Sets the directory for uploaded files
Options:
-p, --port [<NUMBER>] Sets the port number to listen on [default: 54298]
-u, --unix [<FILE>] Sets the unix socket to listen on (Unix only)
-d, --database [<DIRECTORY>] Sets the directory for the SQLite database [default: ./]
--password [<TEXT>] Sets a password for API requests [env: DEKINAI_PASSWORD=]
-b, --blacklist <FILE_EXTENSIONS>... Sets a list of disallowed file extensions
Usage: --blacklist asp html php
--disable-port Disables port listening (Unix only)
-h, --help Print help information
-V, --version Print version information
```

## API
Expand Down
65 changes: 23 additions & 42 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,32 @@
use clap::{arg, command, ArgMatches};
use std::ops::RangeInclusive;
use std::path::Path;

const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
use clap::{arg, command, value_parser, ArgAction, ArgMatches};
use std::path::PathBuf;

pub fn get_matches() -> ArgMatches {
command!()
.arg(arg!(output: <OUTPUT_DIR> "Sets the directory for uploaded files").validator(validate_dir))
.arg(
arg!(-p --port <NUMBER> "Sets the port number to listen on")
.required(false)
.default_value("54298")
.validator(|value| {
value
.parse::<usize>()
.map(|port| PORT_RANGE.contains(&port))
.map_err(|err| err.to_string())
.and_then(|result| if result { Ok(()) } else { Err(format!(
"Port needs to be a number between {} and {}",
PORT_RANGE.start(),
PORT_RANGE.end()
)) })
}),
)
.arg(arg!(-u --unix <FILE> "Sets the unix socket to listen on (Unix only)").required(false))
.arg(
arg!(-d --database <DATABASE_DIR> "Sets the directory for the SQLite database")
.required(false)
.default_value("./")
.validator(validate_dir)
)
.arg(arg!(--password <TEXT> "Sets a password for API requests").required(false).env("DEKINAI_PASSWORD").validator(|value| {
if value.is_ascii() {
Ok(())
} else {
Err("Password needs to contain only ASCII characters")
}
}))
.arg(arg!(-b --blacklist <FILE_EXTENSIONS> "Sets a list of disallowed file extensions\nUsage: --blacklist asp html php").required(false).multiple_values(true))
.arg(arg!(--"disable-port" "Disables port listening (Unix only)").required(false).requires("unix").conflicts_with("port"))
.arg(arg!(output: <OUTPUT_DIRECTORY> "Sets the directory for uploaded files").value_parser(validate_dir))
.arg(arg!(-p --port [NUMBER] "Sets the port number to listen on").default_value("54298").value_parser(value_parser!(u16).range(1..)))
.arg(arg!(-u --unix [FILE] "Sets the unix socket to listen on (Unix only)").value_parser(value_parser!(PathBuf)))
.arg(arg!(-d --database [DIRECTORY] "Sets the directory for the SQLite database").default_value("./").value_parser(validate_dir))
.arg(arg!(--password [TEXT] "Sets a password for API requests").env("DEKINAI_PASSWORD").value_parser(validate_password))
.arg(arg!(-b --blacklist [FILE_EXTENSIONS] ... "Sets a list of disallowed file extensions\nUsage: --blacklist asp html php").num_args(1..))
.arg(arg!(--"disable-port" "Disables port listening (Unix only)").requires("unix").conflicts_with("port").action(ArgAction::SetTrue))
.get_matches()
}

fn validate_dir(path: &str) -> Result<(), String> {
if Path::new(path).is_dir() {
Ok(())
fn validate_dir(value: &str) -> Result<PathBuf, String> {
let path = PathBuf::from(value);

if path.is_dir() {
Ok(path)
} else {
Err(format!("Cannot access directory \"{}\"", value))
}
}

fn validate_password(value: &str) -> Result<String, String> {
if value.is_ascii() {
Ok(value.to_owned())
} else {
Err(format!("Cannot access directory \"{}\"", path))
Err("Password needs to contain only ASCII characters".to_owned())
}
}
30 changes: 16 additions & 14 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::lib;
use crate::util;
use clap::ArgMatches;
use rand::thread_rng;
use sqlx::{migrate, migrate::MigrateDatabase, sqlite::SqlitePoolOptions, Sqlite, SqlitePool};
use std::path::{Path, PathBuf};
use std::path::PathBuf;

#[derive(Clone)]
pub struct AppConfig {
pub blacklist: Option<Vec<String>>,
pub output: PathBuf,
pub password_hash: Option<String>,
pub port: Option<String>,
pub port: Option<u16>,
}

pub struct ServerConfig {
Expand All @@ -19,30 +19,30 @@ pub struct ServerConfig {

impl From<&ArgMatches> for AppConfig {
fn from(matches: &ArgMatches) -> Self {
let port: Option<String>;
let port: Option<u16>;

#[cfg(unix)]
{
port = if !matches.is_present("disable-port") {
Some(matches.value_of("port").unwrap().to_owned())
port = if !matches.get_flag("disable-port") {
matches.get_one::<u16>("port").copied()
} else {
None
};
}

#[cfg(not(unix))]
{
port = Some(matches.value_of("port").unwrap().to_owned());
port = matches.get_one::<u16>("port").copied();
}

Self {
blacklist: matches
.values_of("blacklist")
.map(|values| values.map(str::to_lowercase).collect()),
output: PathBuf::from(matches.value_of("output").unwrap()),
.get_many::<String>("blacklist")
.map(|values| values.map(|value| value.to_lowercase()).collect()),
output: matches.get_one::<PathBuf>("output").unwrap().to_owned(),
password_hash: matches
.value_of("password")
.map(|str| lib::hash_password(&mut thread_rng(), str)),
.get_one::<String>("password")
.map(|value| util::hash_password(&mut thread_rng(), value)),
port,
}
}
Expand All @@ -51,10 +51,12 @@ impl From<&ArgMatches> for AppConfig {
impl From<&ArgMatches> for ServerConfig {
fn from(matches: &ArgMatches) -> Self {
Self {
unix: matches.value_of("unix").map(PathBuf::from),
unix: matches.get_one::<PathBuf>("unix").cloned(),
database_uri: format!(
"sqlite://{}",
Path::new(matches.value_of("database").unwrap())
matches
.get_one::<PathBuf>("database")
.unwrap()
.join("dekinai.sqlite")
.display()
),
Expand Down
2 changes: 1 addition & 1 deletion src/db.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::lib::Error;
use crate::util::Error;
use sqlx::{query, query_scalar, SqlitePool};

pub async fn get_deletion_password(
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::io;
mod cli;
mod config;
mod db;
mod lib;
mod route;
mod util;

#[actix_web::main]
async fn main() -> io::Result<()> {
Expand Down
14 changes: 7 additions & 7 deletions src/route.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{config::AppConfig, db, lib, lib::Error};
use crate::{config::AppConfig, db, util, util::Error};
use actix_multipart::Multipart;
use actix_web::{get, http::header::HeaderMap, post, web, HttpRequest};
use async_recursion::async_recursion;
Expand Down Expand Up @@ -34,7 +34,7 @@ pub async fn upload(req: HttpRequest, mut payload: Multipart) -> Result<web::Jso
let base_url = get_base_url(config, headers)?;

if let Some(mut field) = payload.try_next().await.map_err(Error::InternalServerError)? {
let file_extension = &lib::get_file_extension(
let file_extension = &util::get_file_extension(
field
.content_disposition()
.get_filename()
Expand Down Expand Up @@ -78,7 +78,7 @@ pub async fn delete(req: HttpRequest, path: web::Path<(String, String)>) -> Resu
let config = req.app_data::<AppConfig>().unwrap();
let pool = req.app_data::<SqlitePool>().unwrap();
let file_path = &config.output.join(&path.0);
let (file_stem, file_extension) = lib::get_file_stem_with_extension(&path.0);
let (file_stem, file_extension) = util::get_file_stem_with_extension(&path.0);

if file_path.exists() {
if let Some(deletion_password) = &db::get_deletion_password(pool, file_stem, &file_extension).await? {
Expand Down Expand Up @@ -110,8 +110,8 @@ async fn create_random_file(
pool: &SqlitePool,
file_extension: &str,
) -> Result<(File, PathBuf, String, String), Error> {
let file_stem = lib::get_random_text(rng, 8);
let deletion_password = lib::get_random_text(rng, 24);
let file_stem = util::get_random_text(rng, 8);
let deletion_password = util::get_random_text(rng, 24);

let filename = if file_extension.is_empty() {
file_stem.clone()
Expand All @@ -126,7 +126,7 @@ async fn create_random_file(
pool,
&file_stem,
file_extension,
&lib::hash_password(rng, &deletion_password),
&util::hash_password(rng, &deletion_password),
)
.await?
{
Expand Down Expand Up @@ -168,7 +168,7 @@ fn get_base_url(config: &AppConfig, headers: &HeaderMap) -> Result<String, Error
if host == "localhost" {
if let Some(port) = &config.port {
base_url.push(':');
base_url.push_str(port);
base_url.push_str(&port.to_string());
}
}

Expand Down
File renamed without changes.

0 comments on commit f7a64ef

Please sign in to comment.