Skip to content

Commit 2982ffe

Browse files
santiagocezarOXeu
andcommitted
rebase and merge PR szabodanika#253
Co-authored-by: Xeu <thankrain@qq.com>
1 parent b7e927c commit 2982ffe

17 files changed

Lines changed: 172 additions & 173 deletions

File tree

.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ export MICROBIN_GC_DAYS=90
136136
# Default value: false
137137
export MICROBIN_ENABLE_BURN_AFTER=true
138138

139+
# Enables or disables the "Custom Url" function
140+
# Default value: false
141+
export MICROBIN_ENABLE_CUSTOM_URL=true
142+
139143
# Sets the default burn after setting on the main screen.
140144
# Default value: 0. Available expiration options: 1, 10,
141145
# 100, 1000, 10000, 0 (= no limit)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ On our website [microbin.eu](https://microbin.eu), you will find the following:
4444
- QR code support
4545
- URL shortening and redirection
4646
- Animal names instead of random numbers for upload identifiers (64 animals)
47+
- Custom URL
4748
- SQLite and JSON database support
4849
- Private and public, editable and uneditable, automatically and never expiring uploads
4950
- Automatic dark mode and custom styling support with very little CSS and only vanilla JS (see [`water.css`](https://github.com/kognise/water.css))

compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ services:
3232
MICROBIN_THREADS: ${MICROBIN_THREADS}
3333
MICROBIN_GC_DAYS: ${MICROBIN_GC_DAYS}
3434
MICROBIN_ENABLE_BURN_AFTER: ${MICROBIN_ENABLE_BURN_AFTER}
35+
MICROBIN_ENABLE_CUSTOM_URL: ${MICROBIN_ENABLE_CUSTOM_URL}
3536
MICROBIN_DEFAULT_BURN_AFTER: ${MICROBIN_DEFAULT_BURN_AFTER}
3637
MICROBIN_WIDE: ${MICROBIN_WIDE}
3738
MICROBIN_QR: ${MICROBIN_QR}

src/args.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ pub struct Args {
103103
#[clap(long, env = "MICROBIN_ENABLE_READONLY")]
104104
pub enable_readonly: bool,
105105

106+
#[clap(long, env = "MICROBIN_ENABLE_CUSTOM_URL")]
107+
pub enable_custom_url: bool,
108+
106109
#[clap(long, env = "MICROBIN_DEFAULT_EXPIRY", default_value = "24hour")]
107110
pub default_expiry: String,
108111

@@ -114,7 +117,7 @@ pub struct Args {
114117

115118
#[clap(long, env = "MICROBIN_CUSTOM_CSS")]
116119
pub custom_css: Option<String>,
117-
120+
118121
#[clap(long, env = "MICROBIN_HASH_IDS")]
119122
pub hash_ids: bool,
120123

@@ -211,6 +214,7 @@ impl Args {
211214
max_file_size_encrypted_mb: self.max_file_size_encrypted_mb,
212215
max_file_size_unencrypted_mb: self.max_file_size_unencrypted_mb,
213216
disable_update_checking: self.disable_update_checking,
217+
enable_custom_url: self.enable_custom_url,
214218
}
215219
}
216220
}

src/endpoints/auth_upload.rs

Lines changed: 21 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::args::{Args, ARGS};
22
use crate::endpoints::errors::ErrorTemplate;
3-
use crate::util::animalnumbers::to_u64;
4-
use crate::util::hashids::to_u64 as hashid_to_u64;
3+
use crate::util::hashids::alias_comparator;
54
use crate::util::misc::remove_expired;
65
use crate::AppState;
76
use actix_web::{get, web, HttpResponse};
@@ -25,14 +24,10 @@ pub async fn auth_upload(data: web::Data<AppState>, id: web::Path<String>) -> Ht
2524

2625
remove_expired(&mut pastas);
2726

28-
let intern_id = if ARGS.hash_ids {
29-
hashid_to_u64(&id).unwrap_or(0)
30-
} else {
31-
to_u64(&id).unwrap_or(0)
32-
};
27+
let comparator = alias_comparator(id.as_str());
3328

3429
for (_i, pasta) in pastas.iter().enumerate() {
35-
if pasta.id == intern_id {
30+
if comparator(pasta) {
3631
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
3732
AuthPasta {
3833
args: &ARGS,
@@ -65,14 +60,10 @@ pub async fn auth_upload_with_status(
6560

6661
let (id, status) = param.into_inner();
6762

68-
let intern_id = if ARGS.hash_ids {
69-
hashid_to_u64(&id).unwrap_or(0)
70-
} else {
71-
to_u64(&id).unwrap_or(0)
72-
};
63+
let comparator = alias_comparator(id.as_str());
7364

7465
for (_i, pasta) in pastas.iter().enumerate() {
75-
if pasta.id == intern_id {
66+
if comparator(pasta) {
7667
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
7768
AuthPasta {
7869
args: &ARGS,
@@ -100,14 +91,10 @@ pub async fn auth_raw_pasta(data: web::Data<AppState>, id: web::Path<String>) ->
10091

10192
remove_expired(&mut pastas);
10293

103-
let intern_id = if ARGS.hash_ids {
104-
hashid_to_u64(&id).unwrap_or(0)
105-
} else {
106-
to_u64(&id).unwrap_or(0)
107-
};
94+
let comparator = alias_comparator(id.as_str());
10895

10996
for (_i, pasta) in pastas.iter().enumerate() {
110-
if pasta.id == intern_id {
97+
if comparator(pasta) {
11198
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
11299
AuthPasta {
113100
args: &ARGS,
@@ -140,14 +127,10 @@ pub async fn auth_raw_pasta_with_status(
140127

141128
let (id, status) = param.into_inner();
142129

143-
let intern_id = if ARGS.hash_ids {
144-
hashid_to_u64(&id).unwrap_or(0)
145-
} else {
146-
to_u64(&id).unwrap_or(0)
147-
};
130+
let comparator = alias_comparator(id.as_str());
148131

149132
for (_i, pasta) in pastas.iter().enumerate() {
150-
if pasta.id == intern_id {
133+
if comparator(pasta) {
151134
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
152135
AuthPasta {
153136
args: &ARGS,
@@ -175,14 +158,10 @@ pub async fn auth_edit_private(data: web::Data<AppState>, id: web::Path<String>)
175158

176159
remove_expired(&mut pastas);
177160

178-
let intern_id = if ARGS.hash_ids {
179-
hashid_to_u64(&id).unwrap_or(0)
180-
} else {
181-
to_u64(&id).unwrap_or(0)
182-
};
161+
let comparator = alias_comparator(id.as_str());
183162

184163
for (_, pasta) in pastas.iter().enumerate() {
185-
if pasta.id == intern_id {
164+
if comparator(pasta) {
186165
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
187166
AuthPasta {
188167
args: &ARGS,
@@ -215,14 +194,10 @@ pub async fn auth_edit_private_with_status(
215194

216195
let (id, status) = param.into_inner();
217196

218-
let intern_id = if ARGS.hash_ids {
219-
hashid_to_u64(&id).unwrap_or(0)
220-
} else {
221-
to_u64(&id).unwrap_or(0)
222-
};
197+
let comparator = alias_comparator(id.as_str());
223198

224199
for (_i, pasta) in pastas.iter().enumerate() {
225-
if pasta.id == intern_id {
200+
if comparator(pasta) {
226201
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
227202
AuthPasta {
228203
args: &ARGS,
@@ -250,14 +225,10 @@ pub async fn auth_file(data: web::Data<AppState>, id: web::Path<String>) -> Http
250225

251226
remove_expired(&mut pastas);
252227

253-
let intern_id = if ARGS.hash_ids {
254-
hashid_to_u64(&id).unwrap_or(0)
255-
} else {
256-
to_u64(&id).unwrap_or(0)
257-
};
228+
let comparator = alias_comparator(id.as_str());
258229

259230
for (_, pasta) in pastas.iter().enumerate() {
260-
if pasta.id == intern_id {
231+
if comparator(pasta) {
261232
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
262233
AuthPasta {
263234
args: &ARGS,
@@ -290,14 +261,10 @@ pub async fn auth_file_with_status(
290261

291262
let (id, status) = param.into_inner();
292263

293-
let intern_id = if ARGS.hash_ids {
294-
hashid_to_u64(&id).unwrap_or(0)
295-
} else {
296-
to_u64(&id).unwrap_or(0)
297-
};
264+
let comparator = alias_comparator(id.as_str());
298265

299266
for (_i, pasta) in pastas.iter().enumerate() {
300-
if pasta.id == intern_id {
267+
if comparator(pasta) {
301268
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
302269
AuthPasta {
303270
args: &ARGS,
@@ -325,14 +292,10 @@ pub async fn auth_remove_private(data: web::Data<AppState>, id: web::Path<String
325292

326293
remove_expired(&mut pastas);
327294

328-
let intern_id = if ARGS.hash_ids {
329-
hashid_to_u64(&id).unwrap_or(0)
330-
} else {
331-
to_u64(&id).unwrap_or(0)
332-
};
295+
let comparator = alias_comparator(id.as_str());
333296

334297
for (_, pasta) in pastas.iter().enumerate() {
335-
if pasta.id == intern_id {
298+
if comparator(pasta) {
336299
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
337300
AuthPasta {
338301
args: &ARGS,
@@ -365,14 +328,10 @@ pub async fn auth_remove_private_with_status(
365328

366329
let (id, status) = param.into_inner();
367330

368-
let intern_id = if ARGS.hash_ids {
369-
hashid_to_u64(&id).unwrap_or(0)
370-
} else {
371-
to_u64(&id).unwrap_or(0)
372-
};
331+
let comparator = alias_comparator(id.as_str());
373332

374333
for (_i, pasta) in pastas.iter().enumerate() {
375-
if pasta.id == intern_id {
334+
if comparator(pasta) {
376335
return HttpResponse::Ok().content_type("text/html; charset=utf-8").body(
377336
AuthPasta {
378337
args: &ARGS,

src/endpoints/create.rs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use crate::pasta::PastaFile;
22
use crate::util::animalnumbers::to_animal_names;
3+
use crate::util::animalnumbers::to_u64;
34
use crate::util::db::insert;
45
use crate::util::hashids::to_hashids;
6+
use crate::util::hashids::to_u64 as hashid_to_u64;
57
use crate::util::misc::{encrypt, encrypt_file, is_valid_url};
68
use crate::{AppState, Pasta, ARGS};
79
use actix_multipart::Multipart;
810
use actix_web::error::ErrorBadRequest;
911
use actix_web::cookie::Cookie;
12+
use actix_web::error::ErrorForbidden;
1013
use actix_web::{get, web, Error, HttpResponse, Responder};
1114
use askama::Template;
1215
use bytesize::ByteSize;
@@ -73,8 +76,8 @@ pub fn expiration_to_timestamp(expiration: &str, timenow: i64) -> i64 {
7376

7477
/// receives a file through http Post on url /upload/a-b-c with a, b and c
7578
/// different animals. The client sends the post in response to a form.
76-
// TODO: form field order might need to be changed. In my testing the attachment
77-
// data is nestled between password encryption key etc <21-10-24, dvdsk>
79+
// TODO: form field order might need to be changed. In my testing the attachment
80+
// data is nestled between password encryption key etc <21-10-24, dvdsk>
7881
pub async fn create(
7982
data: web::Data<AppState>,
8083
mut payload: Multipart,
@@ -93,6 +96,7 @@ pub async fn create(
9396
id: rand::thread_rng().gen::<u16>() as u64,
9497
content: String::from(""),
9598
file: None,
99+
custom_alias: None,
96100
extension: String::from(""),
97101
private: false,
98102
readonly: false,
@@ -213,6 +217,31 @@ pub async fn create(
213217
}
214218
continue;
215219
}
220+
"custom_alias" => {
221+
if !ARGS.enable_custom_url {
222+
continue;
223+
}
224+
while let Some(chunk) = field.try_next().await? {
225+
let custom_alias_unchecked =
226+
std::str::from_utf8(&chunk).unwrap().trim().to_string();
227+
// todo check it
228+
if hashid_to_u64(&custom_alias_unchecked).is_ok()
229+
|| to_u64(&&custom_alias_unchecked).is_ok()
230+
{
231+
// prevent conflicts with default url.
232+
return Err(ErrorForbidden("Conflicts with default URL format"));
233+
}
234+
if pastas
235+
.iter()
236+
.any(|pasta| pasta.custom_alias.as_ref() == Some(&custom_alias_unchecked))
237+
{
238+
// prevent conflicts with default url.
239+
return Err(ErrorForbidden("Custom url conflicts with existing pasta"));
240+
}
241+
new_pasta.custom_alias = Some(custom_alias_unchecked);
242+
}
243+
continue;
244+
}
216245
"file" => {
217246
if ARGS.no_file_upload {
218247
continue;
@@ -310,6 +339,7 @@ pub async fn create(
310339
}
311340

312341
let encrypt_server = new_pasta.encrypt_server;
342+
let alias = new_pasta.custom_alias.clone();
313343

314344
pastas.push(new_pasta);
315345

@@ -319,7 +349,9 @@ pub async fn create(
319349
}
320350
}
321351

322-
let slug = if ARGS.hash_ids {
352+
let slug = if let Some(alias) = alias {
353+
alias
354+
} else if ARGS.hash_ids {
323355
to_hashids(id)
324356
} else {
325357
to_animal_names(id)
@@ -336,7 +368,7 @@ pub async fn create(
336368
.unwrap()
337369
.as_secs();
338370
let expiry = timenow + 15; // 15 seconds validity
339-
371+
340372
// Use global HARSH instance
341373
let encoded_token = crate::util::hashids::HARSH.encode(&[expiry, id]);
342374

0 commit comments

Comments
 (0)