ferron/modules/
static_file_serving.rs

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  // Remove the surrounding double quotes
105  let trimmed = input.trim_matches('"');
106
107  // Split the string at the hyphen and take the first part
108  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              // Check if compression is possible at all
218              let mut compression_possible = false;
219
220              if config["enableCompression"].as_bool() != Some(false) {
221                // A hard-coded list of non-compressible file extension
222                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              // Handle ETags
361              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                // Had to use "peek", since "get" would mutate the LRU cache
377                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                  // Build response
539                  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                      // Open file for reading
564                      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                      // Seek and limit the file reader
586                      file.seek(SeekFrom::Start(range_begin)).await?;
587                      let file_limited = file.take(content_length);
588
589                      // Use BufReader for better performance.
590                      let file_bufreader = BufReader::with_capacity(12800, file_limited);
591
592                      // Construct a boxed body
593                      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                  // Some web browsers have broken HTTP compression handling
626                  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                    // Checking the Accept-Encoding header naively...
647                    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                // Build response
663                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                  // Content-Length header + HTTP compression = broken HTTP responses!
702                  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                    // Open file for reading
712                    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                    // Use BufReader for better performance.
734                    let file_bufreader = BufReader::with_capacity(12800, file);
735
736                    // Construct a boxed body
737                    let boxed_body = if use_brotli {
738                      // Use Brotli compression with moderate quality (4) for good compression/speed balance
739                      // Also, set the window size and block size to optimize compression, and reduce memory usage
740                      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                      // Limit the Zstandard window size to 128K (2^17 bytes) to support many HTTP clients
751                      // Also, set the size of the initial probe table to reduce memory usage
752                      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}