refactor config variables, add upload password
This commit is contained in:
parent
bfe7fcde99
commit
eb53030043
13 changed files with 456 additions and 182 deletions
|
@ -2,6 +2,7 @@ use std::{collections::HashSet, fs::File, io::Write};
|
|||
|
||||
use actix::{fut::future::ActorFutureExt, Actor, ActorContext, AsyncContext, StreamHandler};
|
||||
use actix_http::ws::{CloseReason, Item};
|
||||
use actix_web::web;
|
||||
use actix_web_actors::ws::{self, CloseCode};
|
||||
use bytes::Bytes;
|
||||
use log::{debug, error, info, trace};
|
||||
|
@ -9,7 +10,11 @@ use serde::{Deserialize, Serialize};
|
|||
use time::OffsetDateTime;
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
|
||||
use crate::store::{self, storage_dir, StoredFile};
|
||||
use crate::{
|
||||
log_auth_failure,
|
||||
store::{self, FileAddError, StoredFile},
|
||||
AppState,
|
||||
};
|
||||
|
||||
const MAX_FILES: usize = 256;
|
||||
const FILENAME_DATE_FORMAT: &[time::format_description::FormatItem] =
|
||||
|
@ -29,10 +34,14 @@ enum Error {
|
|||
NoFiles,
|
||||
#[error("Number of files submitted by client exceeded the maximum limit")]
|
||||
TooManyFiles,
|
||||
#[error("Requested lifetime was too long")]
|
||||
TooLong,
|
||||
#[error("Requested lifetime was too long, can be at most {0} days")]
|
||||
TooLong(u16),
|
||||
#[error("Upload size was too large, can be at most {0} bytes")]
|
||||
TooBig(u64),
|
||||
#[error("File storage is full")]
|
||||
Full,
|
||||
#[error("Incorrect password")]
|
||||
IncorrectPassword,
|
||||
#[error("Websocket was closed by client before completing transfer")]
|
||||
ClosedEarly(Option<CloseReason>),
|
||||
#[error("Client sent more data than they were supposed to")]
|
||||
|
@ -50,8 +59,10 @@ impl Error {
|
|||
Self::DuplicateFilename
|
||||
| Self::NoFiles
|
||||
| Self::TooManyFiles
|
||||
| Self::TooLong
|
||||
| Self::TooBig(_) => CloseCode::Policy,
|
||||
| Self::TooLong(_)
|
||||
| Self::TooBig(_)
|
||||
| Self::Full
|
||||
| Self::IncorrectPassword => CloseCode::Policy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,17 +70,19 @@ impl Error {
|
|||
pub struct Uploader {
|
||||
writer: Option<Box<dyn Write>>,
|
||||
storage_filename: String,
|
||||
app_data: super::AppData,
|
||||
app_state: web::Data<AppState>,
|
||||
bytes_remaining: u64,
|
||||
ip_addr: String,
|
||||
}
|
||||
|
||||
impl Uploader {
|
||||
pub(crate) fn new(app_data: super::AppData) -> Self {
|
||||
pub(crate) fn new(app_state: web::Data<AppState>, ip_addr: String) -> Self {
|
||||
Self {
|
||||
writer: None,
|
||||
storage_filename: store::gen_storage_code(),
|
||||
app_data,
|
||||
storage_filename: store::gen_storage_code(app_state.config.mnemonic_codes),
|
||||
app_state,
|
||||
bytes_remaining: 0,
|
||||
ip_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +130,8 @@ impl RawUploadedFile {
|
|||
#[derive(Deserialize)]
|
||||
struct UploadManifest {
|
||||
files: Vec<RawUploadedFile>,
|
||||
lifetime: u32,
|
||||
lifetime: u16,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -125,7 +139,8 @@ struct UploadManifest {
|
|||
enum ServerMessage {
|
||||
Ready { code: String },
|
||||
TooBig { max_size: u64 },
|
||||
TooLong { max_days: u32 },
|
||||
TooLong { max_days: u16 },
|
||||
IncorrectPassword,
|
||||
Error { details: String },
|
||||
}
|
||||
|
||||
|
@ -135,9 +150,10 @@ impl From<&Error> for ServerMessage {
|
|||
Error::TooBig(max_size) => ServerMessage::TooBig {
|
||||
max_size: *max_size,
|
||||
},
|
||||
Error::TooLong => ServerMessage::TooLong {
|
||||
max_days: store::max_lifetime(),
|
||||
Error::TooLong(max_days) => ServerMessage::TooLong {
|
||||
max_days: *max_days,
|
||||
},
|
||||
Error::IncorrectPassword => ServerMessage::IncorrectPassword,
|
||||
_ => ServerMessage::Error {
|
||||
details: e.to_string(),
|
||||
},
|
||||
|
@ -189,6 +205,9 @@ fn ack(ctx: &mut Context) {
|
|||
impl Uploader {
|
||||
fn notify_error_and_cleanup(&mut self, e: Error, ctx: &mut Context) {
|
||||
error!("{}", e);
|
||||
if let Error::IncorrectPassword = e {
|
||||
log_auth_failure(&self.ip_addr);
|
||||
}
|
||||
ctx.text(serde_json::to_string(&ServerMessage::from(&e)).unwrap());
|
||||
ctx.close(Some(ws::CloseReason {
|
||||
code: e.close_code(),
|
||||
|
@ -207,9 +226,13 @@ impl Uploader {
|
|||
let UploadManifest {
|
||||
files: raw_files,
|
||||
lifetime,
|
||||
password,
|
||||
} = serde_json::from_slice(text.as_bytes())?;
|
||||
if lifetime > store::max_lifetime() {
|
||||
return Err(Error::TooLong);
|
||||
if std::env::var("TRANSBEAM_UPLOAD_PASSWORD") != Ok(password) {
|
||||
return Err(Error::IncorrectPassword);
|
||||
}
|
||||
if lifetime > self.app_state.config.max_lifetime {
|
||||
return Err(Error::TooLong(self.app_state.config.max_lifetime));
|
||||
}
|
||||
info!("Received file list: {} files", raw_files.len());
|
||||
debug!("{:?}", raw_files);
|
||||
|
@ -234,7 +257,14 @@ impl Uploader {
|
|||
self.bytes_remaining += file.size;
|
||||
files.push(file);
|
||||
}
|
||||
let storage_path = storage_dir().join(self.storage_filename.clone());
|
||||
if self.bytes_remaining > self.app_state.config.max_upload_size {
|
||||
return Err(Error::TooBig(self.app_state.config.max_upload_size));
|
||||
}
|
||||
let storage_path = self
|
||||
.app_state
|
||||
.config
|
||||
.storage_dir
|
||||
.join(self.storage_filename.clone());
|
||||
info!("storing to: {:?}", storage_path);
|
||||
let writer = File::options()
|
||||
.write(true)
|
||||
|
@ -264,25 +294,32 @@ impl Uploader {
|
|||
modtime,
|
||||
expiry: OffsetDateTime::now_utc() + lifetime * time::Duration::DAY,
|
||||
};
|
||||
let data = self.app_data.clone();
|
||||
let state = self.app_state.clone();
|
||||
let storage_filename = self.storage_filename.clone();
|
||||
ctx.spawn(
|
||||
actix::fut::wrap_future(async move {
|
||||
debug!("Spawned future to add entry {} to state", storage_filename);
|
||||
data.write()
|
||||
state
|
||||
.file_store
|
||||
.write()
|
||||
.await
|
||||
.add_file(storage_filename, stored_file)
|
||||
.await
|
||||
})
|
||||
.map(|res, u: &mut Self, ctx: &mut Context| match res {
|
||||
Ok(Ok(())) => ctx.text(
|
||||
Ok(()) => ctx.text(
|
||||
serde_json::to_string(&ServerMessage::Ready {
|
||||
code: u.storage_filename.clone(),
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
Ok(Err(size)) => u.notify_error_and_cleanup(Error::TooBig(size), ctx),
|
||||
Err(e) => u.notify_error_and_cleanup(Error::from(e), ctx),
|
||||
Err(FileAddError::TooBig(size)) => {
|
||||
u.notify_error_and_cleanup(Error::TooBig(size), ctx)
|
||||
}
|
||||
Err(FileAddError::Full) => u.notify_error_and_cleanup(Error::Full, ctx),
|
||||
Err(FileAddError::FileSystem(e)) => {
|
||||
u.notify_error_and_cleanup(Error::from(e), ctx)
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -332,17 +369,20 @@ impl Uploader {
|
|||
"Cleaning up after failed upload of {}",
|
||||
self.storage_filename
|
||||
);
|
||||
let data = self.app_data.clone();
|
||||
let state = self.app_state.clone();
|
||||
let filename = self.storage_filename.clone();
|
||||
ctx.wait(
|
||||
actix::fut::wrap_future(async move {
|
||||
debug!("Spawned future to remove entry {} from state", filename);
|
||||
data.write().await.remove_file(&filename).await.unwrap();
|
||||
state
|
||||
.file_store
|
||||
.write()
|
||||
.await
|
||||
.remove_file(&filename)
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.map(stop_and_flush),
|
||||
);
|
||||
if let Err(e) = std::fs::remove_file(storage_dir().join(&self.storage_filename)) {
|
||||
error!("Failed to remove file {}: {}", self.storage_filename, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue