Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
06dc355
init wasm module
FlareFlo Apr 24, 2026
fca579b
Fix wasm and implement prototype for ENCDEC mechanism
FlareFlo Apr 24, 2026
1d5d864
Impl somewhat working ENCDEC
FlareFlo Apr 24, 2026
369f20c
Add caddy proxyconfig
FlareFlo Apr 24, 2026
84856f6
Fix firefox compat
FlareFlo Apr 24, 2026
239174e
Increase encryption chunk size to 10MB
FlareFlo Apr 24, 2026
2bf9e5c
Add progress to decryption on download
FlareFlo Apr 24, 2026
3f42271
Stream downloads too
FlareFlo Apr 25, 2026
338d122
Properly handle WS error
FlareFlo Apr 30, 2026
1073d4e
Render QR code in browser now
FlareFlo Apr 30, 2026
84abe04
Add TODO comment
FlareFlo Apr 30, 2026
bda778d
Reformat all html
FlareFlo May 3, 2026
8816960
Fix incorrectly formatted include
FlareFlo May 3, 2026
e33f52a
Fix incorrectly formatted templates
FlareFlo May 3, 2026
62e6f2e
Strip useless progress token logic out
FlareFlo May 3, 2026
748da54
Fix reload on delete after decryption
FlareFlo May 3, 2026
87a7730
Allow encryption option and use selects
FlareFlo May 3, 2026
2c695d5
Fix class styling
FlareFlo May 3, 2026
ce5f446
Add dockerignore and build tapfer_crypt for docker
FlareFlo May 3, 2026
3ca3910
Document local caddy dev config
FlareFlo May 4, 2026
cd354d0
Update styling of homepage
FlareFlo May 4, 2026
8f899bc
Disable E2EE in deposit mode until the footgun is resolved
FlareFlo May 4, 2026
b3fd10b
Fix 404 page not rendering
FlareFlo May 4, 2026
5873791
Refine 404 system a little
FlareFlo May 4, 2026
c35cb91
Fix + fmt
FlareFlo May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
target/
tapfer_crypt/target/
tapfer_crypt/pkg/
data/
.git/
8 changes: 8 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# For local development purposes
{
http_port 4080
}

localhost:4000 {
reverse_proxy localhost:3000
}
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tokio = { version = "1.0", features = ["full"] }
tokio-util = { version = "0.7.14", features = ["io"] }
futures-core = "0.3.31"
futures-util = "0.3.31"
tokio-stream = "0.1.18"


# Parsing and formats
Expand Down
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
FROM docker.io/rust:1.92 as builder

RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

WORKDIR /usr/src/app

COPY Cargo.toml Cargo.lock ./
COPY ./src ./src
COPY templates ./templates
COPY tapfer_crypt ./tapfer_crypt

RUN cargo build --release
RUN cd tapfer_crypt && wasm-pack build --target web --release

FROM docker.io/archlinux
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/target/release/tapfer .
COPY ./static ./static
COPY --from=builder /usr/src/app/tapfer_crypt/pkg ./tapfer_crypt/pkg

CMD ["./tapfer"]
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@
2. edit `docker-compose.yml` HOST variable to match the domain that uploads will be available on (for QR code generation)
3. Configure your reverse proxy if applicable according to `rever_proxy`. Default configs are provided, replace {{TLD}} with your real TLD
4. (optional) Configure a ZFS storage quota (or similar) on the `data` folder or keep the data a volume without a local mountpoint
5. `docker-compose up -d --build` To build and deploy the container
5. `docker-compose up -d --build` To build and deploy the container

# Local dev
run caddy for local HTTP/2 support
```sh
caddy reverse-proxy --from localhost:4000 --to localhost:3000
```
5 changes: 5 additions & 0 deletions dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Caddy proxy
The frontend requires some features that are only enabled in secure HTTPS windows
```shell
caddy run
```
9 changes: 1 addition & 8 deletions src/api_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,11 @@ use crate::handlers::delete::__path_request_delete_asset;
use crate::handlers::download::__path_download_file;
use crate::handlers::qrcode::__path_get_qrcode_from_id;
use crate::upload::__path_accept_form;
use crate::upload::__path_progress_token_to_id;
use utoipa::OpenApi;

