1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Type definitions and interaction logic for entries in a password store

use crate::file_io::{CipherFile, PlainFile};
use crate::{utils, PassError, Result};
use std::collections::hash_set::Iter as HashSetIter;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;

/// An entry in the password store
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum StoreEntry {
    /// A reference to a directory which contains other entries
    Directory(StoreDirectoryRef),
    /// A reference to a file that holds the actual content of a store
    File(StoreFileRef),
}

impl StoreEntry {
    /// Retrieve the name of the store entry
    ///
    /// The name is represented as a relative path from the store root and can be used to retrieve this
    /// entry using [`retrieve`](crate::retrieve).
    pub fn name(&self) -> Result<String> {
        match self {
            Self::Directory(dir) => dir.name(),
            Self::File(file) => file.name(),
        }
    }

    /// Verify that this store entry matches what is actually present on the filesystem
    pub(crate) fn verify(&self) -> Result<()> {
        match self {
            Self::Directory(dir) => dir.verify(),
            Self::File(file) => file.verify(),
        }
    }
}

/// A reference to a directory in the password store
#[derive(Debug, Eq, Clone)]
pub struct StoreDirectoryRef {
    /// Absolute path to the referenced directory
    pub path: PathBuf,
    /// Other entries that are contained in this directory
    pub content: HashSet<StoreEntry>,
}

impl StoreDirectoryRef {
    /// Retrieve the name of the store entry
    ///
    /// The name is represented as a relative path from the store root and can be used to retrieve this
    /// entry using [`retrieve`](crate::retrieve).
    pub fn name(&self) -> Result<String> {
        Ok(utils::path2str(utils::abspath2relpath(&self.path)?)?.to_string())
    }

    /// Verify that *self* references an existing directory
    pub(crate) fn verify(&self) -> Result<()> {
        if self.path.exists() && self.path.is_dir() {
            Ok(())
        } else {
            Err(PassError::InvalidStoreFormat(
                self.path.to_owned(),
                "Path either does not exist or is not a directory".to_string(),
            ))
        }
    }

    /// iterate over all the entries contained in the storage hierarchy below this directory
    ///
    /// **Note:** The iterator iterates over all entries even if they are in a subdirectory further down the
    /// storage hierarchy thus flattening it. If you want to iterate only over the entries contained directly
    /// in this directory, use the [`content`](StoreDirectoryRef::content) field instead.
    pub fn iter(&self) -> StoreDirectoryIter {
        StoreDirectoryIter {
            entries: self.content.iter(),
            current_dir: None,
        }
    }
}

impl Hash for StoreDirectoryRef {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.path.hash(state);
    }
}

impl PartialEq for StoreDirectoryRef {
    fn eq(&self, other: &Self) -> bool {
        self.path == other.path
    }
}

impl<'a> IntoIterator for &'a StoreDirectoryRef {
    type Item = &'a StoreEntry;
    type IntoIter = StoreDirectoryIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

/// An iterator that iterates over [`&StoreEntries`](StoreEntry) contained in a directory and its
/// subdirectories
#[derive(Debug)]
pub struct StoreDirectoryIter<'a> {
    entries: HashSetIter<'a, StoreEntry>,
    current_dir: Option<Box<StoreDirectoryIter<'a>>>,
}

impl<'a> Iterator for StoreDirectoryIter<'a> {
    type Item = &'a StoreEntry;

    fn next(&mut self) -> Option<Self::Item> {
        match self.current_dir {
            Some(ref mut entry) => match entry.next() {
                Some(next_entry) => Some(next_entry),
                None => {
                    self.current_dir = None;
                    self.next()
                }
            },
            None => match self.entries.next() {
                Some(next_entry) => match next_entry {
                    StoreEntry::File(_) => Some(next_entry),
                    StoreEntry::Directory(dir) => {
                        self.current_dir = Some(Box::new(dir.iter()));
                        self.next()
                    }
                },
                None => None,
            },
        }
    }
}

/// A reference to a file in the password store
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct StoreFileRef {
    /// Absolute path to the referenced directory
    pub path: PathBuf,
}

impl StoreFileRef {
    /// Retrieve the name of the store entry
    ///
    /// The name is represented as a relative path from the store root and can be used to retrieve this
    /// entry using [`retrieve`](crate::retrieve).
    pub fn name(&self) -> Result<String> {
        let relative_path = utils::path2str(utils::abspath2relpath(&self.path)?)?;

        Ok(relative_path
            .strip_suffix(".gpg")
            .ok_or_else(|| {
                PassError::InvalidStoreFormat(
                    self.path.to_owned(),
                    "File does not end with .gpg extension".to_string(),
                )
            })?
            .to_string())
    }

    /// Get an IO handle to the encrypted content of this file
    pub fn cipher_io(&self) -> Result<CipherFile> {
        CipherFile::new(&self.path)
    }

    /// Get an IO handle to the plaintext content of this file
    pub fn plain_io(&self) -> Result<PlainFile> {
        PlainFile::new(&self.path)
    }

    /// Verify that *self* references an existing file with the expected file extension
    pub(crate) fn verify(&self) -> Result<()> {
        if self.path.exists()
            && self.path.is_file()
            && match self.path.extension() {
                None => false,
                Some(extension) => extension == "gpg",
            }
        {
            Ok(())
        } else {
            Err(PassError::InvalidStoreFormat(self.path.to_owned(), "Path either does not exist, is not a regular file or does not have a .gpg extension".to_string()))
        }
    }
}