| 
					
				 | 
			
			
				@@ -1,4 +1,4 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-use std::collections::HashSet; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+use std::collections::{HashSet, HashMap}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 use std::path::Path; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 use rocket::http::ContentType; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -162,6 +162,18 @@ pub struct CipherData { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Favorite: Option<bool>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     PasswordHistory: Option<Value>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // These are used during key rotation 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    #[serde(rename = "Attachments")] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    _Attachments: Option<Value>, // Unused, contains map of {id: filename} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Attachments2: Option<HashMap<String, Attachments2Data>> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#[derive(Deserialize, Debug)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#[allow(non_snake_case)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+pub struct Attachments2Data { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    FileName: String, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    Key: String, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 #[post("/ciphers/admin", data = "<data>")] 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -221,6 +233,28 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: & 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    // Modify attachments name and keys when rotating 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if let Some(attachments) = data.Attachments2 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for (id, attachment) in attachments { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            let mut saved_att = match Attachment::find_by_id(&id, &conn) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Some(att) => att, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                None => err!("Attachment doesn't exist") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if saved_att.cipher_uuid != cipher.uuid { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                err!("Attachment is not owned by the cipher") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            saved_att.key = Some(attachment.Key); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            saved_att.file_name = attachment.FileName; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            match saved_att.save(&conn) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Ok(()) => (), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Err(_) => err!("Failed to save attachment") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     let type_data_opt = match data.Type { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         1 => data.Login, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         2 => data.SecureNote, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -252,7 +286,7 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: & 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     match cipher.save(&conn) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         Ok(()) => (), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        Err(_) => println!("Error: Failed to save cipher") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Err(_) => err!("Failed to save cipher") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ws.send_cipher_update(ut, &cipher, &cipher.update_users_revision(&conn)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -299,7 +333,6 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // Read the relations between folders and ciphers 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    use std::collections::HashMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     let mut relations_map = HashMap::new(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     for relation in data.FolderRelationships { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -542,37 +575,52 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     let base_path = Path::new(&CONFIG.attachments_folder).join(&cipher.uuid); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    let mut attachment_key = None; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Multipart::with_body(data.open(), boundary).foreach_entry(|mut field| { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-         // This is provided by the client, don't trust it 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-         let name = field.headers.filename.expect("No filename provided"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10])); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let path = base_path.join(&file_name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let size = match field.data.save() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            .memory_threshold(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            .size_limit(None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            .with_path(path) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            SaveResult::Full(SavedData::File(_, size)) => size as i32, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            SaveResult::Full(other) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                println!("Attachment is not a file: {:?}", other); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        match field.headers.name.as_str() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            "key" => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                use std::io::Read; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                let mut key_buffer = String::new(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if field.data.read_to_string(&mut key_buffer).is_ok() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    attachment_key = Some(key_buffer); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            SaveResult::Partial(_, reason) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                println!("Partial result: {:?}", reason); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            "data" => {                 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // This is provided by the client, don't trust it 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                let name = field.headers.filename.expect("No filename provided"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10])); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                let path = base_path.join(&file_name); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                let size = match field.data.save() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .memory_threshold(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .size_limit(None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .with_path(path) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    SaveResult::Full(SavedData::File(_, size)) => size as i32, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    SaveResult::Full(other) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        println!("Attachment is not a file: {:?}", other); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    SaveResult::Partial(_, reason) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        println!("Partial result: {:?}", reason); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    SaveResult::Error(e) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        println!("Error: {:?}", e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                attachment.key = attachment_key.clone(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                match attachment.save(&conn) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    Ok(()) => (), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    Err(_) => println!("Error: failed to save attachment") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            SaveResult::Error(e) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                println!("Error: {:?}", e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        match attachment.save(&conn) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            Ok(()) => (), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            Err(_) => println!("Error: failed to save attachment") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            _ => println!("Error: invalid multipart name") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }).expect("Error processing multipart data"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn))) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -793,6 +841,6 @@ fn _delete_cipher_attachment_by_id(uuid: &str, attachment_id: &str, headers: &He 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             Ok(()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        Err(_) => err!("Deleting attachement failed") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Err(_) => err!("Deleting attachment failed") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |