1use inquire::InquireError;
2
3use crate::{
4 keys::{check_key_conflicts, is_encrypted},
5 typedetect::{FileFormat, detect_format},
6 types::{ProcessHandling, TypedValue, ValueType},
7 vaultfunc,
8};
9
10fn get_at_path_json<'a>(
11 val: &'a serde_json::Value,
12 path: &str,
13) -> Option<&'a serde_json::Value> {
14 let mut current = val;
15 for part in path.split('.') {
16 match current {
17 serde_json::Value::Object(map) => current = map.get(part)?,
18 _ => return None,
19 }
20 }
21 Some(current)
22}
23
24fn set_at_path_json(target: &mut serde_json::Value, path: &str, value: serde_json::Value) {
25 let parts: Vec<&str> = path.split('.').collect();
26 let mut current = target;
27 for (i, &part) in parts.iter().enumerate() {
28 if i == parts.len() - 1 {
29 if let serde_json::Value::Object(map) = current {
30 map.insert(part.to_string(), value);
31 }
32 return;
33 }
34 if let serde_json::Value::Object(map) = current {
35 map.entry(part.to_string())
36 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
37 current = map.get_mut(part).unwrap();
38 }
39 }
40}
41
42fn extract_keys_json(root: &serde_json::Value, keys: &[String]) -> serde_json::Value {
43 let mut result = serde_json::Value::Object(serde_json::Map::new());
44 for key_path in keys {
45 if let Some(val) = get_at_path_json(root, key_path) {
46 set_at_path_json(&mut result, key_path, val.clone());
47 }
48 }
49 result
50}
51
52fn get_at_path_yaml<'a>(
53 val: &'a serde_yaml::Value,
54 path: &str,
55) -> Option<&'a serde_yaml::Value> {
56 let mut current = val;
57 for part in path.split('.') {
58 match current {
59 serde_yaml::Value::Mapping(map) => {
60 let str_key = serde_yaml::Value::String(part.to_string());
61 if let Some(v) = map.get(&str_key) {
62 current = v;
63 } else if let Ok(n) = part.parse::<i64>() {
64 let num_key = serde_yaml::Value::Number(n.into());
65 current = map.get(&num_key)?;
66 } else {
67 return None;
68 }
69 }
70 _ => return None,
71 }
72 }
73 Some(current)
74}
75
76fn set_at_path_yaml(target: &mut serde_yaml::Value, path: &str, value: serde_yaml::Value) {
77 let parts: Vec<&str> = path.split('.').collect();
78 let mut current = target;
79 for (i, &part) in parts.iter().enumerate() {
80 let key = serde_yaml::Value::String(part.to_string());
81 if i == parts.len() - 1 {
82 if let serde_yaml::Value::Mapping(map) = current {
83 map.insert(key, value);
84 }
85 return;
86 }
87 if let serde_yaml::Value::Mapping(map) = current {
88 if !map.contains_key(&key) {
89 map.insert(key.clone(), serde_yaml::Value::Mapping(serde_yaml::Mapping::new()));
90 }
91 current = map.get_mut(&key).unwrap();
92 }
93 }
94}
95
96fn extract_keys_yaml(root: &serde_yaml::Value, keys: &[String]) -> serde_yaml::Value {
97 let mut result = serde_yaml::Value::Mapping(serde_yaml::Mapping::new());
98 for key_path in keys {
99 if let Some(val) = get_at_path_yaml(root, key_path) {
100 set_at_path_yaml(&mut result, key_path, val.clone());
101 }
102 }
103 result
104}
105
106pub fn decrypt(
113 input: &str,
114 output: &str,
115 password: &str,
116 keys: &[String],
117) -> Result<(), Box<dyn std::error::Error>> {
118 check_key_conflicts(keys)?;
119 let mut processor = make_decrypt_processor(password);
120 match detect_format(input)? {
121 FileFormat::Json => crate::json::process_file(input, output, keys, &mut processor),
122 FileFormat::Yaml => crate::yaml::process_file(input, output, keys, &mut processor),
123 }
124}
125
126pub fn decrypt_value_only(
129 input: &str,
130 output: &str,
131 password: &str,
132 keys: &[String],
133) -> Result<(), Box<dyn std::error::Error>> {
134 check_key_conflicts(keys)?;
135 let mut processor = make_decrypt_processor(password);
136 match detect_format(input)? {
137 FileFormat::Json => {
138 let tree = crate::json::process_in_memory(input, keys, &mut processor)?;
139 let minimal = extract_keys_json(&tree, keys);
140 let output_str = serde_json::to_string_pretty(&minimal)?;
141 if output == "stdout" {
142 println!("{}", output_str);
143 } else {
144 std::fs::write(output, &output_str)
145 .map_err(|e| format!("error writing '{}': {}", output, e))?;
146 }
147 }
148 FileFormat::Yaml => {
149 let tree = crate::yaml::process_in_memory(input, keys, &mut processor)?;
150 let minimal = extract_keys_yaml(&tree, keys);
151 let yaml_str = serde_yaml::to_string(&minimal)?;
152 if output == "stdout" {
153 print!("{}", yaml_str);
154 } else {
155 std::fs::write(output, &yaml_str)
156 .map_err(|e| format!("error writing '{}': {}", output, e))?;
157 }
158 }
159 }
160 Ok(())
161}
162
163pub fn decrypt_fuzzy(
168 input: &str,
169 output: &str,
170 password: &str,
171 value_only: bool,
172) -> Result<(), Box<dyn std::error::Error>> {
173 let keys = match detect_format(input)? {
174 FileFormat::Json => crate::json::get_encrypted_keys(input)?,
175 FileFormat::Yaml => crate::yaml::get_encrypted_keys(input)?,
176 };
177
178 if keys.is_empty() {
179 println!("No encrypted values found in '{}'.", input);
180 return Ok(());
181 }
182
183 let selected = match inquire::MultiSelect::new(
184 "Select keys to decrypt (Space to select, Enter to confirm):",
185 keys,
186 )
187 .prompt()
188 {
189 Ok(s) => s,
190 Err(InquireError::OperationCanceled | InquireError::OperationInterrupted) => {
191 println!("Cancelled.");
192 return Ok(());
193 }
194 Err(e) => return Err(e.into()),
195 };
196
197 if selected.is_empty() {
198 println!("No keys selected.");
199 return Ok(());
200 }
201
202 if value_only {
203 decrypt_value_only(input, output, password, &selected)
204 } else {
205 decrypt(input, output, password, &selected)
206 }
207}
208
209pub(crate) fn make_decrypt_processor(
210 password: &str,
211) -> impl FnMut(
212 TypedValue,
213 ValueType,
214 &str,
215) -> Result<(TypedValue, ValueType, ProcessHandling), Box<dyn std::error::Error>>
216+ '_ {
217 move |typed, vt, key_path| {
218 let (encrypted, vault_str) = is_encrypted(&typed);
219 if !encrypted {
220 return Ok((typed, vt, ProcessHandling::Skip));
221 }
222 let vault_str = vault_str.unwrap();
223 match vaultfunc::decrypt(&vault_str, password) {
224 Ok((decrypted, new_vt)) => Ok((decrypted, new_vt, ProcessHandling::Process)),
225 Err(e) => {
226 eprintln!("error decrypting key '{}': {}", key_path, e);
227 Ok((typed, vt, ProcessHandling::Skip))
228 }
229 }
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 fn resources(filename: &str) -> String {
238 format!(
239 "{}/resources/tests/{}",
240 env!("CARGO_MANIFEST_DIR"),
241 filename
242 )
243 }
244
245 struct YamlDecryptCase {
248 input: &'static str,
249 reference: &'static str,
250 keys: &'static [&'static str],
251 }
252
253 fn run_yaml_decrypt(case: &YamlDecryptCase) {
254 let input = resources(case.input);
255 let reference = resources(case.reference);
256 let keys: Vec<String> = case.keys.iter().map(|s| s.to_string()).collect();
257
258 let output = tempfile::NamedTempFile::new().unwrap();
259 let output_path = output.path().to_str().unwrap().to_string();
260
261 decrypt(&input, &output_path, "test999", &keys).unwrap();
262
263 let got: serde_yaml::Value =
264 serde_yaml::from_str(&std::fs::read_to_string(&output_path).unwrap()).unwrap();
265 let expected: serde_yaml::Value =
266 serde_yaml::from_str(&std::fs::read_to_string(&reference).unwrap()).unwrap();
267 assert_eq!(got, expected, "YAML decrypt '{}' failed", case.input);
268 }
269
270 #[test]
271 fn test_decrypt_yaml_all_keys() {
272 run_yaml_decrypt(&YamlDecryptCase {
273 input: "partial_encrypted_example.yaml",
274 reference: "partial_encrypted_example_decrypted_01.yaml",
275 keys: &[],
276 });
277 }
278
279 #[test]
280 fn test_decrypt_yaml_filtered_key() {
281 run_yaml_decrypt(&YamlDecryptCase {
282 input: "partial_encrypted_example.yaml",
283 reference: "partial_encrypted_example_decrypted_03.yaml",
284 keys: &["third.carrot"],
285 });
286 }
287
288 #[test]
289 fn test_decrypt_yaml_multiple_keys() {
290 run_yaml_decrypt(&YamlDecryptCase {
291 input: "partial_encrypted_example.yaml",
292 reference: "partial_encrypted_example_decrypted_04.yaml",
293 keys: &["first.a", "first.z", "second.b.2", "fourth.list"],
294 });
295 }
296
297 #[test]
298 fn test_decrypt_yaml_02() {
299 run_yaml_decrypt(&YamlDecryptCase {
300 input: "partial_encrypted_example_02.yaml",
301 reference: "partial_encrypted_example_decrypted_01.yaml",
302 keys: &[],
303 });
304 }
305
306 #[test]
307 fn test_decrypt_yaml_03() {
308 run_yaml_decrypt(&YamlDecryptCase {
309 input: "partial_encrypted_example_03.yaml",
310 reference: "partial_encrypted_example_decrypted_01.yaml",
311 keys: &[],
312 });
313 }
314
315 struct JsonDecryptCase {
318 input: &'static str,
319 reference: &'static str,
320 keys: &'static [&'static str],
321 }
322
323 fn run_json_decrypt(case: &JsonDecryptCase) {
324 let input = resources(case.input);
325 let reference = resources(case.reference);
326 let keys: Vec<String> = case.keys.iter().map(|s| s.to_string()).collect();
327
328 let output = tempfile::NamedTempFile::new().unwrap();
329 let output_path = output.path().to_str().unwrap().to_string();
330
331 decrypt(&input, &output_path, "test999", &keys).unwrap();
332
333 let got: serde_json::Value =
334 serde_json::from_str(&std::fs::read_to_string(&output_path).unwrap()).unwrap();
335 let expected: serde_json::Value =
336 serde_json::from_str(&std::fs::read_to_string(&reference).unwrap()).unwrap();
337 assert_eq!(got, expected, "JSON decrypt '{}' failed", case.input);
338 }
339
340 #[test]
341 fn test_decrypt_json_all_keys() {
342 run_json_decrypt(&JsonDecryptCase {
343 input: "partial_encrypted_example.json",
344 reference: "partial_encrypted_example_decrypted_01.json",
345 keys: &[],
346 });
347 }
348
349 #[test]
350 fn test_decrypt_json_filtered_key() {
351 run_json_decrypt(&JsonDecryptCase {
352 input: "partial_encrypted_example.json",
353 reference: "partial_encrypted_example_decrypted_03.json",
354 keys: &["third.carrot"],
355 });
356 }
357
358 #[test]
359 fn test_decrypt_json_multiple_keys() {
360 run_json_decrypt(&JsonDecryptCase {
361 input: "partial_encrypted_example.json",
362 reference: "partial_encrypted_example_decrypted_04.json",
363 keys: &["first.a", "first.z", "second.b.2", "fourth.list"],
364 });
365 }
366
367 #[test]
368 fn test_decrypt_json_02() {
369 run_json_decrypt(&JsonDecryptCase {
370 input: "partial_encrypted_example_02.json",
371 reference: "partial_encrypted_example_decrypted_01.json",
372 keys: &[],
373 });
374 }
375
376 #[test]
377 fn test_decrypt_json_03() {
378 run_json_decrypt(&JsonDecryptCase {
379 input: "partial_encrypted_example_03.json",
380 reference: "partial_encrypted_example_decrypted_01.json",
381 keys: &[],
382 });
383 }
384}