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::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  // Remove the surrounding double quotes
104  let trimmed = input.trim_matches('"');
105
106  // Split the string at the hyphen and take the first part
107  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              // Check if compression is possible at all
222              let mut compression_possible = false;
223
224              if config["enableCompression"].as_bool() != Some(false) {
225                // A hard-coded list of non-compressible file extension
226                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              // Handle ETags
365              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                // Had to use "peek", since "get" would mutate the LRU cache
381                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                  // Build response
555                  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                      // Open file for reading
579                      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                      // Seek and limit the file reader
601                      file.seek(SeekFrom::Start(range_begin)).await?;
602                      let file_limited = file.take(content_length);
603
604                      // Use BufReader for better performance.
605                      let file_bufreader = BufReader::with_capacity(12800, file_limited);
606
607                      // Construct a boxed body
608                      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                  // Some web browsers have broken HTTP compression handling
643                  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                    // Checking the Accept-Encoding header naively...
664                    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                // Build response
680                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                  // Content-Length header + HTTP compression = broken HTTP responses!
719                  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                    // Open file for reading
729                    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                    // Use BufReader for better performance.
751                    let file_bufreader = BufReader::with_capacity(12800, file);
752
753                    // Construct a boxed body
754                    let boxed_body = if use_brotli {
755                      // Brotli compression quality of 4
756                      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                      // Limit the Zstandard window size to 128K (2^17 bytes) to support many HTTP clients
764                      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}