Skip to main content

keepass_ng/db/types/
value.rs

1use std::{fmt::Display, ops::Deref};
2
3use secrecy::{ExposeSecret, SecretBox};
4use zeroize::Zeroize;
5
6/// Value in an [Entry][crate::db::Entry]'s fields or an [Attachment][crate::db::Attachment]'s data
7///
8/// Can be either unprotected or protected
9#[derive(Debug)]
10pub enum Value<T: Zeroize> {
11    /// unprotected data
12    Unprotected(T),
13
14    /// protected data
15    Protected(SecretBox<T>),
16}
17
18impl<T: Zeroize + Display> std::fmt::Display for Value<T> {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            Value::Unprotected(data) => write!(f, "{}", data),
22            Value::Protected(_) => write!(f, "[redacted]"),
23        }
24    }
25}
26
27impl<T: Zeroize + Default> Default for Value<T> {
28    fn default() -> Self {
29        Value::Unprotected(Default::default())
30    }
31}
32
33impl<T: Zeroize> Value<T> {
34    /// Create a new unprotected value
35    pub fn unprotected(data: impl Into<T>) -> Self {
36        Value::Unprotected(data.into())
37    }
38
39    /// Create a new protected text data value
40    pub fn protected(data: impl Into<T>) -> Self {
41        Value::Protected(SecretBox::new(Box::new(data.into())))
42    }
43
44    /// Returns true if the value is protected (either PBytes or PString)
45    pub fn is_protected(&self) -> bool {
46        matches!(self, Value::Protected(_))
47    }
48
49    /// Returns the value as a string slice
50    pub fn get(&self) -> &T {
51        match self {
52            Value::Unprotected(data) => data,
53            Value::Protected(data) => data.expose_secret(),
54        }
55    }
56}
57
58impl<T: Zeroize + Clone> Clone for Value<T> {
59    fn clone(&self) -> Self {
60        match self {
61            Value::Unprotected(data) => Value::Unprotected(data.clone()),
62            Value::Protected(data) => Value::Protected(SecretBox::new(Box::new(data.expose_secret().clone()))),
63        }
64    }
65}
66
67impl<T: Zeroize + PartialEq> PartialEq for Value<T> {
68    fn eq(&self, other: &Self) -> bool {
69        match (self, other) {
70            (Value::Unprotected(a), Value::Unprotected(b)) => a == b,
71            (Value::Protected(a), Value::Protected(b)) => a.expose_secret() == b.expose_secret(),
72            _ => false,
73        }
74    }
75}
76
77impl<T: Zeroize + Eq> Eq for Value<T> {}
78
79impl<T: Zeroize> Deref for Value<T> {
80    type Target = T;
81
82    fn deref(&self) -> &Self::Target {
83        self.get()
84    }
85}
86
87#[cfg(feature = "serialization")]
88impl<T: Zeroize + serde::Serialize> serde::Serialize for Value<T> {
89    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90    where
91        S: serde::Serializer,
92    {
93        match self {
94            Value::Unprotected(data) => data.serialize(serializer),
95            Value::Protected(data) => data.expose_secret().serialize(serializer),
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::Value;
103
104    #[test]
105    fn test_value() {
106        let unprotected: Value<String> = Value::unprotected("test");
107        let protected: Value<String> = Value::protected("test");
108        assert!(!unprotected.is_protected());
109        assert!(protected.is_protected());
110        assert!(!unprotected.is_empty());
111        assert!(!protected.is_empty());
112
113        assert_eq!(unprotected.get(), "test");
114        assert_eq!(protected.get(), "test");
115
116        assert_eq!(format!("{}", unprotected), "test");
117        assert_eq!(format!("{}", protected), "[redacted]");
118
119        assert_ne!(unprotected, protected);
120    }
121}