feat(server): add random suffix mode (#69)

* add random suffix mode

* fix linter issues

* add test case

* fix linter issues

* add comments, remove empty lines and single line declarations

* more test cases

* refactor(config): rename suffix_mode to random_suffix

* refactor(paste): clean up the random suffix logic

* chore(config): add random suffix example to default config

* docs(readme): mention random suffix feature

* test(fixtures): add fixture test for random suffix mode

* random_suffix -> suffix_mode

* fix default extension for .dotfile w/o extension

* fix formatting

* style(format): fix the indentation for random suffix fixture

---------

Co-authored-by: Orhun Parmaksız <orhunparmaksiz@gmail.com>
This commit is contained in:
Helmut K. C. Tessarek 2023-06-30 18:11:16 -04:00 committed by GitHub
parent d740ae7e89
commit 62bbfef6a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 18 deletions

View File

@ -68,6 +68,7 @@ Here you can read the blog post about how it is deployed on Shuttle: [https://bl
- random file names (optional)
- pet name (e.g. `capital-mosquito.txt`)
- alphanumeric string (e.g. `yB84D2Dv.txt`)
- random suffix (e.g. `file.MRV5as.tar.gz`)
- supports expiring links
- auto-expiration of files (optional)
- auto-deletion of expired files (optional)
@ -192,7 +193,6 @@ supported units:
- `months`, `month`, `M`
- `years`, `year`, `y`
#### One shot files
```sh

View File

@ -39,6 +39,7 @@ content_type = "text/plain; charset=utf-8"
[paste]
random_url = { enabled = true, type = "petname", words = 2, separator = "-" }
#random_url = { enabled = true, type = "alphanumeric", length = 8 }
#random_url = { enabled = true, type = "alphanumeric", length = 6, suffix_mode = true }
default_extension = "txt"
mime_override = [
{ mime = "image/jpeg", regex = "^.*\\.jpg$" },

View File

@ -6,4 +6,4 @@ upload_path="./upload"
[paste]
random_url = { enabled = false, type = "petname", words = 2, separator = "-" }
default_extension = "txt"
duplicate_files = false
duplicate_files = true

View File

@ -4,6 +4,7 @@ content="test data"
setup() {
echo "$content" > file
echo "$content" > .file
}
run_test() {
@ -11,9 +12,11 @@ run_test() {
test "$file_url" = "http://localhost:8000/file.txt"
test "$content" = "$(cat upload/file.txt)"
test "$content" = "$(curl -s $file_url)"
file_url2=$(curl -s -F "file=@.file" localhost:8000)
test "$file_url2" = "http://localhost:8000/.file.txt"
}
teardown() {
rm file
rm file .file
rm -r upload
}

View File

@ -0,0 +1,9 @@
[server]
address = "127.0.0.1:8000"
max_content_length = "10MB"
upload_path = "./upload"
[paste]
random_url = { enabled = true, type = "alphanumeric", length = "6", suffix_mode = true }
default_extension = "txt"
duplicate_files = true

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
content="test data"
setup() {
echo "$content" >file
}
run_test() {
first_file_url=$(curl -s -F "file=@file" localhost:8000)
test "$first_file_url" != "http://localhost:8000/file.txt"
test "$content" = "$(curl -s $first_file_url)"
second_file_url=$(curl -s -F "file=@file" localhost:8000)
test "$first_file_url" != "http://localhost:8000/file.txt"
test "$content" = "$(curl -s $first_file_url)"
test "$first_file_url" != "$second_file_url"
test "$(cat upload/${first_file_url/http:\/\/localhost:8000\//})" \
= "$(cat upload/${second_file_url/http:\/\/localhost:8000\//})"
[[ $(find upload/ -name "file.*.txt" -print -quit 2>/dev/null) ]] || exit 1
}
teardown() {
rm file
rm -r upload
}

View File

@ -113,31 +113,54 @@ impl Paste {
.and_then(|v| v.to_str())
{
Some("-") => String::from("stdin"),
Some(".") => String::from("file"),
Some(v) => v.to_string(),
None => String::from("file"),
};
let mut path = self
.type_
.get_path(&config.server.upload_path)
.join(file_name);
match path.clone().extension() {
Some(extension) => {
if let Some(file_name) = config.paste.random_url.generate() {
path.set_file_name(file_name);
path.set_extension(extension);
}
.join(&file_name);
let mut parts: Vec<&str> = file_name.split('.').collect();
let mut dotfile = false;
let mut lower_bound = 1;
let mut file_name = match parts[0] {
"" => {
// Index shifts one to the right in the array for the rest of the string (the extension)
dotfile = true;
lower_bound = 2;
// If the first array element is empty, it means the file started with a dot (e.g.: .foo)
format!(".{}", parts[1])
}
None => {
if let Some(file_name) = config.paste.random_url.generate() {
path.set_file_name(file_name);
_ => parts[0].to_string(),
};
let mut extension = if parts.len() > lower_bound {
// To get the rest (the extension), we have to remove the first element of the array, which is the filename
parts.remove(0);
if dotfile {
// If the filename starts with a dot, we have to remove another element, because the first element was empty
parts.remove(0);
}
parts.join(".")
} else {
file_type
.map(|t| t.extension())
.unwrap_or(&config.paste.default_extension)
.to_string()
};
if let Some(random_text) = config.paste.random_url.generate() {
if let Some(suffix_mode) = config.paste.random_url.suffix_mode {
if suffix_mode {
extension = format!("{}.{}", random_text, extension);
} else {
file_name = random_text;
}
path.set_extension(
file_type
.map(|t| t.extension())
.unwrap_or(&config.paste.default_extension),
);
} else {
file_name = random_text;
}
}
path.set_file_name(file_name);
path.set_extension(extension);
let file_name = path
.file_name()
.map(|v| v.to_string_lossy())
@ -274,6 +297,67 @@ mod tests {
);
fs::remove_file(file_name)?;
config.paste.random_url = RandomURLConfig {
enabled: true,
length: Some(4),
type_: RandomURLType::Alphanumeric,
suffix_mode: Some(true),
..RandomURLConfig::default()
};
let paste = Paste {
data: vec![116, 101, 115, 115, 117, 115],
type_: PasteType::File,
};
let file_name = paste.store_file("foo.tar.gz", None, &config)?;
assert_eq!("tessus", fs::read_to_string(&file_name)?);
assert!(file_name.ends_with(".tar.gz"));
assert!(file_name.starts_with("foo."));
fs::remove_file(file_name)?;
config.paste.random_url = RandomURLConfig {
enabled: true,
length: Some(4),
type_: RandomURLType::Alphanumeric,
suffix_mode: Some(true),
..RandomURLConfig::default()
};
let paste = Paste {
data: vec![116, 101, 115, 115, 117, 115],
type_: PasteType::File,
};
let file_name = paste.store_file(".foo.tar.gz", None, &config)?;
assert_eq!("tessus", fs::read_to_string(&file_name)?);
assert!(file_name.ends_with(".tar.gz"));
assert!(file_name.starts_with(".foo."));
fs::remove_file(file_name)?;
config.paste.random_url = RandomURLConfig {
enabled: true,
length: Some(4),
type_: RandomURLType::Alphanumeric,
suffix_mode: Some(false),
..RandomURLConfig::default()
};
let paste = Paste {
data: vec![116, 101, 115, 115, 117, 115],
type_: PasteType::File,
};
let file_name = paste.store_file("foo.tar.gz", None, &config)?;
assert_eq!("tessus", fs::read_to_string(&file_name)?);
assert!(file_name.ends_with(".tar.gz"));
fs::remove_file(file_name)?;
config.paste.default_extension = String::from("txt");
config.paste.random_url.enabled = false;
let paste = Paste {
data: vec![120, 121, 122],
type_: PasteType::File,
};
let file_name = paste.store_file(".foo", None, &config)?;
assert_eq!("xyz", fs::read_to_string(&file_name)?);
assert_eq!(".foo.txt", file_name);
fs::remove_file(file_name)?;
config.paste.default_extension = String::from("bin");
config.paste.random_url.enabled = false;
config.paste.random_url = RandomURLConfig {

View File

@ -14,6 +14,8 @@ pub struct RandomURLConfig {
/// Type of the random URL.
#[serde(rename = "type")]
pub type_: RandomURLType,
/// Append a random string to the original filename.
pub suffix_mode: Option<bool>,
}
impl RandomURLConfig {