master
Edward Shen 2021-10-27 01:49:06 -07:00
parent 16bce1d11b
commit d2755f82c9
Signed by: edward
GPG Key ID: 19182661E818369F
9 changed files with 116 additions and 54 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@
**/database
**/dist/
**/node_modules
test.*
test.*
dist.tar.zst

View File

@ -6,6 +6,9 @@ set -euxo pipefail
yarn
trunk build --release
sed -i 's#/index#/static/index#g' dist/index.html
sed -i 's#stylesheet" href="/main#stylesheet" href="/static/main#g' dist/index.html
# Build server
cargo build --release --bin omegaupload-server
@ -18,4 +21,8 @@ mkdir -p dist/static
find dist -type f -exec mv {} dist/static/ ";"
strip target/release/omegaupload-server
cp target/release/omegaupload-server dist/omegaupload-server
cp target/release/omegaupload-server dist/omegaupload-server
tar -cvf dist.tar dist
rm -r dist.tar.zst
zstd -T0 --ultra --rm -22 dist.tar

View File

@ -7,7 +7,7 @@ use anyhow::{anyhow, bail, Context, Result};
use atty::Stream;
use clap::Parser;
use omegaupload_common::crypto::{gen_key_nonce, open_in_place, seal_in_place, Key};
use omegaupload_common::{base64, hash, Expiration, ParsedUrl, Url};
use omegaupload_common::{base64, hash, Expiration, ParsedUrl, Url, API_ENDPOINT};
use reqwest::blocking::Client;
use reqwest::header::EXPIRES;
use reqwest::StatusCode;
@ -99,7 +99,12 @@ fn handle_upload(mut url: Url, password: Option<SecretString>) -> Result<()> {
Ok(())
}
fn handle_download(url: ParsedUrl) -> Result<()> {
fn handle_download(mut url: ParsedUrl) -> Result<()> {
url.sanitized_url.set_path(&dbg!(format!(
"{}{}",
API_ENDPOINT,
url.sanitized_url.path()
)));
let res = Client::new()
.get(url.sanitized_url)
.send()
@ -109,7 +114,8 @@ fn handle_download(url: ParsedUrl) -> Result<()> {
bail!("Got bad response from server: {}", res.status());
}
let expiration_text = dbg!(res.headers())
let expiration_text = res
.headers()
.get(EXPIRES)
.and_then(|v| Expiration::try_from(v).ok())
.as_ref()

View File

@ -16,6 +16,8 @@ pub use url::Url;
use crate::crypto::{Key, Nonce};
pub const API_ENDPOINT: &str = "/api";
pub mod base64 {
/// URL-safe Base64 encoding.
pub fn encode(input: impl AsRef<[u8]>) -> String {
@ -153,13 +155,13 @@ impl From<&str> for PartialParsedUrl {
for (key, value) in args {
match (key, value) {
("key", Some(value)) => {
decryption_key = dbg!(base64::decode(value).map(|k| *Key::from_slice(&k)).ok());
decryption_key = base64::decode(value).map(|k| *Key::from_slice(&k)).ok();
}
("pw", _) => {
needs_password = true;
}
("nonce", Some(value)) => {
nonce = dbg!(base64::decode(value).as_deref().map(Nonce::from_slice).ok());
nonce = base64::decode(value).as_deref().map(Nonce::from_slice).ok();
}
_ => (),
}

View File

@ -10,10 +10,11 @@ use axum::extract::{Extension, Path, TypedHeader};
use axum::handler::{get, post};
use axum::http::header::EXPIRES;
use axum::http::StatusCode;
use axum::response::Html;
use axum::{service, AddExtensionLayer, Router};
use chrono::Utc;
use headers::HeaderMap;
use omegaupload_common::Expiration;
use omegaupload_common::{Expiration, API_ENDPOINT};
use rand::thread_rng;
use rand::Rng;
use rocksdb::{ColumnFamilyDescriptor, IteratorMode};
@ -61,11 +62,11 @@ async fn main() -> Result<()> {
.route("/", post(upload::<SHORT_CODE_SIZE>))
.route(
"/:code",
get(|| async { include_str!("../../dist/index.html") }),
get(|| async { Html(include_str!("../../dist/index.html")) }),
)
.nest("/static", root_service)
.route(
"/api/:code",
&format!("{}{}", API_ENDPOINT.to_string(), "/:code"),
get(paste::<SHORT_CODE_SIZE>).delete(delete::<SHORT_CODE_SIZE>),
)
.layer(AddExtensionLayer::new(db))

View File

@ -27,6 +27,7 @@ zip = { version = "0.5", default-features = false, features = ["deflate"] }
[dependencies.web-sys]
version = "0.3"
features = [
"BlobPropertyBag",
"TextDecoder",
"IdbFactory",
"IdbOpenDbRequest",

View File

@ -5,9 +5,9 @@ use std::sync::Arc;
use gloo_console::log;
use js_sys::{Array, Uint8Array};
use omegaupload_common::crypto::{open_in_place, Key, Nonce};
use wasm_bindgen::JsCast;
use web_sys::Blob;
use serde::Serialize;
use wasm_bindgen::JsCast;
use web_sys::{Blob, BlobPropertyBag};
#[derive(Clone, Serialize)]
pub struct ArchiveMeta {
@ -66,6 +66,9 @@ pub fn decrypt(
if let Ok(decrypted) = std::str::from_utf8(container) {
Ok(DecryptedData::String(Arc::new(decrypted.to_owned())))
} else {
let mime_type = tree_magic_mini::from_u8(container);
log!("Mimetype: ", mime_type);
log!("Blob conversion started.");
let start = now();
let blob_chunks = Array::new_with_length(container.chunks(65536).len().try_into().unwrap());
@ -74,13 +77,17 @@ pub fn decrypt(
array.copy_from(chunk);
blob_chunks.set(i.try_into().unwrap(), array.dyn_into().unwrap());
}
let blob =
Arc::new(Blob::new_with_u8_array_sequence(blob_chunks.dyn_ref().unwrap()).unwrap());
let mut blob_props = BlobPropertyBag::new();
blob_props.type_(mime_type);
let blob = Arc::new(
Blob::new_with_u8_array_sequence_and_options(
blob_chunks.dyn_ref().unwrap(),
&blob_props,
)
.unwrap(),
);
log!(format!("Blob conversion completed in {}ms", now() - start));
let mime_type = tree_magic_mini::from_u8(container);
log!("Mimetype: ", mime_type);
if mime_type.starts_with("image/") || mime_type == "application/x-riff" {
Ok(DecryptedData::Image(blob, container.len()))
} else if mime_type.starts_with("audio/") {
@ -93,15 +100,18 @@ pub fn decrypt(
if let Ok(mut zip) = zip::ZipArchive::new(cursor) {
for i in 0..zip.len() {
match zip.by_index(i) {
Ok(file) => {
entries.push(ArchiveMeta{name: file.name().to_string(), file_size: file.size() as usize})
},
Err(err) => {
match err {
zip::result::ZipError::UnsupportedArchive(s) => { log!("Unsupported: ", s.to_string()); }
_ => { log!(format!("Error: {}", err)); }
Ok(file) => entries.push(ArchiveMeta {
name: file.name().to_string(),
file_size: file.size() as usize,
}),
Err(err) => match err {
zip::result::ZipError::UnsupportedArchive(s) => {
log!("Unsupported: ", s.to_string());
}
}
_ => {
log!(format!("Error: {}", err));
}
},
}
}
}

View File

@ -40,8 +40,12 @@ main {
font-family: 'Mplus Code', sans-serif;
}
.hljs-ln td.hljs-ln-numbers {
.align-right {
text-align: right;
}
.hljs-ln td.hljs-ln-numbers {
@extend .align-right;
padding-right: $padding;
}
@ -73,4 +77,15 @@ img, audio, video {
.primary {
@extend .hljs;
}
.archive {
&-table {
width: 100%;
}
&-file-size {
@extend .align-right;
padding-left: $padding;
}
}

View File

@ -62,8 +62,9 @@ function createStringPasteUi(data) {
let preEle = document.createElement("pre");
preEle.classList.add("paste");
let headerEle = document.createElement("header");
let headerEle = document.createElement("p");
headerEle.classList.add("unselectable");
headerEle.classList.add("centered");
headerEle.textContent = data.expiration;
preEle.appendChild(headerEle);
@ -141,41 +142,58 @@ function createArchivePasteUi({ expiration, data, entries }) {
bodyEle.textContent = '';
let mainEle = document.createElement("main");
mainEle.classList.add("hljs");
mainEle.classList.add("centered");
mainEle.classList.add("fullscreen");
const downloadLink = URL.createObjectURL(data);
let sectionEle = document.createElement("section");
sectionEle.classList.add("paste");
let expirationEle = document.createElement("p");
expirationEle.textContent = expiration;
mainEle.appendChild(expirationEle);
let mediaEle = document.createElement("table");
mediaEle.style.width = "50%";
const tr = mediaEle.insertRow();
const tdName = tr.insertCell();
tdName.appendChild(document.createTextNode("Name"));
const tdSize = tr.insertCell();
tdSize.appendChild(document.createTextNode("File Size"));
for (const entry of entries) {
const tr = mediaEle.insertRow();
const tdName = tr.insertCell();
tdName.appendChild(document.createTextNode(entry.name));
const tdSize = tr.insertCell();
tdSize.appendChild(document.createTextNode(entry.file_size));
}
mainEle.appendChild(mediaEle);
expirationEle.classList.add("centered");
sectionEle.appendChild(expirationEle);
let downloadEle = document.createElement("a");
downloadEle.href = downloadLink;
downloadEle.href = URL.createObjectURL(data);
downloadEle.download = window.location.pathname;
downloadEle.classList.add("hljs-meta");
mainEle.appendChild(downloadEle);
bodyEle.appendChild(mainEle);
downloadEle.textContent = "Download";
downloadEle.classList.add("hljs-meta");
downloadEle.classList.add("centered");
sectionEle.appendChild(downloadEle);
sectionEle.appendChild(document.createElement("hr"));
let mediaEle = document.createElement("table");
mediaEle.classList.add("archive-table");
const tr = mediaEle.insertRow();
tr.classList.add("hljs-title");
const tdName = tr.insertCell();
tdName.textContent = "Name";
const tdSize = tr.insertCell();
tdSize.classList.add("align-right");
tdSize.textContent = "File Size";
// Because it's a stable sort, we can first sort by name (to get all folder
// items grouped together) and then sort by if there's a / or not.
entries.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
entries.sort((a, b) => {
return b.name.includes("/") - a.name.includes("/");
});
for (const { name, file_size } of entries) {
const tr = mediaEle.insertRow();
const tdName = tr.insertCell();
tdName.textContent = name;
const tdSize = tr.insertCell();
tdSize.textContent = file_size;
tdSize.classList.add("align-right");
tdSize.classList.add("hljs-number");
}
sectionEle.appendChild(mediaEle);
mainEle.appendChild(sectionEle);
bodyEle.appendChild(mainEle);
}
function createMultiMediaPasteUi(tag, expiration, data, on_create?) {
@ -198,6 +216,7 @@ function createMultiMediaPasteUi(tag, expiration, data, on_create?) {
mediaEle.controls = true;
mainEle.appendChild(mediaEle);
let downloadEle = document.createElement("a");
downloadEle.href = downloadLink;
downloadEle.download = window.location.pathname;