implement basic auth and additional upload limits for unauthenticated users
This commit is contained in:
parent
1c43d70457
commit
f97b3d79be
|
@ -1,4 +1,4 @@
|
||||||
FROM ekidd/rust-musl-builder:latest as build
|
FROM ekidd/rust-musl-builder:beta as build
|
||||||
|
|
||||||
USER rust
|
USER rust
|
||||||
WORKDIR /home/rust/src/
|
WORKDIR /home/rust/src/
|
||||||
|
|
47
README.md
47
README.md
|
@ -25,17 +25,40 @@ docker-compose up -d --build
|
||||||
|
|
||||||
## running & config
|
## running & config
|
||||||
|
|
||||||
The static files directory needs to be next to the binary.
|
- The static files directory needs to be next to the binary.
|
||||||
|
- The maximum filename length is 255
|
||||||
|
|
||||||
| environment variable | default value |
|
### General configuration
|
||||||
| -------------------- | -------------------- |
|
|
||||||
| DATABASE_URL | |
|
|
||||||
| DATABASE_USER | |
|
|
||||||
| DATABASE_PASS | |
|
|
||||||
| DATABASE_HOST | localhost |
|
|
||||||
| DATABASE_NAME | datatrash |
|
|
||||||
| FILES_DIR | ./files |
|
|
||||||
| UPLOAD_MAX_BYTES | 8388608 (8MiB) |
|
|
||||||
| BIND_ADDRESS | 0.0.0.0:8000 |
|
|
||||||
|
|
||||||
The maximum filename length is 255
|
| environment variable | default value |
|
||||||
|
| -------------------- | -------------- |
|
||||||
|
| FILES_DIR | ./files |
|
||||||
|
| UPLOAD_MAX_BYTES | 8388608 (8MiB) |
|
||||||
|
| BIND_ADDRESS | 0.0.0.0:8000 |
|
||||||
|
|
||||||
|
### Database configuration
|
||||||
|
|
||||||
|
| environment variable | default value |
|
||||||
|
| -------------------- | ------------- |
|
||||||
|
| DATABASE_URL | |
|
||||||
|
| DATABASE_USER | |
|
||||||
|
| DATABASE_PASS | |
|
||||||
|
| DATABASE_HOST | localhost |
|
||||||
|
| DATABASE_NAME | datatrash |
|
||||||
|
|
||||||
|
### No auth limits configuration
|
||||||
|
|
||||||
|
Require authentication for certain uploads
|
||||||
|
|
||||||
|
- The password is provided as plain text
|
||||||
|
- Uploads with longer validity than NO_AUTH_MAX_TIME require authentication
|
||||||
|
- Uploads larger than NO_AUTH_LARGE_FILE_SIZE require auth when they are valid for longer than
|
||||||
|
NO_AUTH_LARGE_FILE_MAX_TIME
|
||||||
|
- All times are in seconds, the size is in bytes
|
||||||
|
|
||||||
|
| environment variable | default value |
|
||||||
|
| --------------------------- | ------------- |
|
||||||
|
| AUTH_PASSWORD | |
|
||||||
|
| NO_AUTH_MAX_TIME | |
|
||||||
|
| NO_AUTH_LARGE_FILE_MAX_TIME | |
|
||||||
|
| NO_AUTH_LARGE_FILE_SIZE | |
|
||||||
|
|
|
@ -2,16 +2,38 @@ version: "3.3"
|
||||||
services:
|
services:
|
||||||
datatrash:
|
datatrash:
|
||||||
build: .
|
build: .
|
||||||
|
container_name: datatrash
|
||||||
|
volumes:
|
||||||
|
- /data/datatrash/files:/opt/datatrash/files
|
||||||
environment:
|
environment:
|
||||||
DATABASE_USER: admin
|
DATABASE_HOST: db
|
||||||
DATABASE_PASSWORD: secure
|
DATABASE_USER: datatrash
|
||||||
DATABASE_HOST: postgres
|
DATABASE_PASS: jNmLZYr75as0W5TY7iSaIEVPSa2awaWAAgC5Zt8JsRAXbYrscLW4Dk7ZxHL1Bu4v
|
||||||
ports:
|
UPLOAD_MAX_BYTES: 1073741824
|
||||||
- '8000:8000'
|
AUTH_PASSWORD: auth_password
|
||||||
postgres:
|
NO_AUTH_MAX_TIME: 604800
|
||||||
|
NO_AUTH_LARGE_FILE_MAX_TIME: 1800
|
||||||
|
NO_AUTH_LARGE_FILE_SIZE: 10485760
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- nginx
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
db:
|
||||||
image: postgres
|
image: postgres
|
||||||
|
container_name: datatrash-db
|
||||||
|
volumes:
|
||||||
|
- /data/datatrash/db:/var/lib/postgresql/data
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: admin
|
POSTGRES_DB: datatrash
|
||||||
POSTGRES_PASSWORD: secure
|
POSTGRES_USER: datatrash
|
||||||
|
POSTGRES_PASSWORD: jNmLZYr75as0W5TY7iSaIEVPSa2awaWAAgC5Zt8JsRAXbYrscLW4Dk7ZxHL1Bu4v
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nginx:
|
||||||
|
external:
|
||||||
|
name: nginx_default
|
||||||
|
|
|
@ -6,6 +6,15 @@ use async_std::{fs, path::PathBuf};
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub files_dir: PathBuf,
|
pub files_dir: PathBuf,
|
||||||
pub max_file_size: Option<u64>,
|
pub max_file_size: Option<u64>,
|
||||||
|
pub no_auth_limits: Option<NoAuthLimits>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NoAuthLimits {
|
||||||
|
pub auth_password: String,
|
||||||
|
pub max_time: u64,
|
||||||
|
pub large_file_max_time: u64,
|
||||||
|
pub large_file_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_config() -> Config {
|
pub async fn get_config() -> Config {
|
||||||
|
@ -20,8 +29,30 @@ pub async fn get_config() -> Config {
|
||||||
.await
|
.await
|
||||||
.expect("could not create directory for storing files");
|
.expect("could not create directory for storing files");
|
||||||
|
|
||||||
|
let no_auth_limits = match (
|
||||||
|
env::var("AUTH_PASSWORD").ok(),
|
||||||
|
env_number("NO_AUTH_MAX_TIME"),
|
||||||
|
env_number("NO_AUTH_LARGE_FILE_MAX_TIME"),
|
||||||
|
env_number("NO_AUTH_LARGE_FILE_SIZE"),
|
||||||
|
) {
|
||||||
|
(Some(auth_password), Some(max_time), Some(large_file_max_time), Some(large_file_size)) => {
|
||||||
|
Some(NoAuthLimits {
|
||||||
|
auth_password,
|
||||||
|
max_time,
|
||||||
|
large_file_max_time,
|
||||||
|
large_file_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
files_dir,
|
files_dir,
|
||||||
max_file_size,
|
max_file_size,
|
||||||
|
no_auth_limits,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env_number(variable: &str) -> Option<u64> {
|
||||||
|
env::var(variable).ok().and_then(|n| n.parse::<u64>().ok())
|
||||||
|
}
|
||||||
|
|
|
@ -52,6 +52,9 @@ async fn main() -> std::io::Result<()> {
|
||||||
web::resource(["/upload/{id}", "/upload/{id}/{name}"])
|
web::resource(["/upload/{id}", "/upload/{id}/{name}"])
|
||||||
.route(web::get().to(upload::uploaded)),
|
.route(web::get().to(upload::uploaded)),
|
||||||
)
|
)
|
||||||
|
.service(
|
||||||
|
web::resource("/assets/auth-hide.js").route(web::get().to(upload::auth_hide)),
|
||||||
|
)
|
||||||
.service(Files::new("/static", "static").disable_content_disposition())
|
.service(Files::new("/static", "static").disable_content_disposition())
|
||||||
.service(
|
.service(
|
||||||
web::resource([
|
web::resource([
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::file_kind::FileKind;
|
use crate::{config, file_kind::FileKind};
|
||||||
use actix_multipart::{Field, Multipart};
|
use actix_multipart::{Field, Multipart};
|
||||||
use actix_web::{error, http::header::DispositionParam};
|
use actix_web::{error, http::header::DispositionParam};
|
||||||
use async_std::{fs, fs::File, path::Path, prelude::*};
|
use async_std::{fs, fs::File, path::Path, prelude::*};
|
||||||
|
@ -16,12 +16,14 @@ pub(crate) async fn parse_multipart(
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
file_id: &str,
|
file_id: &str,
|
||||||
filename: &Path,
|
filename: &Path,
|
||||||
max_size: Option<u64>,
|
config: &config::Config,
|
||||||
) -> Result<UploadConfig, error::Error> {
|
) -> Result<UploadConfig, error::Error> {
|
||||||
let mut original_name: Option<String> = None;
|
let mut original_name: Option<String> = None;
|
||||||
let mut keep_for: Option<String> = None;
|
let mut keep_for: Option<String> = None;
|
||||||
let mut kind: Option<FileKind> = None;
|
let mut kind: Option<FileKind> = None;
|
||||||
let mut delete_on_download = false;
|
let mut delete_on_download = false;
|
||||||
|
let mut password = None;
|
||||||
|
let mut size = 0;
|
||||||
|
|
||||||
while let Ok(Some(field)) = payload.try_next().await {
|
while let Ok(Some(field)) = payload.try_next().await {
|
||||||
let name = get_field_name(&field)?;
|
let name = get_field_name(&field)?;
|
||||||
|
@ -40,7 +42,7 @@ pub(crate) async fn parse_multipart(
|
||||||
let mut file = fs::File::create(&filename)
|
let mut file = fs::File::create(&filename)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| error::ErrorInternalServerError("could not create file"))?;
|
.map_err(|_| error::ErrorInternalServerError("could not create file"))?;
|
||||||
write_to_file(&mut file, field, max_size).await?;
|
size = write_to_file(&mut file, field, config.max_file_size).await?;
|
||||||
}
|
}
|
||||||
"text" => {
|
"text" => {
|
||||||
if original_name.is_some() {
|
if original_name.is_some() {
|
||||||
|
@ -51,11 +53,14 @@ pub(crate) async fn parse_multipart(
|
||||||
let mut file = fs::File::create(&filename)
|
let mut file = fs::File::create(&filename)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| error::ErrorInternalServerError("could not create file"))?;
|
.map_err(|_| error::ErrorInternalServerError("could not create file"))?;
|
||||||
write_to_file(&mut file, field, max_size).await?;
|
size = write_to_file(&mut file, field, config.max_file_size).await?;
|
||||||
}
|
}
|
||||||
"delete_on_download" => {
|
"delete_on_download" => {
|
||||||
delete_on_download = dbg!(parse_string(name, field).await?) != "false";
|
delete_on_download = dbg!(parse_string(name, field).await?) != "false";
|
||||||
}
|
}
|
||||||
|
"password" => {
|
||||||
|
password = Some(parse_string(name, field).await?);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -66,21 +71,24 @@ pub(crate) async fn parse_multipart(
|
||||||
if original_name.len() > 255 {
|
if original_name.len() > 255 {
|
||||||
return Err(error::ErrorBadRequest("filename is too long"));
|
return Err(error::ErrorBadRequest("filename is too long"));
|
||||||
}
|
}
|
||||||
let valid_till = if let Some(keep_for) = keep_for {
|
let validated_keep_for: u64 = if let Some(keep_for) = keep_for {
|
||||||
let keep_for = keep_for.parse().map_err(|e| {
|
let seconds = keep_for.parse().map_err(|e| {
|
||||||
error::ErrorBadRequest(format!("field keep_for is not a number: {}", e))
|
error::ErrorBadRequest(format!("field keep_for is not a number: {}", e))
|
||||||
})?;
|
})?;
|
||||||
let max_keep_for = Duration::days(31).num_seconds();
|
let max_keep_for = Duration::days(31).num_seconds() as u64;
|
||||||
if keep_for > max_keep_for {
|
if seconds > max_keep_for {
|
||||||
return Err(error::ErrorBadRequest(format!(
|
return Err(error::ErrorBadRequest(format!(
|
||||||
"maximum allowed validity is {} seconds, but you specified {} seconds",
|
"maximum allowed validity is {} seconds, but you specified {} seconds",
|
||||||
max_keep_for, keep_for
|
max_keep_for, seconds
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Local::now() + Duration::seconds(keep_for)
|
seconds
|
||||||
} else {
|
} else {
|
||||||
Local::now() + Duration::seconds(1800)
|
1800
|
||||||
};
|
};
|
||||||
|
let valid_till = Local::now() + Duration::seconds(validated_keep_for as i64);
|
||||||
|
|
||||||
|
check_auth_requirements(size, validated_keep_for, password, config)?;
|
||||||
|
|
||||||
Ok(UploadConfig {
|
Ok(UploadConfig {
|
||||||
original_name,
|
original_name,
|
||||||
|
@ -90,6 +98,25 @@ pub(crate) async fn parse_multipart(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_auth_requirements(
|
||||||
|
size: u64,
|
||||||
|
validated_keep_for: u64,
|
||||||
|
password: Option<String>,
|
||||||
|
config: &config::Config,
|
||||||
|
) -> Result<(), error::Error> {
|
||||||
|
if let Some(no_auth_limits) = &config.no_auth_limits {
|
||||||
|
let requires_auth = validated_keep_for > no_auth_limits.max_time
|
||||||
|
|| validated_keep_for > no_auth_limits.large_file_max_time
|
||||||
|
&& size > no_auth_limits.large_file_size;
|
||||||
|
if requires_auth && password.as_ref() != Some(&no_auth_limits.auth_password) {
|
||||||
|
return Err(error::ErrorBadRequest(
|
||||||
|
"upload requires authentication, but authentication was incorrect",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn get_field_name(field: &Field) -> Result<String, error::Error> {
|
fn get_field_name(field: &Field) -> Result<String, error::Error> {
|
||||||
Ok(field
|
Ok(field
|
||||||
.content_disposition()
|
.content_disposition()
|
||||||
|
@ -117,12 +144,12 @@ async fn write_to_file(
|
||||||
file: &mut File,
|
file: &mut File,
|
||||||
mut field: actix_multipart::Field,
|
mut field: actix_multipart::Field,
|
||||||
max_size: Option<u64>,
|
max_size: Option<u64>,
|
||||||
) -> Result<(), error::Error> {
|
) -> Result<u64, error::Error> {
|
||||||
let mut written_bytes: u64 = 0;
|
let mut written_bytes: u64 = 0;
|
||||||
while let Some(chunk) = field.next().await {
|
while let Some(chunk) = field.next().await {
|
||||||
let chunk = chunk.map_err(error::ErrorBadRequest)?;
|
let chunk = chunk.map_err(error::ErrorBadRequest)?;
|
||||||
|
written_bytes += chunk.len() as u64;
|
||||||
if let Some(max_size) = max_size {
|
if let Some(max_size) = max_size {
|
||||||
written_bytes += chunk.len() as u64;
|
|
||||||
if written_bytes > max_size {
|
if written_bytes > max_size {
|
||||||
return Err(error::ErrorBadRequest(format!(
|
return Err(error::ErrorBadRequest(format!(
|
||||||
"exceeded maximum file size of {} bytes",
|
"exceeded maximum file size of {} bytes",
|
||||||
|
@ -134,7 +161,7 @@ async fn write_to_file(
|
||||||
.await
|
.await
|
||||||
.map_err(|_| error::ErrorInternalServerError("could not write file"))?;
|
.map_err(|_| error::ErrorInternalServerError("could not write file"))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(written_bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_original_filename(field: &actix_multipart::Field) -> Option<String> {
|
fn get_original_filename(field: &actix_multipart::Field) -> Option<String> {
|
||||||
|
|
|
@ -9,6 +9,8 @@ use rand::prelude::SliceRandom;
|
||||||
use sqlx::postgres::PgPool;
|
use sqlx::postgres::PgPool;
|
||||||
|
|
||||||
const INDEX_HTML: &str = include_str!("../template/index.html");
|
const INDEX_HTML: &str = include_str!("../template/index.html");
|
||||||
|
const INDEX_AUTH_HTML: &str = include_str!("../template/index-auth.html");
|
||||||
|
const AUTH_HIDE_JS: &str = include_str!("../template/auth-hide.js");
|
||||||
const UPLOAD_HTML: &str = include_str!("../template/upload.html");
|
const UPLOAD_HTML: &str = include_str!("../template/upload.html");
|
||||||
|
|
||||||
const ID_CHARS: &[char] = &[
|
const ID_CHARS: &[char] = &[
|
||||||
|
@ -16,12 +18,40 @@ const ID_CHARS: &[char] = &[
|
||||||
'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
];
|
];
|
||||||
|
|
||||||
pub async fn index(req: web::HttpRequest) -> Result<HttpResponse, Error> {
|
pub async fn index(
|
||||||
|
req: web::HttpRequest,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
let upload_url = format!("{}/upload", get_host_url(&req));
|
let upload_url = format!("{}/upload", get_host_url(&req));
|
||||||
let index_html = INDEX_HTML.replace("{upload_url}", upload_url.as_str());
|
let index_html = if config.no_auth_limits.is_some() {
|
||||||
|
INDEX_AUTH_HTML
|
||||||
|
} else {
|
||||||
|
INDEX_HTML
|
||||||
|
};
|
||||||
|
let filled_index_html = index_html.replace("{upload_url}", upload_url.as_str());
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(index_html))
|
.body(filled_index_html))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn auth_hide(config: web::Data<Config>) -> Result<HttpResponse, Error> {
|
||||||
|
if let Some(no_auth_limits) = &config.no_auth_limits {
|
||||||
|
let auth_hide_js = AUTH_HIDE_JS
|
||||||
|
.replace("{no_auth_max_time}", &no_auth_limits.max_time.to_string())
|
||||||
|
.replace(
|
||||||
|
"{no_auth_large_file_max_time}",
|
||||||
|
&no_auth_limits.large_file_max_time.to_string(),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"{no_auth_large_file_size}",
|
||||||
|
&no_auth_limits.large_file_size.to_string(),
|
||||||
|
);
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("application/javascript")
|
||||||
|
.body(auth_hide_js))
|
||||||
|
} else {
|
||||||
|
Err(error::ErrorNotFound("file not found"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn upload(
|
pub async fn upload(
|
||||||
|
@ -35,8 +65,7 @@ pub async fn upload(
|
||||||
let mut filename = config.files_dir.clone();
|
let mut filename = config.files_dir.clone();
|
||||||
filename.push(&file_id);
|
filename.push(&file_id);
|
||||||
|
|
||||||
let parsed_multipart =
|
let parsed_multipart = multipart::parse_multipart(payload, &file_id, &filename, &config).await;
|
||||||
multipart::parse_multipart(payload, &file_id, &filename, config.max_file_size).await;
|
|
||||||
let UploadConfig {
|
let UploadConfig {
|
||||||
original_name,
|
original_name,
|
||||||
valid_till,
|
valid_till,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
const fileUpload = document.getElementById("file-upload");
|
||||||
|
const textUpload = document.getElementById("text-upload");
|
||||||
|
const keepFor = document.getElementById("keep_for");
|
||||||
|
const passwordInput = document.getElementById("password-input");
|
||||||
|
|
||||||
|
const maxTime = Number("{no_auth_max_time}");
|
||||||
|
const largeFileMaxTime = Number("{no_auth_large_file_max_time}");
|
||||||
|
const largeFileSize = Number("{no_auth_large_file_size}");
|
||||||
|
const updatePasswordInput = () => {
|
||||||
|
const requirePassword = keep > maxTime || (size > largeFileSize && keep > largeFileMaxTime);
|
||||||
|
passwordInput.className = requirePassword ? "" : "hidden";
|
||||||
|
};
|
||||||
|
|
||||||
|
let keep = Number(keepFor.value);
|
||||||
|
let size = fileUpload.files[0]
|
||||||
|
? fileUpload.files[0].size
|
||||||
|
: textUpload.value.length;
|
||||||
|
updatePasswordInput();
|
||||||
|
|
||||||
|
fileUpload.addEventListener("change", (e) => {
|
||||||
|
size = fileUpload.files[0]
|
||||||
|
? fileUpload.files[0].size
|
||||||
|
: textUpload.value.length;
|
||||||
|
updatePasswordInput();
|
||||||
|
});
|
||||||
|
textUpload.addEventListener("input", (e) => {
|
||||||
|
if (!fileUpload.files[0]) {
|
||||||
|
size = textUpload.value.length;
|
||||||
|
updatePasswordInput();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keepFor.addEventListener("change", (e) => {
|
||||||
|
keep = Number(keepFor.value);
|
||||||
|
updatePasswordInput();
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de-DE">
|
||||||
|
<head>
|
||||||
|
<title>datatrash</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="description" content="Temporärer Dateiaustausch" />
|
||||||
|
<link href="/static/index.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>datatrash</h1>
|
||||||
|
<form action="/upload" method="POST" enctype="multipart/form-data">
|
||||||
|
<label for="file-upload">datei</label>
|
||||||
|
<br />
|
||||||
|
<input id="file-upload" type="file" name="file" />
|
||||||
|
<br />
|
||||||
|
<label for="text-upload">oder asciitrash</label>
|
||||||
|
<br />
|
||||||
|
<textarea id="text-upload" name="text" rows="20" cols="120"></textarea>
|
||||||
|
<br />
|
||||||
|
<label for="keep_for">gültig für</label>
|
||||||
|
<select id="keep_for" name="keep_for">
|
||||||
|
<option value="1800">30 minuten</option>
|
||||||
|
<option value="3600">60 minuten</option>
|
||||||
|
<option value="43200">12 stunden</option>
|
||||||
|
<option value="86400">24 stunden</option>
|
||||||
|
<option value="604800">eine woche</option>
|
||||||
|
<option value="2678400">einen monat</option>
|
||||||
|
</select>
|
||||||
|
<br />
|
||||||
|
<input
|
||||||
|
id="delete_on_download"
|
||||||
|
type="checkbox"
|
||||||
|
name="delete_on_download"
|
||||||
|
/>
|
||||||
|
<label for="delete_on_download">nach einem download löschen</label>
|
||||||
|
<br />
|
||||||
|
<div id="password-input">
|
||||||
|
<label for="password">
|
||||||
|
authentifizierung für große, oder lang gültige uploads
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
<input id="password" name="password" type="password" />
|
||||||
|
</div>
|
||||||
|
<input class="main button" type="submit" value="hochladen" />
|
||||||
|
</form>
|
||||||
|
<section class="usage">
|
||||||
|
<pre>
|
||||||
|
file upload
|
||||||
|
curl -F 'file=@yourfile.rs' {upload_url}
|
||||||
|
text upload
|
||||||
|
curl -F 'text=your text' {upload_url}
|
||||||
|
including time
|
||||||
|
curl -F 'text=your text' -F 'keep_for=1800' {upload_url}
|
||||||
|
limit to one download
|
||||||
|
curl -F 'text=your text' -F 'delete_on_download=true' {upload_url}</pre
|
||||||
|
>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<script src="/assets/auth-hide.js" lang="javascript"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue