1use std::error::Error;
2use std::fmt::Write;
3use std::io::SeekFrom;
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::sync::Arc;
7use std::time::Duration;
8
9use crate::ferron_common::{
10 ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
11 ServerModuleHandlers, SocketData,
12};
13use crate::ferron_common::{HyperUpgraded, WithRuntime};
14use async_compression::tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder};
15use async_compression::zstd::CParameter;
16use async_compression::Level;
17use async_trait::async_trait;
18use chrono::offset::Local;
19use chrono::DateTime;
20use futures_util::TryStreamExt;
21use hashlink::LruCache;
22use http::HeaderValue;
23use http_body_util::{BodyExt, Empty, Full, StreamBody};
24use hyper::body::Bytes;
25use hyper::{body::Frame, Response, StatusCode};
26use hyper::{header, HeaderMap, Method};
27use hyper_tungstenite::HyperWebsocket;
28use sha2::{Digest, Sha256};
29use tokio::fs;
30use tokio::io::{AsyncReadExt, AsyncSeekExt, BufReader};
31use tokio::runtime::Handle;
32use tokio::sync::RwLock;
33use tokio_util::io::ReaderStream;
34
35use crate::ferron_util::generate_directory_listing::generate_directory_listing;
36use crate::ferron_util::ttl_cache::TtlCache;
37
38pub fn server_module_init(
39) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
40 let pathbuf_cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
41 let etag_cache = Arc::new(RwLock::new(LruCache::new(1000)));
42 Ok(Box::new(StaticFileServingModule::new(
43 pathbuf_cache,
44 etag_cache,
45 )))
46}
47
48struct StaticFileServingModule {
49 pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
50 etag_cache: Arc<RwLock<LruCache<String, String>>>,
51}
52
53impl StaticFileServingModule {
54 fn new(
55 pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
56 etag_cache: Arc<RwLock<LruCache<String, String>>>,
57 ) -> Self {
58 Self {
59 pathbuf_cache,
60 etag_cache,
61 }
62 }
63}
64
65impl ServerModule for StaticFileServingModule {
66 fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
67 Box::new(StaticFileServingModuleHandlers {
68 pathbuf_cache: self.pathbuf_cache.clone(),
69 etag_cache: self.etag_cache.clone(),
70 handle,
71 })
72 }
73}
74struct StaticFileServingModuleHandlers {
75 pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
76 etag_cache: Arc<RwLock<LruCache<String, String>>>,
77 handle: Handle,
78}
79
80fn parse_range_header(range_str: &str, default_end: u64) -> Option<(u64, u64)> {
81 if let Some(range_part) = range_str.strip_prefix("bytes=") {
82 let parts: Vec<&str> = range_part.split('-').collect();
83 if parts.len() == 2 {
84 if parts[0].is_empty() {
85 if let Ok(end) = u64::from_str(parts[1]) {
86 return Some((default_end - end + 1, default_end));
87 }
88 } else if parts[1].is_empty() {
89 if let Ok(start) = u64::from_str(parts[0]) {
90 return Some((start, default_end));
91 }
92 } else if !parts[0].is_empty() && !parts[1].is_empty() {
93 if let (Ok(start), Ok(end)) = (u64::from_str(parts[0]), u64::from_str(parts[1])) {
94 return Some((start, end));
95 }
96 }
97 }
98 }
99 None
100}
101
102fn extract_etag_inner(input: &str) -> Option<String> {
103 let trimmed = input.trim_matches('"');
105
106 let parts: Vec<&str> = trimmed.split('-').collect();
108 if parts.is_empty() {
109 None
110 } else {
111 Some(parts[0].to_string())
112 }
113}
114
115#[async_trait]
116impl ServerModuleHandlers for StaticFileServingModuleHandlers {
117 async fn request_handler(
118 &mut self,
119 request: RequestData,
120 config: &ServerConfig,
121 _socket_data: &SocketData,
122 _error_logger: &ErrorLogger,
123 ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
124 WithRuntime::new(self.handle.clone(), async move {
125 if let Some(wwwroot) = config["wwwroot"].as_str() {
126 let hyper_request = request.get_hyper_request();
127 let request_path = hyper_request.uri().path();
128 let mut request_path_bytes = request_path.bytes();
129 if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
130 return Ok(
131 ResponseData::builder(request)
132 .status(StatusCode::BAD_REQUEST)
133 .build(),
134 );
135 }
136
137 let original_request_path = request
138 .get_original_url()
139 .map_or(request_path, |u| u.path());
140
141 let cache_key = format!(
142 "{}{}{}",
143 match config["ip"].as_str() {
144 Some(ip) => format!("{}-", ip),
145 None => String::from(""),
146 },
147 match config["domain"].as_str() {
148 Some(domain) => format!("{}-", domain),
149 None => String::from(""),
150 },
151 request_path
152 );
153
154 let rwlock_read = self.pathbuf_cache.read().await;
155 let joined_pathbuf_option = rwlock_read.get(&cache_key);
156 drop(rwlock_read);
157
158 let joined_pathbuf_cached = joined_pathbuf_option.is_some();
159 let mut joined_pathbuf = match joined_pathbuf_option {
160 Some(joined_pathbuf) => joined_pathbuf,
161 None => {
162 let path = Path::new(wwwroot);
163 let mut relative_path = &request_path[1..];
164 while relative_path.as_bytes().first().copied() == Some(b'/') {
165 relative_path = &relative_path[1..];
166 }
167
168 let decoded_relative_path = match urlencoding::decode(relative_path) {
169 Ok(path) => path.to_string(),
170 Err(_) => {
171 return Ok(
172 ResponseData::builder(request)
173 .status(StatusCode::BAD_REQUEST)
174 .build(),
175 );
176 }
177 };
178
179 path.join(decoded_relative_path)
180 }
181 };
182
183 match fs::metadata(&joined_pathbuf).await {
184 Ok(mut metadata) => {
185 if !joined_pathbuf_cached {
186 if metadata.is_dir() {
187 let indexes = vec!["index.html", "index.htm", "index.xhtml"];
188 for index in indexes {
189 let temp_joined_pathbuf = joined_pathbuf.join(index);
190 match fs::metadata(&temp_joined_pathbuf).await {
191 Ok(temp_metadata) => {
192 if temp_metadata.is_file() {
193 metadata = temp_metadata;
194 joined_pathbuf = temp_joined_pathbuf;
195 break;
196 }
197 }
198 Err(err) => match err.kind() {
199 tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
200 continue;
201 }
202 tokio::io::ErrorKind::PermissionDenied => {
203 return Ok(
204 ResponseData::builder(request)
205 .status(StatusCode::FORBIDDEN)
206 .build(),
207 );
208 }
209 _ => Err(err)?,
210 },
211 };
212 }
213 }
214 let mut rwlock_write = self.pathbuf_cache.write().await;
215 rwlock_write.cleanup();
216 rwlock_write.insert(cache_key, joined_pathbuf.clone());
217 drop(rwlock_write);
218 }
219
220 if metadata.is_file() {
221 let mut compression_possible = false;
223
224 if config["enableCompression"].as_bool() != Some(false) {
225 let non_compressible_file_extensions = vec![
227 "7z",
228 "air",
229 "amlx",
230 "apk",
231 "apng",
232 "appinstaller",
233 "appx",
234 "appxbundle",
235 "arj",
236 "au",
237 "avif",
238 "bdoc",
239 "boz",
240 "br",
241 "bz",
242 "bz2",
243 "caf",
244 "class",
245 "doc",
246 "docx",
247 "dot",
248 "dvi",
249 "ear",
250 "epub",
251 "flv",
252 "gdoc",
253 "gif",
254 "gsheet",
255 "gslides",
256 "gz",
257 "iges",
258 "igs",
259 "jar",
260 "jnlp",
261 "jp2",
262 "jpe",
263 "jpeg",
264 "jpf",
265 "jpg",
266 "jpg2",
267 "jpgm",
268 "jpm",
269 "jpx",
270 "kmz",
271 "latex",
272 "m1v",
273 "m2a",
274 "m2v",
275 "m3a",
276 "m4a",
277 "mesh",
278 "mk3d",
279 "mks",
280 "mkv",
281 "mov",
282 "mp2",
283 "mp2a",
284 "mp3",
285 "mp4",
286 "mp4a",
287 "mp4v",
288 "mpe",
289 "mpeg",
290 "mpg",
291 "mpg4",
292 "mpga",
293 "msg",
294 "msh",
295 "msix",
296 "msixbundle",
297 "odg",
298 "odp",
299 "ods",
300 "odt",
301 "oga",
302 "ogg",
303 "ogv",
304 "ogx",
305 "opus",
306 "p12",
307 "pdf",
308 "pfx",
309 "pgp",
310 "pkpass",
311 "png",
312 "pot",
313 "pps",
314 "ppt",
315 "pptx",
316 "qt",
317 "ser",
318 "silo",
319 "sit",
320 "snd",
321 "spx",
322 "stpxz",
323 "stpz",
324 "swf",
325 "tif",
326 "tiff",
327 "ubj",
328 "usdz",
329 "vbox-extpack",
330 "vrml",
331 "war",
332 "wav",
333 "weba",
334 "webm",
335 "wmv",
336 "wrl",
337 "x3dbz",
338 "x3dvz",
339 "xla",
340 "xlc",
341 "xlm",
342 "xls",
343 "xlsx",
344 "xlt",
345 "xlw",
346 "xpi",
347 "xps",
348 "zip",
349 "zst",
350 ];
351 let file_extension = joined_pathbuf
352 .extension()
353 .map_or_else(|| "".to_string(), |ext| ext.to_string_lossy().to_string());
354 let file_extension_compressible =
355 !non_compressible_file_extensions.contains(&(&file_extension as &str));
356
357 if metadata.len() > 256 && file_extension_compressible {
358 compression_possible = true;
359 }
360 }
361
362 let vary;
363
364 let mut etag_option = None;
366 if config["enableETag"].as_bool() != Some(false) {
367 let etag_cache_key = format!(
368 "{}-{}-{}",
369 joined_pathbuf.to_string_lossy(),
370 metadata.len(),
371 match metadata.modified() {
372 Ok(mtime) => {
373 let datetime: DateTime<Local> = mtime.into();
374 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
375 }
376 Err(_) => String::from(""),
377 }
378 );
379 let rwlock_read = self.etag_cache.read().await;
380 let etag_locked_option = rwlock_read.peek(&etag_cache_key).cloned();
382 drop(rwlock_read);
383 let etag = match etag_locked_option {
384 Some(etag) => etag,
385 None => {
386 let etag_cache_key_clone = etag_cache_key.clone();
387 let etag = tokio::task::spawn_blocking(move || {
388 let mut hasher = Sha256::new();
389 hasher.update(etag_cache_key_clone);
390 hasher
391 .finalize()
392 .iter()
393 .fold(String::new(), |mut output, b| {
394 let _ = write!(output, "{b:02x}");
395 output
396 })
397 })
398 .await?;
399
400 let mut rwlock_write = self.etag_cache.write().await;
401 rwlock_write.insert(etag_cache_key, etag.clone());
402 drop(rwlock_write);
403
404 etag
405 }
406 };
407
408 vary = if compression_possible {
409 "Accept-Encoding, If-Match, If-None-Match, Range"
410 } else {
411 "If-Match, If-None-Match, Range"
412 };
413
414 if let Some(if_none_match_value) =
415 hyper_request.headers().get(header::IF_NONE_MATCH)
416 {
417 match if_none_match_value.to_str() {
418 Ok(if_none_match) => {
419 if let Some(etag_extracted) = extract_etag_inner(if_none_match) {
420 if etag_extracted == etag {
421 let etag_original = if_none_match.to_string();
422 return Ok(
423 ResponseData::builder(request)
424 .response(
425 Response::builder()
426 .status(StatusCode::NOT_MODIFIED)
427 .header(header::ETAG, etag_original)
428 .header(header::VARY, vary)
429 .body(Empty::new().map_err(|e| match e {}).boxed())?,
430 )
431 .build(),
432 );
433 }
434 }
435 }
436 Err(_) => {
437 let mut header_map = HeaderMap::new();
438 if let Ok(vary) = HeaderValue::from_str(vary) {
439 header_map.insert(header::VARY, vary);
440 }
441 return Ok(
442 ResponseData::builder(request)
443 .status(StatusCode::BAD_REQUEST)
444 .headers(header_map)
445 .build(),
446 );
447 }
448 }
449 }
450
451 if let Some(if_match_value) = hyper_request.headers().get(header::IF_MATCH) {
452 match if_match_value.to_str() {
453 Ok(if_match) => {
454 if if_match != "*" {
455 if let Some(etag_extracted) = extract_etag_inner(if_match) {
456 if etag_extracted != etag {
457 let mut header_map = HeaderMap::new();
458 header_map.insert(header::ETAG, if_match_value.clone());
459 if let Ok(vary) = HeaderValue::from_str(vary) {
460 header_map.insert(header::VARY, vary);
461 }
462 return Ok(
463 ResponseData::builder(request)
464 .status(StatusCode::PRECONDITION_FAILED)
465 .headers(header_map)
466 .build(),
467 );
468 }
469 }
470 }
471 }
472 Err(_) => {
473 let mut header_map = HeaderMap::new();
474 if let Ok(vary) = HeaderValue::from_str(vary) {
475 header_map.insert(header::VARY, vary);
476 }
477 return Ok(
478 ResponseData::builder(request)
479 .status(StatusCode::BAD_REQUEST)
480 .headers(header_map)
481 .build(),
482 );
483 }
484 }
485 }
486 etag_option = Some(etag);
487 } else {
488 vary = if compression_possible {
489 "Accept-Encoding, Range"
490 } else {
491 "Range"
492 };
493 }
494
495 let content_type_option = new_mime_guess::from_path(&joined_pathbuf)
496 .first()
497 .map(|mime_type| mime_type.to_string());
498
499 let range_header = match hyper_request.headers().get(header::RANGE) {
500 Some(value) => match value.to_str() {
501 Ok(value) => Some(value),
502 Err(_) => {
503 let mut header_map = HeaderMap::new();
504 if let Ok(vary) = HeaderValue::from_str(vary) {
505 header_map.insert(header::VARY, vary);
506 }
507 return Ok(
508 ResponseData::builder(request)
509 .status(StatusCode::BAD_REQUEST)
510 .headers(header_map)
511 .build(),
512 );
513 }
514 },
515 None => None,
516 };
517
518 if let Some(range_header) = range_header {
519 let file_length = metadata.len();
520 if file_length == 0 {
521 let mut header_map = HeaderMap::new();
522 if let Ok(vary) = HeaderValue::from_str(vary) {
523 header_map.insert(header::VARY, vary);
524 }
525 return Ok(
526 ResponseData::builder(request)
527 .status(StatusCode::RANGE_NOT_SATISFIABLE)
528 .headers(header_map)
529 .build(),
530 );
531 }
532 if let Some((range_begin, range_end)) =
533 parse_range_header(range_header, file_length - 1)
534 {
535 if range_end > file_length - 1
536 || range_begin > file_length - 1
537 || range_begin > range_end
538 {
539 let mut header_map = HeaderMap::new();
540 if let Ok(vary) = HeaderValue::from_str(vary) {
541 header_map.insert(header::VARY, vary);
542 }
543 return Ok(
544 ResponseData::builder(request)
545 .status(StatusCode::RANGE_NOT_SATISFIABLE)
546 .headers(header_map)
547 .build(),
548 );
549 }
550
551 let request_method = hyper_request.method();
552 let content_length = range_end - range_begin + 1;
553
554 let mut response_builder = Response::builder()
556 .status(StatusCode::PARTIAL_CONTENT)
557 .header(header::CONTENT_LENGTH, content_length)
558 .header(
559 header::CONTENT_RANGE,
560 format!("bytes {}-{}/{}", range_begin, range_end, file_length),
561 );
562
563 if let Some(etag) = etag_option {
564 response_builder = response_builder.header(header::ETAG, etag);
565 }
566
567 if let Some(content_type) = content_type_option {
568 response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
569 }
570
571 response_builder = response_builder.header(header::VARY, vary);
572
573 let response = match request_method {
574 &Method::HEAD => {
575 response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
576 }
577 _ => {
578 let mut file = match fs::File::open(joined_pathbuf).await {
580 Ok(file) => file,
581 Err(err) => match err.kind() {
582 tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
583 return Ok(
584 ResponseData::builder(request)
585 .status(StatusCode::NOT_FOUND)
586 .build(),
587 );
588 }
589 tokio::io::ErrorKind::PermissionDenied => {
590 return Ok(
591 ResponseData::builder(request)
592 .status(StatusCode::FORBIDDEN)
593 .build(),
594 );
595 }
596 _ => Err(err)?,
597 },
598 };
599
600 file.seek(SeekFrom::Start(range_begin)).await?;
602 let file_limited = file.take(content_length);
603
604 let file_bufreader = BufReader::with_capacity(12800, file_limited);
606
607 let reader_stream = ReaderStream::new(file_bufreader);
609 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
610 let boxed_body = stream_body.boxed();
611
612 response_builder.body(boxed_body)?
613 }
614 };
615
616 return Ok(ResponseData::builder(request).response(response).build());
617 } else {
618 let mut header_map = HeaderMap::new();
619 if let Ok(vary) = HeaderValue::from_str(vary) {
620 header_map.insert(header::VARY, vary);
621 }
622
623 return Ok(
624 ResponseData::builder(request)
625 .status(StatusCode::RANGE_NOT_SATISFIABLE)
626 .headers(header_map)
627 .build(),
628 );
629 }
630 } else {
631 let mut use_gzip = false;
632 let mut use_deflate = false;
633 let mut use_brotli = false;
634 let mut use_zstd = false;
635
636 if compression_possible {
637 let user_agent = match hyper_request.headers().get(header::USER_AGENT) {
638 Some(user_agent_value) => user_agent_value.to_str().unwrap_or_default(),
639 None => "",
640 };
641
642 let is_netscape_4_broken_html_compression = user_agent.starts_with("Mozilla/4.");
644 let is_netscape_4_broken_compression = match user_agent.strip_prefix("Mozilla/4.")
645 {
646 Some(stripped_user_agent) => matches!(
647 stripped_user_agent.chars().nth(0),
648 Some('6') | Some('7') | Some('8')
649 ),
650 None => false,
651 };
652 let is_w3m_broken_html_compression = user_agent.starts_with("w3m/");
653 if !(content_type_option == Some("text/html".to_string())
654 && (is_netscape_4_broken_html_compression || is_w3m_broken_html_compression))
655 && !is_netscape_4_broken_compression
656 {
657 let accept_encoding = match hyper_request.headers().get(header::ACCEPT_ENCODING)
658 {
659 Some(header_value) => header_value.to_str().unwrap_or_default(),
660 None => "",
661 };
662
663 if accept_encoding.contains("br") {
665 use_brotli = true;
666 } else if accept_encoding.contains("zstd") {
667 use_zstd = true;
668 } else if accept_encoding.contains("deflate") {
669 use_deflate = true;
670 } else if accept_encoding.contains("gzip") {
671 use_gzip = true;
672 }
673 }
674 }
675
676 let request_method = hyper_request.method();
677 let content_length = metadata.len();
678
679 let mut response_builder = Response::builder()
681 .status(StatusCode::OK)
682 .header(header::ACCEPT_RANGES, "bytes");
683
684 if let Some(etag) = etag_option {
685 if use_brotli {
686 response_builder =
687 response_builder.header(header::ETAG, format!("\"{}-br\"", etag));
688 } else if use_zstd {
689 response_builder =
690 response_builder.header(header::ETAG, format!("\"{}-zstd\"", etag));
691 } else if use_deflate {
692 response_builder =
693 response_builder.header(header::ETAG, format!("\"{}-deflate\"", etag));
694 } else if use_gzip {
695 response_builder =
696 response_builder.header(header::ETAG, format!("\"{}-gzip\"", etag));
697 } else {
698 response_builder =
699 response_builder.header(header::ETAG, format!("\"{}\"", etag));
700 }
701 }
702
703 response_builder = response_builder.header(header::VARY, vary);
704
705 if let Some(content_type) = content_type_option {
706 response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
707 }
708
709 if use_brotli {
710 response_builder = response_builder.header(header::CONTENT_ENCODING, "br");
711 } else if use_zstd {
712 response_builder = response_builder.header(header::CONTENT_ENCODING, "zstd");
713 } else if use_deflate {
714 response_builder = response_builder.header(header::CONTENT_ENCODING, "deflate");
715 } else if use_gzip {
716 response_builder = response_builder.header(header::CONTENT_ENCODING, "gzip");
717 } else {
718 response_builder =
720 response_builder.header(header::CONTENT_LENGTH, content_length);
721 }
722
723 let response = match request_method {
724 &Method::HEAD => {
725 response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
726 }
727 _ => {
728 let file = match fs::File::open(joined_pathbuf).await {
730 Ok(file) => file,
731 Err(err) => match err.kind() {
732 tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
733 return Ok(
734 ResponseData::builder(request)
735 .status(StatusCode::NOT_FOUND)
736 .build(),
737 );
738 }
739 tokio::io::ErrorKind::PermissionDenied => {
740 return Ok(
741 ResponseData::builder(request)
742 .status(StatusCode::FORBIDDEN)
743 .build(),
744 );
745 }
746 _ => Err(err)?,
747 },
748 };
749
750 let file_bufreader = BufReader::with_capacity(12800, file);
752
753 let boxed_body = if use_brotli {
755 let reader_stream = ReaderStream::new(BrotliEncoder::with_quality(
757 file_bufreader,
758 Level::Precise(4),
759 ));
760 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
761 stream_body.boxed()
762 } else if use_zstd {
763 let reader_stream = ReaderStream::new(ZstdEncoder::with_quality_and_params(
765 file_bufreader,
766 Level::Default,
767 &[CParameter::window_log(17)],
768 ));
769 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
770 stream_body.boxed()
771 } else if use_deflate {
772 let reader_stream = ReaderStream::new(DeflateEncoder::new(file_bufreader));
773 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
774 stream_body.boxed()
775 } else if use_gzip {
776 let reader_stream = ReaderStream::new(GzipEncoder::new(file_bufreader));
777 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
778 stream_body.boxed()
779 } else {
780 let reader_stream = ReaderStream::new(file_bufreader);
781 let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
782 stream_body.boxed()
783 };
784
785 response_builder.body(boxed_body)?
786 }
787 };
788
789 return Ok(ResponseData::builder(request).response(response).build());
790 }
791 } else if metadata.is_dir() {
792 if config["enableDirectoryListing"].as_bool() == Some(true) {
793 let joined_maindesc_pathbuf = joined_pathbuf.join(".maindesc");
794 let directory = match fs::read_dir(joined_pathbuf).await {
795 Ok(directory) => directory,
796 Err(err) => match err.kind() {
797 tokio::io::ErrorKind::NotFound => {
798 return Ok(
799 ResponseData::builder(request)
800 .status(StatusCode::NOT_FOUND)
801 .build(),
802 );
803 }
804 tokio::io::ErrorKind::PermissionDenied => {
805 return Ok(
806 ResponseData::builder(request)
807 .status(StatusCode::FORBIDDEN)
808 .build(),
809 );
810 }
811 _ => Err(err)?,
812 },
813 };
814
815 let description = (fs::read_to_string(joined_maindesc_pathbuf).await).ok();
816
817 let directory_listing_html =
818 generate_directory_listing(directory, original_request_path, description).await?;
819 let content_length: Option<u64> = directory_listing_html.len().try_into().ok();
820
821 let mut response_builder = Response::builder().status(StatusCode::OK);
822
823 if let Some(content_length) = content_length {
824 response_builder = response_builder.header(header::CONTENT_LENGTH, content_length)
825 }
826 response_builder = response_builder.header(header::CONTENT_TYPE, "text/html");
827
828 let response = response_builder.body(
829 Full::new(Bytes::from(directory_listing_html))
830 .map_err(|e| match e {})
831 .boxed(),
832 )?;
833
834 return Ok(ResponseData::builder(request).response(response).build());
835 } else {
836 return Ok(
837 ResponseData::builder(request)
838 .status(StatusCode::FORBIDDEN)
839 .build(),
840 );
841 }
842 } else {
843 return Ok(
844 ResponseData::builder(request)
845 .status(StatusCode::NOT_IMPLEMENTED)
846 .build(),
847 );
848 }
849 }
850 Err(err) => match err.kind() {
851 tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
852 return Ok(
853 ResponseData::builder(request)
854 .status(StatusCode::NOT_FOUND)
855 .build(),
856 );
857 }
858 tokio::io::ErrorKind::PermissionDenied => {
859 return Ok(
860 ResponseData::builder(request)
861 .status(StatusCode::FORBIDDEN)
862 .build(),
863 );
864 }
865 _ => Err(err)?,
866 },
867 }
868 }
869
870 Ok(ResponseData::builder(request).build())
871 })
872 .await
873 }
874
875 async fn proxy_request_handler(
876 &mut self,
877 request: RequestData,
878 _config: &ServerConfig,
879 _socket_data: &SocketData,
880 _error_logger: &ErrorLogger,
881 ) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
882 Ok(ResponseData::builder(request).build())
883 }
884
885 async fn response_modifying_handler(
886 &mut self,
887 response: HyperResponse,
888 ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
889 Ok(response)
890 }
891
892 async fn proxy_response_modifying_handler(
893 &mut self,
894 response: HyperResponse,
895 ) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
896 Ok(response)
897 }
898
899 async fn connect_proxy_request_handler(
900 &mut self,
901 _upgraded_request: HyperUpgraded,
902 _connect_address: &str,
903 _config: &ServerConfig,
904 _socket_data: &SocketData,
905 _error_logger: &ErrorLogger,
906 ) -> Result<(), Box<dyn Error + Send + Sync>> {
907 Ok(())
908 }
909
910 fn does_connect_proxy_requests(&mut self) -> bool {
911 false
912 }
913
914 async fn websocket_request_handler(
915 &mut self,
916 _websocket: HyperWebsocket,
917 _uri: &hyper::Uri,
918 _config: &ServerConfig,
919 _socket_data: &SocketData,
920 _error_logger: &ErrorLogger,
921 ) -> Result<(), Box<dyn Error + Send + Sync>> {
922 Ok(())
923 }
924
925 fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
926 false
927 }
928}