claude_agent/client/
network.rs1use std::env;
4use std::path::PathBuf;
5
6#[derive(Clone, Debug, Default)]
8pub struct NetworkConfig {
9 pub proxy: Option<ProxyConfig>,
11 pub ca_cert: Option<PathBuf>,
13 pub client_cert: Option<ClientCertConfig>,
15}
16
17#[derive(Clone, Debug)]
19pub struct ProxyConfig {
20 pub https: Option<String>,
22 pub http: Option<String>,
24 pub no_proxy: Vec<String>,
26}
27
28#[derive(Clone, Debug)]
30pub struct ClientCertConfig {
31 pub cert_path: PathBuf,
33 pub key_path: PathBuf,
35 pub key_passphrase: Option<String>,
37}
38
39impl NetworkConfig {
40 pub fn from_env() -> Self {
42 Self {
43 proxy: ProxyConfig::from_env(),
44 ca_cert: env::var("SSL_CERT_FILE")
45 .ok()
46 .or_else(|| env::var("REQUESTS_CA_BUNDLE").ok())
47 .map(PathBuf::from),
48 client_cert: ClientCertConfig::from_env(),
49 }
50 }
51
52 pub fn with_proxy(mut self, proxy: ProxyConfig) -> Self {
54 self.proxy = Some(proxy);
55 self
56 }
57
58 pub fn with_ca_cert(mut self, path: impl Into<PathBuf>) -> Self {
60 self.ca_cert = Some(path.into());
61 self
62 }
63
64 pub fn with_client_cert(mut self, cert: ClientCertConfig) -> Self {
66 self.client_cert = Some(cert);
67 self
68 }
69
70 pub fn is_configured(&self) -> bool {
72 self.proxy.is_some() || self.ca_cert.is_some() || self.client_cert.is_some()
73 }
74
75 pub fn apply_to_builder(
77 &self,
78 mut builder: reqwest::ClientBuilder,
79 ) -> Result<reqwest::ClientBuilder, std::io::Error> {
80 if let Some(ref proxy) = self.proxy {
81 builder = proxy.apply_to_builder(builder)?;
82 }
83
84 if let Some(ref ca_path) = self.ca_cert {
85 let cert_data = std::fs::read(ca_path)?;
86 if let Ok(cert) = reqwest::Certificate::from_pem(&cert_data) {
87 builder = builder.add_root_certificate(cert);
88 }
89 }
90
91 if let Some(ref client_cert) = self.client_cert {
92 builder = client_cert.apply_to_builder(builder)?;
93 }
94
95 Ok(builder)
96 }
97}
98
99impl ProxyConfig {
100 pub fn from_env() -> Option<Self> {
102 let https = env::var("HTTPS_PROXY")
103 .ok()
104 .or_else(|| env::var("https_proxy").ok());
105 let http = env::var("HTTP_PROXY")
106 .ok()
107 .or_else(|| env::var("http_proxy").ok());
108
109 if https.is_none() && http.is_none() {
110 return None;
111 }
112
113 let no_proxy = env::var("NO_PROXY")
114 .ok()
115 .or_else(|| env::var("no_proxy").ok())
116 .map(|s| {
117 s.split([',', ' '])
118 .map(|p| p.trim().to_string())
119 .filter(|p| !p.is_empty())
120 .collect()
121 })
122 .unwrap_or_default();
123
124 Some(Self {
125 https,
126 http,
127 no_proxy,
128 })
129 }
130
131 pub fn https(url: impl Into<String>) -> Self {
133 Self {
134 https: Some(url.into()),
135 http: None,
136 no_proxy: Vec::new(),
137 }
138 }
139
140 pub fn with_http(mut self, url: impl Into<String>) -> Self {
142 self.http = Some(url.into());
143 self
144 }
145
146 pub fn with_no_proxy(mut self, patterns: impl IntoIterator<Item = String>) -> Self {
148 self.no_proxy.extend(patterns);
149 self
150 }
151
152 pub fn apply_to_builder(
154 &self,
155 mut builder: reqwest::ClientBuilder,
156 ) -> Result<reqwest::ClientBuilder, std::io::Error> {
157 if let Some(ref https_url) = self.https
158 && let Ok(proxy) = reqwest::Proxy::https(https_url)
159 {
160 builder = builder.proxy(proxy);
161 }
162 if let Some(ref http_url) = self.http
163 && let Ok(proxy) = reqwest::Proxy::http(http_url)
164 {
165 builder = builder.proxy(proxy);
166 }
167 Ok(builder)
169 }
170}
171
172impl ClientCertConfig {
173 pub fn from_env() -> Option<Self> {
175 let cert_path = env::var("CLAUDE_CODE_CLIENT_CERT").ok()?;
176 let key_path = env::var("CLAUDE_CODE_CLIENT_KEY").ok()?;
177 let key_passphrase = env::var("CLAUDE_CODE_CLIENT_KEY_PASSPHRASE").ok();
178
179 Some(Self {
180 cert_path: PathBuf::from(cert_path),
181 key_path: PathBuf::from(key_path),
182 key_passphrase,
183 })
184 }
185
186 pub fn new(cert_path: impl Into<PathBuf>, key_path: impl Into<PathBuf>) -> Self {
188 Self {
189 cert_path: cert_path.into(),
190 key_path: key_path.into(),
191 key_passphrase: None,
192 }
193 }
194
195 pub fn with_passphrase(mut self, passphrase: impl Into<String>) -> Self {
197 self.key_passphrase = Some(passphrase.into());
198 self
199 }
200
201 pub fn apply_to_builder(
203 &self,
204 builder: reqwest::ClientBuilder,
205 ) -> Result<reqwest::ClientBuilder, std::io::Error> {
206 let cert_data = std::fs::read(&self.cert_path)?;
207 let key_data = std::fs::read(&self.key_path)?;
208
209 let mut pem_data = cert_data;
210 pem_data.extend_from_slice(b"\n");
211 pem_data.extend_from_slice(&key_data);
212
213 let identity = reqwest::Identity::from_pem(&pem_data)
214 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
215
216 Ok(builder.identity(identity))
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_proxy_config_builder() {
226 let proxy = ProxyConfig::https("https://proxy.example.com:8080")
227 .with_http("http://proxy.example.com:8080")
228 .with_no_proxy(vec!["localhost".to_string(), "*.internal".to_string()]);
229
230 assert!(proxy.https.is_some());
231 assert!(proxy.http.is_some());
232 assert_eq!(proxy.no_proxy.len(), 2);
233 }
234
235 #[test]
236 fn test_network_config_builder() {
237 let config = NetworkConfig::default()
238 .with_proxy(ProxyConfig::https("https://proxy.com"))
239 .with_ca_cert("/path/to/ca.pem");
240
241 assert!(config.proxy.is_some());
242 assert!(config.ca_cert.is_some());
243 assert!(config.is_configured());
244 }
245}