cargo/util/credential/
process.rs
1use std::{
5 io::{BufRead, BufReader, Write},
6 path::PathBuf,
7 process::{Child, Command, Stdio},
8};
9
10use anyhow::Context;
11use cargo_credential::{
12 Action, Credential, CredentialHello, CredentialRequest, CredentialResponse, Error, RegistryInfo,
13};
14
15pub struct CredentialProcessCredential {
16 path: PathBuf,
17}
18
19impl<'a> CredentialProcessCredential {
20 pub fn new(path: &str) -> Self {
21 Self {
22 path: PathBuf::from(path),
23 }
24 }
25
26 fn run(
27 &self,
28 child: &mut Child,
29 action: &Action<'_>,
30 registry: &RegistryInfo<'_>,
31 args: &[&str],
32 ) -> Result<Result<CredentialResponse, Error>, Error> {
33 let mut output_from_child = BufReader::new(child.stdout.take().unwrap());
34 let mut input_to_child = child.stdin.take().unwrap();
35 let mut buffer = String::new();
36
37 output_from_child
39 .read_line(&mut buffer)
40 .context("failed to read hello from credential provider")?;
41 let credential_hello: CredentialHello =
42 serde_json::from_str(&buffer).context("failed to deserialize hello")?;
43 tracing::debug!("credential-process > {credential_hello:?}");
44 if !credential_hello
45 .v
46 .contains(&cargo_credential::PROTOCOL_VERSION_1)
47 {
48 return Err(format!(
49 "credential provider supports protocol versions {:?}, while Cargo supports {:?}",
50 credential_hello.v,
51 [cargo_credential::PROTOCOL_VERSION_1]
52 )
53 .into());
54 }
55
56 let req = CredentialRequest {
58 v: cargo_credential::PROTOCOL_VERSION_1,
59 action: action.clone(),
60 registry: registry.clone(),
61 args: args.to_vec(),
62 };
63 let request = serde_json::to_string(&req).context("failed to serialize request")?;
64 tracing::debug!("credential-process < {req:?}");
65 writeln!(input_to_child, "{request}").context("failed to write to credential provider")?;
66 buffer.clear();
67 output_from_child
68 .read_line(&mut buffer)
69 .context("failed to read response from credential provider")?;
70
71 let response: Result<CredentialResponse, Error> =
73 serde_json::from_str(&buffer).context("failed to deserialize response")?;
74 tracing::debug!("credential-process > {response:?}");
75
76 drop(input_to_child);
78 let status = child.wait().context("credential process never started")?;
79 if !status.success() {
80 return Err(anyhow::anyhow!(
81 "credential process `{}` failed with status {}`",
82 self.path.display(),
83 status
84 )
85 .into());
86 }
87 tracing::trace!("credential process exited successfully");
88 Ok(response)
89 }
90}
91
92impl<'a> Credential for CredentialProcessCredential {
93 fn perform(
94 &self,
95 registry: &RegistryInfo<'_>,
96 action: &Action<'_>,
97 args: &[&str],
98 ) -> Result<CredentialResponse, Error> {
99 let mut cmd = Command::new(&self.path);
100 cmd.stdout(Stdio::piped());
101 cmd.stdin(Stdio::piped());
102 cmd.arg("--cargo-plugin");
103 tracing::debug!("credential-process: {cmd:?}");
104 let mut child = cmd.spawn().context("failed to spawn credential process")?;
105 match self.run(&mut child, action, registry, args) {
106 Err(e) => {
107 let _ = child.kill();
110 Err(e)
111 }
112 Ok(response) => response,
113 }
114 }
115}