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 pub value: Option<String>,
24}
25
26#[derive(Debug, Clone, Default)]
28pub struct NetworkConfig {
29 pub allow: Vec<String>,
32}
33
34impl ProxyConfig {
35 pub fn is_domain_allowed(&self, domain: &str) -> bool {
38 if self.network.allow.is_empty() {
39 return true;
40 }
41 self.network
42 .allow
43 .iter()
44 .any(|pattern| domain_matches(pattern, domain))
45 }
46
47 pub fn secrets_for_domain(
49 &self,
50 domain: &str,
51 placeholders: &HashMap<String, String>,
52 ) -> Vec<(String, String)> {
53 let mut substitutions = Vec::new();
54 for (name, secret) in &self.secrets {
55 if secret
56 .hosts
57 .iter()
58 .any(|pattern| domain_matches(pattern, domain))
59 {
60 if let Some(placeholder) = placeholders.get(name) {
61 let real_value = secret
62 .value
63 .clone()
64 .or_else(|| std::env::var(&secret.from).ok());
65 if let Some(real_value) = real_value {
66 substitutions.push((placeholder.clone(), real_value));
67 }
68 }
69 }
70 }
71 substitutions
72 }
73}
74
75fn domain_matches(pattern: &str, domain: &str) -> bool {
79 if let Some(suffix) = pattern.strip_prefix("*.") {
80 domain.ends_with(suffix) && domain.len() > suffix.len() && domain.as_bytes()[domain.len() - suffix.len() - 1] == b'.'
81 } else {
82 pattern == domain
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_domain_matching() {
92 assert!(domain_matches("example.com", "example.com"));
93 assert!(!domain_matches("example.com", "api.example.com"));
94 assert!(domain_matches("*.example.com", "api.example.com"));
95 assert!(domain_matches("*.example.com", "deep.api.example.com"));
96 assert!(!domain_matches("*.example.com", "example.com"));
97 assert!(!domain_matches("*.example.com", "notexample.com"));
98 }
99}