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