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