fluidattacks_core_git/clone/
reason.rs1use tokio::process::Command;
4
5use crate::call::not_run;
6use crate::errors::GitCallError;
7
8const REDACTED: &str = "[REDACTED]";
9
10pub async fn run(command: &mut Command, secrets: &[&str]) -> Result<(), GitCallError> {
14 let output = command
15 .output()
16 .await
17 .map_err(|source| not_run("clone", source))?;
18
19 if output.status.success() {
20 return Ok(());
21 }
22
23 Err(GitCallError::Refused {
24 command: "clone",
25 status: output.status,
26 stderr: hide(&String::from_utf8_lossy(&output.stderr), secrets),
27 })
28}
29
30#[must_use]
33pub fn hide(said: &str, secrets: &[&str]) -> String {
34 secrets
35 .iter()
36 .filter(|secret| !secret.is_empty())
37 .fold(said.to_owned(), |text, secret| {
38 text.replace(secret, REDACTED)
39 })
40}
41
42#[cfg(test)]
43mod tests {
44 use tokio::process::Command;
45
46 use super::run;
47 use crate::errors::GitCallError;
48
49 fn shell(script: &str) -> Command {
50 let mut command = Command::new("sh");
51 command.args(["-c", script]);
52
53 command
54 }
55
56 fn refusal(outcome: Result<(), GitCallError>) -> (Option<i32>, String) {
57 match outcome {
58 Err(GitCallError::Refused { status, stderr, .. }) => (status.code(), stderr),
59 other => panic!("{other:?}"),
60 }
61 }
62
63 #[tokio::test]
64 async fn a_clone_that_worked_says_nothing() {
65 assert!(run(&mut shell("exit 0"), &[]).await.is_ok());
66 }
67
68 #[tokio::test]
69 async fn a_clone_that_failed_carries_what_it_printed_whole() {
70 let outcome = run(
71 &mut shell("echo 'fatal: no such repository' >&2; exit 128"),
72 &[],
73 )
74 .await;
75
76 assert_eq!(
77 refusal(outcome),
78 (Some(128), "fatal: no such repository\n".to_owned())
79 );
80 }
81
82 #[tokio::test]
83 async fn a_clone_that_printed_nothing_carries_its_status() {
84 let outcome = run(&mut shell("exit 3"), &[]).await;
85
86 assert_eq!(refusal(outcome), (Some(3), String::new()));
87 }
88
89 #[tokio::test]
90 async fn a_secret_the_server_answered_is_taken_out() {
91 let outcome = run(
92 &mut shell("echo 'remote: rejected s3cr3t' >&2; exit 128"),
93 &["s3cr3t"],
94 )
95 .await;
96
97 assert_eq!(refusal(outcome).1, "remote: rejected [REDACTED]\n");
98 }
99
100 #[tokio::test]
101 async fn a_credential_that_is_empty_leaves_the_message_alone() {
102 let outcome = run(
103 &mut shell("echo 'fatal: no such repository' >&2; exit 128"),
104 &[""],
105 )
106 .await;
107
108 assert_eq!(refusal(outcome).1, "fatal: no such repository\n");
109 }
110}