|
|
@ -17,6 +17,9 @@ use crate::{ |
|
|
|
|
|
|
|
const SEND_INACCESSIBLE_MSG: &str = "Send does not exist or is no longer available"; |
|
|
|
|
|
|
|
// The max file size allowed by Bitwarden clients and add an extra 5% to avoid issues
|
|
|
|
const SIZE_525_MB: u64 = 550_502_400; |
|
|
|
|
|
|
|
pub fn routes() -> Vec<rocket::Route> { |
|
|
|
routes![ |
|
|
|
get_sends, |
|
|
@ -28,7 +31,9 @@ pub fn routes() -> Vec<rocket::Route> { |
|
|
|
put_send, |
|
|
|
delete_send, |
|
|
|
put_remove_password, |
|
|
|
download_send |
|
|
|
download_send, |
|
|
|
post_send_file_v2, |
|
|
|
post_send_file_v2_data |
|
|
|
] |
|
|
|
} |
|
|
|
|
|
|
@ -58,6 +63,7 @@ struct SendData { |
|
|
|
Notes: Option<String>, |
|
|
|
Text: Option<Value>, |
|
|
|
File: Option<Value>, |
|
|
|
FileLength: Option<NumberOrString>, |
|
|
|
} |
|
|
|
|
|
|
|
/// Enforces the `Disable Send` policy. A non-owner/admin user belonging to
|
|
|
@ -185,6 +191,14 @@ struct UploadData<'f> { |
|
|
|
data: TempFile<'f>, |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(FromForm)] |
|
|
|
struct UploadDataV2<'f> { |
|
|
|
data: TempFile<'f>, |
|
|
|
} |
|
|
|
|
|
|
|
// @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads (v2).
|
|
|
|
// This method still exists to support older clients, probably need to remove it sometime.
|
|
|
|
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L164-L167
|
|
|
|
#[post("/sends/file", format = "multipart/form-data", data = "<data>")] |
|
|
|
async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult { |
|
|
|
enforce_disable_send_policy(&headers, &conn).await?; |
|
|
@ -197,9 +211,6 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo |
|
|
|
|
|
|
|
enforce_disable_hide_email_policy(&model, &headers, &conn).await?; |
|
|
|
|
|
|
|
// Get the file length and add an extra 5% to avoid issues
|
|
|
|
const SIZE_525_MB: u64 = 550_502_400; |
|
|
|
|
|
|
|
let size_limit = match CONFIG.user_attachment_limit() { |
|
|
|
Some(0) => err!("File uploads are disabled"), |
|
|
|
Some(limit_kb) => { |
|
|
@ -217,10 +228,12 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo |
|
|
|
err!("Send content is not a file"); |
|
|
|
} |
|
|
|
|
|
|
|
// There seems to be a bug somewhere regarding uploading attachments using the Android Client (Maybe iOS too?)
|
|
|
|
// See: https://github.com/dani-garcia/vaultwarden/issues/2644
|
|
|
|
// Since all other clients seem to match TempFile::File and not TempFile::Buffered lets catch this and return an error for now.
|
|
|
|
// We need to figure out how to solve this, but for now it's better to not accept these attachments since they will be broken.
|
|
|
|
// There is a bug regarding uploading attachments/sends using the Mobile clients
|
|
|
|
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
|
|
|
|
// This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
|
|
|
|
// On the vaultwarden side this is temporarily fixed by using a custom multer library
|
|
|
|
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
|
|
|
|
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
|
|
|
|
if let TempFile::Buffered { |
|
|
|
content: _, |
|
|
|
} = &data |
|
|
@ -252,11 +265,110 @@ async fn post_send_file(data: Form<UploadData<'_>>, headers: Headers, conn: DbCo |
|
|
|
|
|
|
|
// Save the changes in the database
|
|
|
|
send.save(&conn).await?; |
|
|
|
nt.send_send_update(UpdateType::SyncSendUpdate, &send, &send.update_users_revision(&conn).await).await; |
|
|
|
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await; |
|
|
|
|
|
|
|
Ok(Json(send.to_json())) |
|
|
|
} |
|
|
|
|
|
|
|
// Upstream: https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L190
|
|
|
|
#[post("/sends/file/v2", data = "<data>")] |
|
|
|
async fn post_send_file_v2(data: JsonUpcase<SendData>, headers: Headers, conn: DbConn) -> JsonResult { |
|
|
|
enforce_disable_send_policy(&headers, &conn).await?; |
|
|
|
|
|
|
|
let data = data.into_inner().data; |
|
|
|
|
|
|
|
if data.Type != SendType::File as i32 { |
|
|
|
err!("Send content is not a file"); |
|
|
|
} |
|
|
|
|
|
|
|
enforce_disable_hide_email_policy(&data, &headers, &conn).await?; |
|
|
|
|
|
|
|
let file_length = match &data.FileLength { |
|
|
|
Some(m) => Some(m.into_i32()?), |
|
|
|
_ => None, |
|
|
|
}; |
|
|
|
|
|
|
|
let size_limit = match CONFIG.user_attachment_limit() { |
|
|
|
Some(0) => err!("File uploads are disabled"), |
|
|
|
Some(limit_kb) => { |
|
|
|
let left = (limit_kb * 1024) - Attachment::size_by_user(&headers.user.uuid, &conn).await; |
|
|
|
if left <= 0 { |
|
|
|
err!("Attachment storage limit reached! Delete some attachments to free up space") |
|
|
|
} |
|
|
|
std::cmp::Ord::max(left as u64, SIZE_525_MB) |
|
|
|
} |
|
|
|
None => SIZE_525_MB, |
|
|
|
}; |
|
|
|
|
|
|
|
if file_length.is_some() && file_length.unwrap() as u64 > size_limit { |
|
|
|
err!("Attachment storage limit exceeded with this file"); |
|
|
|
} |
|
|
|
|
|
|
|
let mut send = create_send(data, headers.user.uuid)?; |
|
|
|
|
|
|
|
let file_id = crate::crypto::generate_send_id(); |
|
|
|
|
|
|
|
let mut data_value: Value = serde_json::from_str(&send.data)?; |
|
|
|
if let Some(o) = data_value.as_object_mut() { |
|
|
|
o.insert(String::from("Id"), Value::String(file_id.clone())); |
|
|
|
o.insert(String::from("Size"), Value::Number(file_length.unwrap().into())); |
|
|
|
o.insert(String::from("SizeName"), Value::String(crate::util::get_display_size(file_length.unwrap()))); |
|
|
|
} |
|
|
|
send.data = serde_json::to_string(&data_value)?; |
|
|
|
send.save(&conn).await?; |
|
|
|
|
|
|
|
Ok(Json(json!({ |
|
|
|
"fileUploadType": 0, // 0 == Direct | 1 == Azure
|
|
|
|
"object": "send-fileUpload", |
|
|
|
"url": format!("/sends/{}/file/{}", send.uuid, file_id), |
|
|
|
"sendResponse": send.to_json() |
|
|
|
}))) |
|
|
|
} |
|
|
|
|
|
|
|
// https://github.com/bitwarden/server/blob/d0c793c95181dfb1b447eb450f85ba0bfd7ef643/src/Api/Controllers/SendsController.cs#L243
|
|
|
|
#[post("/sends/<send_uuid>/file/<file_id>", format = "multipart/form-data", data = "<data>")] |
|
|
|
async fn post_send_file_v2_data( |
|
|
|
send_uuid: String, |
|
|
|
file_id: String, |
|
|
|
data: Form<UploadDataV2<'_>>, |
|
|
|
headers: Headers, |
|
|
|
conn: DbConn, |
|
|
|
nt: Notify<'_>, |
|
|
|
) -> EmptyResult { |
|
|
|
enforce_disable_send_policy(&headers, &conn).await?; |
|
|
|
|
|
|
|
let mut data = data.into_inner(); |
|
|
|
|
|
|
|
// There is a bug regarding uploading attachments/sends using the Mobile clients
|
|
|
|
// See: https://github.com/dani-garcia/vaultwarden/issues/2644 && https://github.com/bitwarden/mobile/issues/2018
|
|
|
|
// This has been fixed via a PR: https://github.com/bitwarden/mobile/pull/2031, but hasn't landed in a new release yet.
|
|
|
|
// On the vaultwarden side this is temporarily fixed by using a custom multer library
|
|
|
|
// See: https://github.com/dani-garcia/vaultwarden/pull/2675
|
|
|
|
// In any case we will match TempFile::File and not TempFile::Buffered, since Buffered will alter the contents.
|
|
|
|
if let TempFile::Buffered { |
|
|
|
content: _, |
|
|
|
} = &data.data |
|
|
|
{ |
|
|
|
err!("Error reading attachment data. Please try an other client."); |
|
|
|
} |
|
|
|
|
|
|
|
if let Some(send) = Send::find_by_uuid(&send_uuid, &conn).await { |
|
|
|
let folder_path = tokio::fs::canonicalize(&CONFIG.sends_folder()).await?.join(&send_uuid); |
|
|
|
let file_path = folder_path.join(&file_id); |
|
|
|
tokio::fs::create_dir_all(&folder_path).await?; |
|
|
|
|
|
|
|
if let Err(_err) = data.data.persist_to(&file_path).await { |
|
|
|
data.data.move_copy_to(file_path).await? |
|
|
|
} |
|
|
|
|
|
|
|
nt.send_send_update(UpdateType::SyncSendCreate, &send, &send.update_users_revision(&conn).await).await; |
|
|
|
} else { |
|
|
|
err!("Send not found. Unable to save the file."); |
|
|
|
} |
|
|
|
|
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Deserialize)] |
|
|
|
#[allow(non_snake_case)] |
|
|
|
pub struct SendAccessData { |
|
|
|