| 
					
				 | 
			
			
				@@ -79,19 +79,39 @@ impl Cipher { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn validate_notes(cipher_data: &[CipherData]) -> EmptyResult { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn validate_cipher_data(cipher_data: &[CipherData]) -> EmptyResult { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let mut validation_errors = serde_json::Map::new(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let max_note_size = CONFIG._max_note_size(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let max_note_size_msg = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             format!("The field Notes exceeds the maximum encrypted value length of {} characters.", &max_note_size); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         for (index, cipher) in cipher_data.iter().enumerate() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // Validate the note size and if it is exceeded return a warning 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if let Some(note) = &cipher.notes { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if note.len() > max_note_size { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     validation_errors 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         .insert(format!("Ciphers[{index}].Notes"), serde_json::to_value([&max_note_size_msg]).unwrap()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            // Validate the password history if it contains `null` values and if so, return a warning 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if let Some(Value::Array(password_history)) = &cipher.password_history { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                for pwh in password_history { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if let Value::Object(pwo) = pwh { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        if pwo.get("password").is_some_and(|p| !p.is_string()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            validation_errors.insert( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                format!("Ciphers[{index}].Notes"), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                serde_json::to_value([ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                    "The password history contains a `null` value. Only strings are allowed.", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                ]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                .unwrap(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if !validation_errors.is_empty() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             let err_json = json!({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 "message": "The model state is invalid.", 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -153,27 +173,39 @@ impl Cipher { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .as_ref() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .and_then(|s| { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 serde_json::from_str::<Vec<LowerCase<Value>>>(s) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    .inspect_err(|e| warn!("Error parsing fields {:?}", e)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .inspect_err(|e| warn!("Error parsing fields {e:?} for {}", self.uuid)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     .ok() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .map(|d| d.into_iter().map(|d| d.data).collect()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .unwrap_or_default(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let password_history_json: Vec<_> = self 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .password_history 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .as_ref() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .and_then(|s| { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 serde_json::from_str::<Vec<LowerCase<Value>>>(s) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    .inspect_err(|e| warn!("Error parsing password history {:?}", e)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .inspect_err(|e| warn!("Error parsing password history {e:?} for {}", self.uuid)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     .ok() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            .map(|d| d.into_iter().map(|d| d.data).collect()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            .map(|d| { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // Check every password history item if they are valid and return it. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // If a password field has the type `null` skip it, it breaks newer Bitwarden clients 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                d.into_iter() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .filter_map(|d| match d.data.get("password") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        Some(p) if p.is_string() => Some(d.data), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        _ => None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    .collect() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             .unwrap_or_default(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // Get the type_data or a default to an empty json object '{}'. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // If not passing an empty object, mobile clients will crash. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut type_data_json = serde_json::from_str::<LowerCase<Value>>(&self.data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            .map(|d| d.data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            .unwrap_or_else(|_| Value::Object(serde_json::Map::new())); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let mut type_data_json = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            serde_json::from_str::<LowerCase<Value>>(&self.data).map(|d| d.data).unwrap_or_else(|_| { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                warn!("Error parsing data field for {}", self.uuid); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Value::Object(serde_json::Map::new()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // Set the first element of the Uris array as Uri, this is needed several (mobile) clients. 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -189,10 +221,13 @@ impl Cipher { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // Fix secure note issues when data is invalid 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // This breaks at least the native mobile clients 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if self.atype == 2 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            && (self.data.is_empty() || self.data.eq("{}") || self.data.to_ascii_lowercase().eq("{\"type\":null}")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            type_data_json = json!({"type": 0}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self.atype == 2 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            match type_data_json { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                Value::Object(ref t) if t.get("type").is_some_and(|t| t.is_number()) => {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                _ => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    type_data_json = json!({"type": 0}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // Clone the type_data and add some default value. 
			 |