Skip to main content

gurty_cli/
handlers.rs

1use std::path::Path;
2
3pub trait FileHandler: Send + Sync {
4    fn can_handle(&self, path: &Path) -> bool;
5    fn get_content_type(&self, path: &Path) -> String;
6    fn handle_file(&self, path: &Path) -> crate::Result<Vec<u8>>;
7}
8
9pub struct DefaultFileHandler;
10
11impl FileHandler for DefaultFileHandler {
12    fn can_handle(&self, _path: &Path) -> bool {
13        true // Default
14    }
15
16    fn get_content_type(&self, path: &Path) -> String {
17        match path.extension().and_then(|ext| ext.to_str()) {
18            Some("html") | Some("htm") => "text/html".to_string(),
19            Some("css") => "text/css".to_string(),
20            Some("js") => "application/javascript".to_string(),
21            Some("json") => "application/json".to_string(),
22            Some("png") => "image/png".to_string(),
23            Some("jpg") | Some("jpeg") => "image/jpeg".to_string(),
24            Some("gif") => "image/gif".to_string(),
25            Some("svg") => "image/svg+xml".to_string(),
26            Some("ico") => "image/x-icon".to_string(),
27            Some("txt") => "text/plain".to_string(),
28            Some("xml") => "application/xml".to_string(),
29            Some("pdf") => "application/pdf".to_string(),
30            _ => "application/octet-stream".to_string(),
31        }
32    }
33
34    fn handle_file(&self, path: &Path) -> crate::Result<Vec<u8>> {
35        std::fs::read(path).map_err(crate::ServerError::from)
36    }
37}
38
39pub trait DirectoryHandler: Send + Sync {
40    fn handle_directory(&self, path: &Path, request_path: &str) -> crate::Result<String>;
41}
42
43pub struct DefaultDirectoryHandler;
44
45impl DirectoryHandler for DefaultDirectoryHandler {
46    fn handle_directory(&self, path: &Path, request_path: &str) -> crate::Result<String> {
47        let entries = std::fs::read_dir(path)?;
48        
49        let mut listing = String::from(include_str!("../templates/directory_listing_start.html"));
50
51        if request_path != "/" {
52            listing.push_str(include_str!("../templates/directory_parent_link.html"));
53        }
54
55        listing.push_str(include_str!("../templates/directory_content_start.html"));
56
57        for entry in entries.flatten() {
58            let file_name = entry.file_name();
59            let name = file_name.to_string_lossy();
60            let is_dir = entry.path().is_dir();
61            let display_name = if is_dir { 
62                format!("{}/", name) 
63            } else { 
64                name.to_string() 
65            };
66            let class = if is_dir { "dir" } else { "file" };
67            
68            listing.push_str(&format!(
69                r#"        <a href="{}" style="{}">{}</a>"#,
70                name, class, display_name
71            ));
72            listing.push('\n');
73        }
74        
75        listing.push_str(include_str!("../templates/directory_listing_end.html"));
76        Ok(listing)
77    }
78}
79
80pub fn get_404_html() -> &'static str {
81    include_str!("../templates/404.html")
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use std::path::Path;
88
89    #[test]
90    fn test_default_file_handler_can_handle_any_file() {
91        let handler = DefaultFileHandler;
92        let path = Path::new("test.txt");
93        assert!(handler.can_handle(path));
94        
95        let path = Path::new("some/random/file");
96        assert!(handler.can_handle(path));
97    }
98
99    #[test]
100    fn test_content_type_detection() {
101        let handler = DefaultFileHandler;
102        
103        assert_eq!(handler.get_content_type(Path::new("index.html")), "text/html");
104        assert_eq!(handler.get_content_type(Path::new("style.css")), "text/css");
105        assert_eq!(handler.get_content_type(Path::new("script.js")), "application/javascript");
106        assert_eq!(handler.get_content_type(Path::new("data.json")), "application/json");
107        
108        assert_eq!(handler.get_content_type(Path::new("image.png")), "image/png");
109        assert_eq!(handler.get_content_type(Path::new("photo.jpg")), "image/jpeg");
110        assert_eq!(handler.get_content_type(Path::new("photo.jpeg")), "image/jpeg");
111        assert_eq!(handler.get_content_type(Path::new("icon.ico")), "image/x-icon");
112        assert_eq!(handler.get_content_type(Path::new("vector.svg")), "image/svg+xml");
113        
114        assert_eq!(handler.get_content_type(Path::new("readme.txt")), "text/plain");
115        assert_eq!(handler.get_content_type(Path::new("data.xml")), "application/xml");
116        assert_eq!(handler.get_content_type(Path::new("document.pdf")), "application/pdf");
117        
118        assert_eq!(handler.get_content_type(Path::new("file.unknown")), "application/octet-stream");
119        
120        assert_eq!(handler.get_content_type(Path::new("noextension")), "application/octet-stream");
121    }
122
123    #[test]
124    fn test_directory_handler_generates_valid_html() {
125        use std::fs;
126        use std::env;
127        
128        let temp_dir = env::temp_dir().join("gurty_test");
129        let _ = fs::create_dir_all(&temp_dir);
130        
131        let _ = fs::write(temp_dir.join("test.txt"), "test content");
132        let _ = fs::create_dir_all(temp_dir.join("subdir"));
133        
134        let handler = DefaultDirectoryHandler;
135        let result = handler.handle_directory(&temp_dir, "/test/");
136        
137        assert!(result.is_ok());
138        let html = result.unwrap();
139        
140        assert!(html.contains("<!DOCTYPE html>"));
141        assert!(html.contains("<title>Directory Listing</title>"));
142        assert!(html.contains("← Parent Directory"));
143        assert!(html.contains("test.txt"));
144        assert!(html.contains("subdir/"));
145        
146        let _ = fs::remove_dir_all(&temp_dir);
147    }
148
149    #[test]
150    fn test_directory_handler_root_path() {
151        use std::fs;
152        use std::env;
153        
154        let temp_dir = env::temp_dir().join("gurty_test_root");
155        let _ = fs::create_dir_all(&temp_dir);
156        
157        let handler = DefaultDirectoryHandler;
158        let result = handler.handle_directory(&temp_dir, "/");
159        
160        assert!(result.is_ok());
161        let html = result.unwrap();
162        
163        assert!(!html.contains("← Parent Directory"));
164        
165        let _ = fs::remove_dir_all(&temp_dir);
166    }
167
168    #[test]
169    fn test_get_404_html_content() {
170        let html = get_404_html();
171        
172        assert!(html.contains("<!DOCTYPE html>"));
173        assert!(html.contains("404 Page Not Found"));
174        assert!(html.contains("The requested path was not found"));
175        assert!(html.contains("Back to home"));
176    }
177
178    #[test]
179    fn test_directory_handler_with_empty_directory() {
180        use std::fs;
181        use std::env;
182        
183        let temp_dir = env::temp_dir().join("gurty_test_empty");
184        let _ = fs::create_dir_all(&temp_dir);
185        
186        let handler = DefaultDirectoryHandler;
187        let result = handler.handle_directory(&temp_dir, "/empty/");
188        
189        assert!(result.is_ok());
190        let html = result.unwrap();
191        
192        assert!(html.contains("<!DOCTYPE html>"));
193        assert!(html.contains("Directory Listing"));
194        
195        let _ = fs::remove_dir_all(&temp_dir);
196    }
197}