diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 5256c28a..1870d12b 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -947,10 +947,12 @@ async fn save_attachment( let mut data = data.into_inner(); - // 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.data diff --git a/src/api/core/sends.rs b/src/api/core/sends.rs index 3d150b31..e9d374f3 100644 --- a/src/api/core/sends.rs +++ b/src/api/core/sends.rs @@ -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 { routes![ get_sends, @@ -28,7 +31,9 @@ pub fn routes() -> Vec { 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, Text: Option, File: Option, + FileLength: Option, } /// 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 = "")] async fn post_send_file(data: Form>, 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>, 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>, 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>, 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 = "")] +async fn post_send_file_v2(data: JsonUpcase, 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//file/", format = "multipart/form-data", data = "")] +async fn post_send_file_v2_data( + send_uuid: String, + file_id: String, + data: Form>, + 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 {