git_lfs_git/
http_options.rs1use std::path::Path;
13use std::process::Command;
14
15use crate::Error;
16
17#[derive(Debug, Default, Clone)]
18pub struct HttpOptions {
19 pub ssl_ca_info: Option<String>,
21 pub ssl_verify: Option<bool>,
24}
25
26impl HttpOptions {
27 pub fn for_url(cwd: &Path, url: &str) -> Result<Self, Error> {
31 let scoped = scoped_keys(cwd, url)?;
32 Ok(Self {
33 ssl_ca_info: scoped
34 .lookup("sslcainfo")
35 .or_else(|| get_global(cwd, "http.sslcainfo").ok().flatten()),
36 ssl_verify: scoped
37 .lookup("sslverify")
38 .or_else(|| get_global(cwd, "http.sslVerify").ok().flatten())
39 .map(|v| parse_bool(&v)),
40 })
41 }
42}
43
44struct Scoped(Vec<(String, String, String)>);
48
49impl Scoped {
50 fn lookup(&self, key: &str) -> Option<String> {
51 let key = key.to_ascii_lowercase();
52 self.0
55 .iter()
56 .find(|(_, k, _)| k.to_ascii_lowercase() == key)
57 .map(|(_, _, v)| v.clone())
58 }
59}
60
61fn scoped_keys(cwd: &Path, url: &str) -> Result<Scoped, Error> {
62 let out = Command::new("git")
63 .arg("-C")
64 .arg(cwd)
65 .args([
66 "config",
67 "--includes",
68 "--null",
69 "--get-regexp",
70 r"^http\..+\..+$",
71 ])
72 .output()?;
73 if !out.status.success() {
74 return Ok(Scoped(Vec::new()));
76 }
77 let raw = String::from_utf8_lossy(&out.stdout);
78 let mut entries: Vec<(String, String, String)> = Vec::new();
79 for record in raw.split('\0').filter(|s| !s.is_empty()) {
80 let (key_full, value) = match record.split_once('\n') {
82 Some((k, v)) => (k, v),
83 None => (record, ""),
84 };
85 let parts: Vec<&str> = key_full.splitn(2, '.').collect();
88 if parts.len() != 2 || parts[0] != "http" {
89 continue;
90 }
91 let rest = parts[1];
92 let last_dot = rest.rfind('.').unwrap_or(rest.len());
93 if last_dot == rest.len() {
94 continue;
95 }
96 let prefix = &rest[..last_dot];
97 let subkey = &rest[last_dot + 1..];
98 if url_matches(prefix, url) {
99 entries.push((prefix.to_owned(), subkey.to_owned(), value.to_owned()));
100 }
101 }
102 entries.sort_by_key(|e| std::cmp::Reverse(e.0.len()));
104 Ok(Scoped(entries))
105}
106
107fn get_global(cwd: &Path, key: &str) -> Result<Option<String>, Error> {
108 let out = Command::new("git")
109 .arg("-C")
110 .arg(cwd)
111 .args(["config", "--includes", "--get", key])
112 .output()?;
113 match out.status.code() {
114 Some(0) => Ok(Some(String::from_utf8_lossy(&out.stdout).trim().to_owned())),
115 Some(1) | Some(128) | Some(129) => Ok(None),
116 _ => Err(Error::Failed(
117 String::from_utf8_lossy(&out.stderr).trim().to_owned(),
118 )),
119 }
120}
121
122fn url_matches(prefix: &str, url: &str) -> bool {
128 let p = prefix.trim_end_matches('/').to_ascii_lowercase();
129 let u = url.trim_end_matches('/').to_ascii_lowercase();
130 u == p || u.starts_with(&format!("{p}/"))
131}
132
133fn parse_bool(s: &str) -> bool {
134 matches!(
135 s.trim().to_ascii_lowercase().as_str(),
136 "true" | "1" | "yes" | "on"
137 )
138}