@ -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 ::SyncSend Upd ate, & send , & send . update_users_revision ( & conn ) . await ) . await ;
nt . send_send_update ( UpdateType ::SyncSend Cre ate, & 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 {