1use std::collections::HashMap;
2
3#[derive(Debug, Clone, Default)]
5pub struct ProxyConfig {
6 pub secrets: HashMap<String, SecretConfig>,
10 pub network: NetworkConfig,
12}
13
14#[derive(Debug, Clone)]
16pub struct SecretConfig {
17 pub from: String,
19 pub hosts: Vec<String>,
22}
23
24#[derive(Debug, Clone, Default)]
26pub struct NetworkConfig {
27 pub allow: Vec<String>,
30}
31
32impl ProxyConfig {
33 pub fn is_domain_allowed(&self, domain: &str) -> bool {
36 if self.network.allow.is_empty() {
37 return true;
38 }
39 self.network
40 .allow
41 .iter()
42 .any(|pattern| domain_matches(pattern, domain))
43 }
44
45 pub fn secrets_for_domain(
47 &self,
48 domain: &str,
49 placeholders: &HashMap<String, String>,
50 ) -> Vec<(String, String)> {
51 let mut result = Vec::new();
52 for (name, secret) in &self.secrets {
53 if secret
54 .hosts
55 .iter()
56 .any(|pattern| domain_matches(pattern, domain))
57 {
58 if let Some(placeholder) = placeholders.get(name) {
59 if let Ok(real_value) = std::env::var(&secret.from) {
60 result.push((placeholder.clone(), real_value));
61 }
62 }
63 }
64 }
65 result
66 }
67}
68
69fn domain_matches(pattern: &str, domain: &str) -> bool {
73 if let Some(suffix) = pattern.strip_prefix("*.") {
74 domain.ends_with(suffix) && domain.len() > suffix.len() && domain.as_bytes()[domain.len() - suffix.len() - 1] == b'.'
75 } else {
76 pattern == domain
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn test_domain_matching() {
86 assert!(domain_matches("example.com", "example.com"));
87 assert!(!domain_matches("example.com", "api.example.com"));
88 assert!(domain_matches("*.example.com", "api.example.com"));
89 assert!(domain_matches("*.example.com", "deep.api.example.com"));
90 assert!(!domain_matches("*.example.com", "example.com"));
91 assert!(!domain_matches("*.example.com", "notexample.com"));
92 }
93}