just rewrite the fucking upload method, its too buggy to fix

This commit is contained in:
blek 2023-10-23 01:24:38 +10:00
parent f1ee297b8d
commit 8d3b81244d
Signed by: blek
GPG Key ID: 14546221E3595D0C
1 changed files with 176 additions and 123 deletions

View File

@ -6,14 +6,14 @@
use std::collections::HashMap;
use askama::Template;
use warp::{Filter, reply::Reply, reject::Rejection, filters::multipart::FormData, http::StatusCode};
use warp::{Filter, reply::{Reply, json, with_status, html}, reject::Rejection, filters::multipart::FormData, http::StatusCode};
use futures_util::TryStreamExt;
use bytes::BufMut;
use serde::Serialize;
use crate::files::{File, lookup::LookupKind, DeleteMode};
use super::{state::SharedState, pages::{BadActionReq, UploadSuccessPage, self}, rejection::HttpReject};
use super::{state::SharedState, pages::{BadActionReq, UploadSuccessPage, self, ErrorPage}, rejection::HttpReject};
#[derive(Debug, Serialize, Clone)]
struct FormElement {
@ -24,6 +24,125 @@ impl FormElement {
pub fn as_str_or_reject(self: &Self) -> Result<String, Rejection> {
Ok(String::from_utf8(self.data.clone()).map_err(|err| warp::reject::custom(HttpReject::FromUtf8Error(err)))?)
}
pub fn as_atr_or_none(self: &Self) -> Option<String> {
if let Ok(res) = String::from_utf8(self.data.clone()) {
Some(res)
} else {
None
}
}
pub fn is_checked(self: &Self) -> bool {
if self.data.len() != 2 {
return false
}
let data = self.data.clone();
let on = "on".bytes().collect::<Vec<u8>>();
data == on
}
pub async fn from_formdata(form: FormData) -> Result<HashMap<String, FormElement>, warp::Error> {
form.and_then(|mut field| async move {
let mut bytes: Vec<u8> = vec![];
while let Some(byte) = field.data().await {
bytes.put(byte.unwrap())
}
Ok((field.name().into(), FormElement { data: bytes, mime: field.content_type().unwrap_or("text/plain").to_string() }))
}).try_collect()
.await
}
}
struct UploadFormData {
filename: Option<String>,
password: Option<String>,
lookup_kind: LookupKind,
delmode: DeleteMode,
file: Vec<u8>,
mime: String,
tos_consent: bool
}
impl Default for UploadFormData {
fn default() -> Self {
UploadFormData {
filename: None,
password: None,
lookup_kind: LookupKind::ByHash,
delmode: DeleteMode::Time,
file: vec![],
mime: "application/x-octet-stream".into(),
tos_consent: false
}
}
}
impl UploadFormData {
pub fn from_formdata(data: HashMap<String, FormElement>) -> Option<UploadFormData> {
let mut out = Self::default();
// Add a name
match data.get("named") {
Some(val) => {
if val.is_checked() {
let name = data.get("filename")?;
out.filename = Some(name.as_atr_or_none()?);
out.lookup_kind = LookupKind::ByHash
}
},
None => ()
}
// Add a password
match data.get("passworded") {
Some(val) => {
if val.is_checked() {
let pass = data.get("password")?;
out.password = Some(pass.as_atr_or_none()?);
out.lookup_kind = LookupKind::ByName
}
},
None => ()
}
// Delete mode
match data.get("delmode") {
Some(val) => {
let val = val.data.clone();
let is_30 = val == "30".bytes().collect::<Vec<u8>>();
if is_30 {
out.delmode = DeleteMode::Time
} else {
out.delmode = DeleteMode::TimeOrDownload
}
},
None => {
return None
}
}
let file = data.get("file")?;
out.file = file.data.clone();
out.mime = file.mime.clone();
out.tos_consent = match data.get("tos_consent") {
Some(v) => v.is_checked(),
None => false
};
Some(out)
}
}
fn bad_req_html(data: String) -> Box<dyn Reply> {
Box::new(
with_status(
html(data),
StatusCode::BAD_REQUEST
)
)
}
pub async fn upload(form: FormData, state: SharedState) -> Result<Box<dyn Reply>, Rejection> {
@ -34,141 +153,75 @@ pub async fn upload(form: FormData, state: SharedState) -> Result<Box<dyn Reply>
)
}
let params: HashMap<String, FormElement> = form.and_then(|mut field| async move {
let mut bytes: Vec<u8> = vec![];
while let Some(byte) = field.data().await {
bytes.put(byte.unwrap())
}
Ok((field.name().into(), FormElement { data: bytes, mime: field.content_type().unwrap_or("text/plain").to_string() }))
}).try_collect()
.await
.map_err(|err| warp::reject::custom(HttpReject::WarpError(err.into())))?;
let params: HashMap<String, FormElement> = FormElement::from_formdata(form).await.map_err(|x| HttpReject::WarpError(x))?;
let formdata = UploadFormData::from_formdata(params.clone());
if let Some(formdata) = formdata {
// check that required fields exist
let mut all_exist = true;
let _ = vec!["delmode", "file", "filename", "password"].iter().for_each(|x| {
let field = x.to_string();
if ! params.contains_key(&field) {
all_exist = false;
}
});
if ! formdata.tos_consent {
let error = ErrorPage {
env: state.env,
conf: state.config,
error_text: "You must agree to the ToS".into(),
link: None,
link_text: None
};
if ! all_exist {
return Ok(Box::new(
warp::reply::with_status(
warp::reply::html(
BadActionReq {
env: state.env.clone(),
conf: state.config.clone()
}
.render()
.map_err(|err| warp::reject::custom(HttpReject::AskamaError(err.into())))?
),
StatusCode::BAD_REQUEST
return Ok(
bad_req_html(
error.render()
.map_err(|x| HttpReject::AskamaError(x))?
)
)
))
}
let check_off = FormElement { data: "off".as_bytes().to_vec(), mime: "text/plain".into() };
let data = params.get("file").unwrap();
let delmode = params.get("delmode").unwrap();
let named = params.get("named");
let filename = params.get("filename").unwrap();
let tos_check = match params.get("tos_consent") {
Some(v) => (*v).clone(),
None => check_off.clone()
};
let protected = params.get("passworded").unwrap_or(&check_off.clone()).as_str_or_reject()?;
let protected = protected == "on";
let password: Option<String> = {
if ! state.config.files.allow_pass_protection {
None
} else {
let pass = params.get("password");
if protected && pass.is_some() {
Some(pass.unwrap().as_str_or_reject()?)
} else {
None
}
}
};
let mut is_named = named.is_none();
let tos_check = tos_check.as_str_or_reject()?;
if tos_check != "on" {
let file = File::create(
formdata.file,
formdata.mime,
formdata.filename.clone(),
state.env.clone(),
formdata.delmode,
formdata.password
).await
.map_err(|x| HttpReject::StringError(x.to_string()))
?;
state.file_mgr.save(&file, formdata.lookup_kind)
.map_err(|x| HttpReject::StringError(x.to_string()))?;
let page = UploadSuccessPage {
env: state.env,
conf: state.config,
link: match formdata.filename {
Some(v) => v,
None => file.hash()
}
};
return Ok(
Box::new(
warp::reply::html(
pages::ErrorPage {
env: state.env,
conf: state.config.clone(),
error_text: "You must consent to the terms and conditions!".into(),
link: Some("/".into()),
link_text: Some("Go back".into())
}
.render()
.map_err(
|err|
warp::reject::custom(HttpReject::AskamaError(err))
)?
html(
page.render()
.map_err(|x| HttpReject::AskamaError(x))?
)
)
)
}
let delmode = delmode.as_str_or_reject()?;
if delmode != "30" && delmode != "dl" {
return Err(warp::reject::custom(HttpReject::StringError("delmode is neither 30 or dl!".into())));
}
if named.is_some() {
if state.config.files.allow_custom_names {
is_named = false;
} else {
is_named = named.unwrap().as_str_or_reject()? == "on";
}
}
let file = File::create(
data.data.clone(),
data.mime.clone(),
{
if is_named {
Some(filename.as_str_or_reject()?)
} else {
None
}
},
state.env.clone(),
{
if delmode == "30" {
DeleteMode::Time
} else {
DeleteMode::TimeOrDownload
}
},
password
).await
.map_err(|err| warp::reject::custom(HttpReject::StringError(err.to_string())))?;
state.file_mgr.save(&file, {
if is_named {
LookupKind::ByName
} else {
LookupKind::ByHash
}
}).map_err(|err| warp::reject::custom(HttpReject::StringError(err.to_string())))?;
let uploaded = UploadSuccessPage {
env: state.env.clone(),
conf: state.config.clone(),
link: file.leftmost_link()
let error = ErrorPage {
env: state.env,
conf: state.config,
error_text: "Form is malformed".into(),
link: None,
link_text: None
};
Ok(Box::new(warp::reply::html(uploaded.render().unwrap())))
return Ok(
bad_req_html(
error.render()
.map_err(|x| HttpReject::AskamaError(x))?
)
)
}