#[derive(OpenApi)]
#[openapi(
paths(
accept_form,
download_file,
progress_token_to_id,
request_delete_asset,
get_qrcode_from_id
),
paths(accept_form, download_file, request_delete_asset, get_qrcode_from_id),
info(title = "Tapfer API", version = "1.0")
)]
pub struct ApiDoc;
15 changes: 5 additions & 10 deletions src/handlers/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::configuration::{EMBED_DESCRIPTION, FAVICON, QR_CODE_ECC, QR_CODE_SIZE};
use crate::structs::error::TapferResult;
use crate::websocket::wss_method;
use askama::Template;
use axum::extract::{Query, WebSocketUpgrade};
use axum::response::{Html, IntoResponse, Response};
Expand All @@ -17,26 +16,22 @@ pub struct Deposit {
qr_size: usize,
qr_b64: String,
ws_url: String,
upload_url: String,
}

pub async fn show_form(Host(host): Host) -> TapferResult<impl IntoResponse> {
let deposit_id = Uuid::new_v4().as_u64_pair().0; // Hacky? Sure. But this avoids another RNG library that we use once
let url = format!("https://{host}?deposit={deposit_id}");

let qr_code = qrcode_generator::to_png_to_vec_from_str(
format!("https://{host}?deposit={deposit_id}",),
QR_CODE_ECC,
QR_CODE_SIZE,
)?;
let qr_code = qrcode_generator::to_png_to_vec_from_str(&url, QR_CODE_ECC, QR_CODE_SIZE)?;

let template = Deposit {
embed_image_url: FAVICON,
embed_description: EMBED_DESCRIPTION,
qr_size: QR_CODE_SIZE,
qr_b64: BASE64_STANDARD.encode(&qr_code),
ws_url: format!(
"{}://{host}/deposit/ws?deposit={deposit_id}",
wss_method(&host)
),
upload_url: url,
ws_url: format!("wss://{host}/deposit/ws?deposit={deposit_id}",),
};

Ok(Html(template.render()?))
Expand Down
18 changes: 9 additions & 9 deletions src/handlers/download.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use crate::configuration::{DOWNLOAD_CHUNKSIZE, EMBED_DESCRIPTION, QR_CODE_SIZE};
use crate::handlers;
use crate::handlers::checksum::get_sha512_for_asset;
use crate::handlers::is_localhost;
use crate::handlers::qrcode::base64_qr_from_id;
use crate::retention_control::delete_asset;
use crate::structs::error::{TapferError, TapferResult};
use crate::structs::file_meta::{FileMeta, RemovalPolicy};
use crate::structs::tapfer_id::TapferId;
use crate::updown::upload_handle::UploadHandle;
use crate::updown::upload_pool::UploadFsm;
use crate::websocket::wss_method;
use askama::Template;
use axum::body::Body;
use axum::extract::Path;
Expand Down Expand Up @@ -38,6 +36,7 @@ struct DownloadTemplate<'a> {
download_url: &'a str,
mimetype: &'a str,
filesize: &'a str,
file_size_bits: u64,
embed_image_url: &'a str,
qr_size: usize,
embed_description: &'a str,
Expand All @@ -62,16 +61,16 @@ pub async fn download_html(
RemovalPolicy::Expiry { .. } => meta.expires_on_utc().unwrap().format(&DES)?.clone(),
};

let localhost = is_localhost(&host);
let prefix = if host.contains("localhost") {
""
} else {
"cdn."
};
let sha512 = get_sha512_for_asset(id)?;
let template = DownloadTemplate {
filename: meta.name(),
expiry: &expiry,
download_url: if !localhost {
&format!("https://cdn.{host}/uploads/{id}/download")
} else {
&format!("http://localhost:3000/uploads/{id}/download")
},
download_url: &format!("https://{prefix}{host}/uploads/{id}/download"),
mimetype: meta.content_type(),
filesize: if meta.known_size().is_some() {
&human_bytes(meta.size() as f64)
Expand All @@ -80,6 +79,7 @@ pub async fn download_html(
} else {
&human_bytes(meta.size() as f64)
},
file_size_bits: meta.known_size().unwrap_or(0),
embed_image_url: &format!("/qrcg/{id}"),
qr_size: QR_CODE_SIZE,
embed_description: EMBED_DESCRIPTION,
Expand All @@ -88,7 +88,7 @@ pub async fn download_html(
unix_expiry: meta
.expires_on_utc()
.map_or(0, time::UtcDateTime::unix_timestamp),
ws_url: &format!("{}://{host}/uploads/{id}/ws", wss_method(&host)),
ws_url: &format!("wss://{host}/uploads/{id}/ws"),
sha512: sha512.as_deref().unwrap_or("computing..."),
sha512url: format!("/uploads/{id}/checksum.sha512"),
};
Expand Down
16 changes: 3 additions & 13 deletions src/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use crate::UPLOAD_POOL;
use crate::handlers::download::UpDownFsm;
use crate::handlers::not_found::NotFound;
use crate::handlers::not_found::Reason404;
use crate::structs::error::{TapferError, TapferResult};
use crate::structs::file_meta::FileMeta;
use crate::structs::tapfer_id::TapferId;
use askama::Template;
use axum::http::StatusCode;
use axum::response::Html;
use std::str::FromStr;
use tokio::fs;

Expand All @@ -15,7 +12,7 @@ pub mod delete;
pub mod deposit;
pub mod download;
pub mod homepage;
mod not_found;
pub mod not_found;
pub mod qrcode;
pub mod upload;

Expand All @@ -33,10 +30,7 @@ async fn get_any_meta(path: &String) -> TapferResult<((TapferId, FileMeta), UpDo
match UPLOAD_POOL.uploads.get(&id) {
// The upload is not in progress either, so it does not exist
None => {
return Err(TapferError::Custom {
status_code: StatusCode::NOT_FOUND,
body: Html(NotFound::default().render()?),
});
return Err(TapferError::NotFound(Reason404::Deleted));
}
// The upload is in-progress
Some(handle) => {
Expand All @@ -58,7 +52,3 @@ async fn get_any_meta(path: &String) -> TapferResult<((TapferId, FileMeta), UpDo
};
Ok(res)
}

pub fn is_localhost(host: &str) -> bool {
host.starts_with("localhost") || host.starts_with("127.0.0.1")
}
48 changes: 46 additions & 2 deletions src/handlers/not_found.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,62 @@
use crate::configuration::{EMBED_DESCRIPTION, FAVICON};
use askama::Template;
use axum::extract::Query;
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse, Redirect};
use std::fmt::{Display, Formatter};

#[derive(Template)]
#[template(path = "404.html")]
pub struct NotFound {
embed_image_url: &'static str,
embed_description: &'static str,
reason: &'static str,
}

impl Default for NotFound {
fn default() -> Self {
impl NotFound {
pub fn with_reason(hint: Option<Reason404>) -> Self {
Self {
embed_image_url: FAVICON,
embed_description: EMBED_DESCRIPTION,
reason: match hint {
None => "The asset you're looking for doesn’t exist or has been deleted",
Some(Reason404::Deleted) => "The asset has been deleted",
},
}
}
}

pub async fn not_found_handler(Query(params): Query<Params>) -> impl IntoResponse {
match NotFound::with_reason(params.hint).render() {
Ok(html) => (StatusCode::NOT_FOUND, Html(html)),
Err(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
Html("500 Internal Server Error".to_string()),
),
}
}

pub fn redirect_not_found(reason404: Reason404) -> impl IntoResponse {
Redirect::to(&format!("/404?hint={reason404}"))
}

#[derive(Debug, Clone, serde::Deserialize)]
pub struct Params {
hint: Option<Reason404>,
}

#[derive(Debug, Copy, Clone, serde::Deserialize)]
pub enum Reason404 {
#[serde(rename = "deleted")]
Deleted,
}

impl Display for Reason404 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Reason404::Deleted => {
write!(f, "deleted")
}
}
}
}
Loading