1use crate::{AcmeError, Result};
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7use tracing::{debug, info};
8
9#[derive(Clone)]
14pub struct Http01Handler {
15 challenges: Arc<RwLock<HashMap<String, String>>>,
17}
18
19impl Http01Handler {
20 pub fn new() -> Self {
22 Self {
23 challenges: Arc::new(RwLock::new(HashMap::new())),
24 }
25 }
26
27 pub async fn add_challenge(&self, token: String, key_authorization: String) {
29 let mut challenges = self.challenges.write().await;
30 challenges.insert(token.clone(), key_authorization);
31 info!("Added HTTP-01 challenge for token: {}", token);
32 }
33
34 pub async fn get_challenge(&self, token: &str) -> Option<String> {
36 let challenges = self.challenges.read().await;
37 let response = challenges.get(token).cloned();
38 debug!(
39 "HTTP-01 challenge lookup for token {}: {:?}",
40 token,
41 response.is_some()
42 );
43 response
44 }
45
46 pub async fn remove_challenge(&self, token: &str) {
48 let mut challenges = self.challenges.write().await;
49 challenges.remove(token);
50 info!("Removed HTTP-01 challenge for token: {}", token);
51 }
52
53 pub async fn clear(&self) {
55 let mut challenges = self.challenges.write().await;
56 challenges.clear();
57 info!("Cleared all HTTP-01 challenges");
58 }
59
60 pub async fn handle_request(&self, token: &str) -> Result<String> {
65 self.get_challenge(token).await.ok_or_else(|| {
66 AcmeError::ChallengeFailed(format!("Challenge token not found: {}", token))
67 })
68 }
69
70 pub fn well_known_path(token: &str) -> String {
72 format!("/.well-known/acme-challenge/{}", token)
73 }
74
75 pub async fn verify_accessibility(&self, domain: &str, token: &str) -> Result<bool> {
77 let url = format!("http://{}/.well-known/acme-challenge/{}", domain, token);
78
79 match reqwest::get(&url).await {
80 Ok(response) => {
81 if response.status().is_success() {
82 let body = response.text().await?;
83 let expected = self.get_challenge(token).await;
84
85 if let Some(expected_value) = expected {
86 Ok(body == expected_value)
87 } else {
88 Ok(false)
89 }
90 } else {
91 Ok(false)
92 }
93 }
94 Err(_) => Ok(false),
95 }
96 }
97}
98
99impl Default for Http01Handler {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[tokio::test]
110 async fn test_http01_handler_creation() {
111 let handler = Http01Handler::new();
112 let result = handler.get_challenge("test").await;
113 assert_eq!(result, None);
114 }
115
116 #[tokio::test]
117 async fn test_add_and_get_challenge() {
118 let handler = Http01Handler::new();
119
120 handler
121 .add_challenge("test-token".to_string(), "test-key-auth".to_string())
122 .await;
123
124 let result = handler.get_challenge("test-token").await;
125 assert_eq!(result, Some("test-key-auth".to_string()));
126 }
127
128 #[tokio::test]
129 async fn test_remove_challenge() {
130 let handler = Http01Handler::new();
131
132 handler
133 .add_challenge("test-token".to_string(), "test-key-auth".to_string())
134 .await;
135
136 let result = handler.get_challenge("test-token").await;
137 assert!(result.is_some());
138
139 handler.remove_challenge("test-token").await;
140
141 let result = handler.get_challenge("test-token").await;
142 assert_eq!(result, None);
143 }
144
145 #[tokio::test]
146 async fn test_clear_all_challenges() {
147 let handler = Http01Handler::new();
148
149 handler
150 .add_challenge("token1".to_string(), "auth1".to_string())
151 .await;
152 handler
153 .add_challenge("token2".to_string(), "auth2".to_string())
154 .await;
155
156 handler.clear().await;
157
158 assert_eq!(handler.get_challenge("token1").await, None);
159 assert_eq!(handler.get_challenge("token2").await, None);
160 }
161
162 #[tokio::test]
163 async fn test_handle_request_success() {
164 let handler = Http01Handler::new();
165
166 handler
167 .add_challenge("test-token".to_string(), "test-key-auth".to_string())
168 .await;
169
170 let result = handler.handle_request("test-token").await;
171 assert!(result.is_ok());
172 assert_eq!(result.unwrap(), "test-key-auth");
173 }
174
175 #[tokio::test]
176 async fn test_handle_request_not_found() {
177 let handler = Http01Handler::new();
178
179 let result = handler.handle_request("nonexistent").await;
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_well_known_path() {
185 let path = Http01Handler::well_known_path("test-token");
186 assert_eq!(path, "/.well-known/acme-challenge/test-token");
187 }
188
189 #[tokio::test]
190 async fn test_multiple_challenges() {
191 let handler = Http01Handler::new();
192
193 for i in 0..10 {
194 handler
195 .add_challenge(format!("token-{}", i), format!("auth-{}", i))
196 .await;
197 }
198
199 for i in 0..10 {
200 let result = handler.get_challenge(&format!("token-{}", i)).await;
201 assert_eq!(result, Some(format!("auth-{}", i)));
202 }
203 }
204
205 #[tokio::test]
206 async fn test_overwrite_challenge() {
207 let handler = Http01Handler::new();
208
209 handler
210 .add_challenge("token".to_string(), "auth1".to_string())
211 .await;
212 handler
213 .add_challenge("token".to_string(), "auth2".to_string())
214 .await;
215
216 let result = handler.get_challenge("token").await;
217 assert_eq!(result, Some("auth2".to_string()));
218 }
219}