just rewrite the fucking upload method, its too buggy to fix
This commit is contained in:
parent
f1ee297b8d
commit
8d3b81244d
|
@ -6,14 +6,14 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use askama::Template;
|
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 futures_util::TryStreamExt;
|
||||||
use bytes::BufMut;
|
use bytes::BufMut;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::files::{File, lookup::LookupKind, DeleteMode};
|
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)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
struct FormElement {
|
struct FormElement {
|
||||||
|
@ -24,6 +24,125 @@ impl FormElement {
|
||||||
pub fn as_str_or_reject(self: &Self) -> Result<String, Rejection> {
|
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)))?)
|
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> {
|
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 params: HashMap<String, FormElement> = FormElement::from_formdata(form).await.map_err(|x| HttpReject::WarpError(x))?;
|
||||||
let mut bytes: Vec<u8> = vec![];
|
let formdata = UploadFormData::from_formdata(params.clone());
|
||||||
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() }))
|
if let Some(formdata) = formdata {
|
||||||
}).try_collect()
|
|
||||||
.await
|
|
||||||
.map_err(|err| warp::reject::custom(HttpReject::WarpError(err.into())))?;
|
|
||||||
|
|
||||||
// check that required fields exist
|
if ! formdata.tos_consent {
|
||||||
let mut all_exist = true;
|
let error = ErrorPage {
|
||||||
let _ = vec!["delmode", "file", "filename", "password"].iter().for_each(|x| {
|
env: state.env,
|
||||||
let field = x.to_string();
|
conf: state.config,
|
||||||
if ! params.contains_key(&field) {
|
error_text: "You must agree to the ToS".into(),
|
||||||
all_exist = false;
|
link: None,
|
||||||
}
|
link_text: None
|
||||||
});
|
};
|
||||||
|
|
||||||
if ! all_exist {
|
return Ok(
|
||||||
return Ok(Box::new(
|
bad_req_html(
|
||||||
warp::reply::with_status(
|
error.render()
|
||||||
warp::reply::html(
|
.map_err(|x| HttpReject::AskamaError(x))?
|
||||||
BadActionReq {
|
)
|
||||||
env: state.env.clone(),
|
|
||||||
conf: state.config.clone()
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.map_err(|err| warp::reject::custom(HttpReject::AskamaError(err.into())))?
|
|
||||||
),
|
|
||||||
StatusCode::BAD_REQUEST
|
|
||||||
)
|
)
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 file = File::create(
|
||||||
let tos_check = tos_check.as_str_or_reject()?;
|
formdata.file,
|
||||||
if tos_check != "on" {
|
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(
|
return Ok(
|
||||||
Box::new(
|
Box::new(
|
||||||
warp::reply::html(
|
html(
|
||||||
pages::ErrorPage {
|
page.render()
|
||||||
env: state.env,
|
.map_err(|x| HttpReject::AskamaError(x))?
|
||||||
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))
|
|
||||||
)?
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let delmode = delmode.as_str_or_reject()?;
|
let error = ErrorPage {
|
||||||
if delmode != "30" && delmode != "dl" {
|
env: state.env,
|
||||||
return Err(warp::reject::custom(HttpReject::StringError("delmode is neither 30 or dl!".into())));
|
conf: state.config,
|
||||||
}
|
error_text: "Form is malformed".into(),
|
||||||
|
link: None,
|
||||||
if named.is_some() {
|
link_text: None
|
||||||
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()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(warp::reply::html(uploaded.render().unwrap())))
|
return Ok(
|
||||||
|
bad_req_html(
|
||||||
|
error.render()
|
||||||
|
.map_err(|x| HttpReject::AskamaError(x))?
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue