fluidattacks_core_git/clone/
https.rs1use anyhow::{Context, Result};
2use base64::Engine;
3use std::path::Path;
4use tokio::process::Command;
5use url::Url;
6
7use super::{build_clone_args, reason, CloneOpts};
8use crate::types::CredentialKind;
9
10pub async fn clone_https(opts: &CloneOpts, dest: &Path) -> Result<()> {
11 let CredentialKind::Https { user, password } = &opts
12 .credentials
13 .as_ref()
14 .context("missing credentials")?
15 .kind
16 else {
17 anyhow::bail!("expected HTTPS credentials");
18 };
19 let repo_url = embed_credentials_in_url(&opts.repo_url, user, password)?;
20
21 let extra_config = vec!["-c".to_owned(), "http.sslVerify=false".to_owned()];
22 let args = build_clone_args(opts, &repo_url, dest, &extra_config);
23 run_git_clone(&args, &[password.as_str()]).await
24}
25
26pub async fn clone_https_token(opts: &CloneOpts, dest: &Path) -> Result<()> {
27 let CredentialKind::Token {
28 token,
29 is_pat,
30 oauth_type: _,
31 } = &opts
32 .credentials
33 .as_ref()
34 .context("missing credentials")?
35 .kind
36 else {
37 anyhow::bail!("expected Token credentials");
38 };
39
40 if *is_pat {
41 let encoded = base64::engine::general_purpose::STANDARD.encode(format!(":{token}"));
42 let extra_config = vec![
43 "-c".to_owned(),
44 "http.sslVerify=false".to_owned(),
45 "-c".to_owned(),
46 format!("http.extraHeader=Authorization: Basic {encoded}"),
47 ];
48 let args = build_clone_args(opts, &opts.repo_url, dest, &extra_config);
49 return run_git_clone(&args, &[token.as_str(), &encoded]).await;
50 }
51
52 let user = if opts.repo_url.to_lowercase().contains("bitbucket") {
53 "x-token-auth"
54 } else {
55 "oauth2"
56 };
57
58 let repo_url = embed_credentials_in_url(&opts.repo_url, user, token)?;
59 let extra_config = vec!["-c".to_owned(), "http.sslVerify=false".to_owned()];
60 let args = build_clone_args(opts, &repo_url, dest, &extra_config);
61 run_git_clone(&args, &[token.as_str()]).await
62}
63
64pub async fn clone_https_public(opts: &CloneOpts, dest: &Path) -> Result<()> {
65 let extra_config = vec!["-c".to_owned(), "http.sslVerify=false".to_owned()];
66 let args = build_clone_args(opts, &opts.repo_url, dest, &extra_config);
67 run_git_clone(&args, &[]).await
68}
69
70pub fn embed_credentials_in_url(raw_url: &str, user: &str, password: &str) -> Result<String> {
71 let mut url = Url::parse(raw_url).context("parsing URL")?;
72 url.set_username(user)
73 .map_err(|()| anyhow::anyhow!("failed to set username"))?;
74 url.set_password(Some(password))
75 .map_err(|()| anyhow::anyhow!("failed to set password"))?;
76 Ok(url.to_string())
77}
78
79#[must_use]
81pub fn build_https_config_args(
82 follow_redirects: bool,
83 is_pat: bool,
84 token: Option<&str>,
85 disable_credential_prompt: bool,
86) -> Vec<String> {
87 let mut args = vec![
88 "-c".to_owned(),
89 "http.sslVerify=false".to_owned(),
90 "-c".to_owned(),
91 format!("http.followRedirects={follow_redirects}"),
92 ];
93 if is_pat {
94 if let Some(t) = token {
95 let encoded = base64::engine::general_purpose::STANDARD.encode(format!(":{t}"));
96 args.extend([
97 "-c".to_owned(),
98 format!("http.extraHeader=Authorization: Basic {encoded}"),
99 ]);
100 }
101 }
102 if disable_credential_prompt {
103 args.extend(["-c".to_owned(), "credential.helper=".to_owned()]);
104 }
105 args
106}
107
108#[allow(clippy::too_many_arguments)]
110pub fn format_https_url(
111 repo_url: &str,
112 user: Option<&str>,
113 password: Option<&str>,
114 token: Option<&str>,
115 provider: Option<&str>,
116 is_pat: bool,
117) -> Result<String> {
118 let mut parsed = Url::parse(repo_url).context("parsing URL")?;
119 let path = parsed.path().to_owned();
121 parsed.set_path(&path);
122
123 if is_pat {
124 return Ok(parsed.to_string());
125 }
126
127 if let Some(t) = token {
128 let user_part = match provider {
129 Some(p) if p.eq_ignore_ascii_case("BITBUCKET") => "x-token-auth",
130 Some(_) => "oauth2",
131 None => "",
132 };
133 if user_part.is_empty() {
134 parsed
136 .set_username(t)
137 .map_err(|()| anyhow::anyhow!("set username"))?;
138 parsed
139 .set_password(None)
140 .map_err(|()| anyhow::anyhow!("set password"))?;
141 } else {
142 parsed
144 .set_username(user_part)
145 .map_err(|()| anyhow::anyhow!("set username"))?;
146 parsed
147 .set_password(Some(t))
148 .map_err(|()| anyhow::anyhow!("set password"))?;
149 }
150 return Ok(parsed.to_string());
151 }
152
153 if let (Some(u), Some(p)) = (user, password) {
154 parsed
155 .set_username(u)
156 .map_err(|()| anyhow::anyhow!("set username"))?;
157 parsed
158 .set_password(Some(p))
159 .map_err(|()| anyhow::anyhow!("set password"))?;
160 return Ok(parsed.to_string());
161 }
162
163 Ok(parsed.to_string())
164}
165
166async fn run_git_clone(args: &[String], secrets: &[&str]) -> Result<()> {
167 reason::run(Command::new("git").args(args), "HTTPS", secrets).await
168}