Skip to main content

tower_http/services/fs/serve_dir/
mod.rs

1use self::future::ResponseFuture;
2use crate::{
3    body::UnsyncBoxBody,
4    content_encoding::{encodings, SupportedEncodings},
5    set_status::SetStatus,
6};
7use bytes::Bytes;
8use futures_util::FutureExt;
9use http::{header, HeaderValue, Method, Request, Response, StatusCode};
10use http_body_util::{BodyExt, Empty};
11use percent_encoding::percent_decode;
12use std::{
13    convert::Infallible,
14    io,
15    path::{Component, Path, PathBuf},
16    task::{Context, Poll},
17};
18use tower_service::Service;
19
20mod backend;
21pub(crate) mod future;
22mod headers;
23mod open_file;
24
25#[cfg(test)]
26mod tests;
27
28pub use self::backend::{Backend, File, Metadata, TokioBackend, TokioFile};
29
30// default capacity 64KiB
31const DEFAULT_CAPACITY: usize = 65536;
32
33/// Service that serves files from a given directory and all its sub directories.
34///
35/// The `Content-Type` will be guessed from the file extension.
36///
37/// An empty response with status `404 Not Found` will be returned if:
38///
39/// - The file doesn't exist
40/// - Any segment of the path contains `..`
41/// - Any segment of the path contains a backslash
42/// - On unix, any segment of the path referenced as directory is actually an
43///   existing file (`/file.html/something`)
44/// - We don't have necessary permissions to read the file
45///
46/// # Example
47///
48/// ```
49/// use tower_http::services::ServeDir;
50///
51/// // This will serve files in the "assets" directory and
52/// // its subdirectories
53/// let service = ServeDir::new("assets");
54/// ```
55#[derive(Clone, Debug)]
56pub struct ServeDir<F = DefaultServeDirFallback, B = TokioBackend> {
57    base: PathBuf,
58    redirect_path_prefix: String,
59    buf_chunk_size: usize,
60    ignore_multi_range_requests: bool,
61    precompressed_variants: Option<PrecompressedVariants>,
62    // This is used to specialize implementation for
63    // single files
64    variant: ServeVariant,
65    fallback: Option<F>,
66    call_fallback_on_method_not_allowed: bool,
67    backend: B,
68}
69
70impl ServeDir<DefaultServeDirFallback> {
71    /// Create a new [`ServeDir`].
72    pub fn new<P>(path: P) -> Self
73    where
74        P: AsRef<Path>,
75    {
76        let mut base = PathBuf::from(".");
77        base.push(path.as_ref());
78
79        Self {
80            base,
81            redirect_path_prefix: String::new(),
82            buf_chunk_size: DEFAULT_CAPACITY,
83            ignore_multi_range_requests: false,
84            precompressed_variants: None,
85            variant: ServeVariant::Directory {
86                append_index_html_on_directories: true,
87                redirect_to_trailing_slash: true,
88                html_as_default_extension: false,
89            },
90            fallback: None,
91            call_fallback_on_method_not_allowed: false,
92            backend: TokioBackend,
93        }
94    }
95
96    pub(crate) fn new_single_file<P>(path: P, mime: HeaderValue) -> Self
97    where
98        P: AsRef<Path>,
99    {
100        Self {
101            base: path.as_ref().to_owned(),
102            redirect_path_prefix: String::new(),
103            buf_chunk_size: DEFAULT_CAPACITY,
104            ignore_multi_range_requests: false,
105            precompressed_variants: None,
106            variant: ServeVariant::SingleFile { mime },
107            fallback: None,
108            call_fallback_on_method_not_allowed: false,
109            backend: TokioBackend,
110        }
111    }
112}
113
114impl<B: Backend> ServeDir<DefaultServeDirFallback, B> {
115    /// Create a new [`ServeDir`] with a custom [`Backend`].
116    ///
117    /// This allows serving files from sources other than the local filesystem.
118    pub fn with_backend<P>(path: P, backend: B) -> Self
119    where
120        P: AsRef<Path>,
121    {
122        let mut base = PathBuf::from(".");
123        base.push(path.as_ref());
124
125        ServeDir {
126            base,
127            buf_chunk_size: DEFAULT_CAPACITY,
128            ignore_multi_range_requests: false,
129            precompressed_variants: None,
130            variant: ServeVariant::Directory {
131                append_index_html_on_directories: true,
132                redirect_to_trailing_slash: true,
133                html_as_default_extension: false,
134            },
135            fallback: None,
136            call_fallback_on_method_not_allowed: false,
137            redirect_path_prefix: String::new(),
138            backend,
139        }
140    }
141}
142
143impl<F, B: Backend> ServeDir<F, B> {
144    /// If the requested path is a directory append `index.html`.
145    ///
146    /// This is useful for static sites.
147    ///
148    /// Defaults to `true`.
149    pub fn append_index_html_on_directories(mut self, append: bool) -> Self {
150        match &mut self.variant {
151            ServeVariant::Directory {
152                append_index_html_on_directories,
153                ..
154            } => {
155                *append_index_html_on_directories = append;
156                self
157            }
158            ServeVariant::SingleFile { mime: _ } => self,
159        }
160    }
161
162    /// Whether to redirect directory requests without a trailing slash.
163    ///
164    /// When enabled, a request to `/dir` redirects to `/dir/`. When disabled,
165    /// `/dir/index.html` is served directly at `/dir` if
166    /// [`append_index_html_on_directories`](Self::append_index_html_on_directories) is enabled.
167    ///
168    /// Defaults to `true`.
169    pub fn redirect_to_trailing_slash(mut self, redirect: bool) -> Self {
170        match &mut self.variant {
171            ServeVariant::Directory {
172                redirect_to_trailing_slash,
173                ..
174            } => {
175                *redirect_to_trailing_slash = redirect;
176                self
177            }
178            ServeVariant::SingleFile { mime: _ } => self,
179        }
180    }
181
182    /// If the requested path doesn't specify a file extension, append `.html`.
183    ///
184    /// Defaults to `false`.
185    pub fn html_as_default_extension(mut self, append: bool) -> Self {
186        match &mut self.variant {
187            ServeVariant::Directory {
188                html_as_default_extension,
189                ..
190            } => {
191                *html_as_default_extension = append;
192                self
193            }
194            ServeVariant::SingleFile { mime: _ } => self,
195        }
196    }
197
198    /// Sets a path to be prepended when performing a trailing slash redirect.
199    ///
200    /// This is useful when you want to serve the files at another location than `/`, for example
201    /// when you are using multiple services and want this instance to handle `/static/<path>`.
202    /// In that example, you should pass in `/static` so that a trailing slash redirect does not
203    /// redirect to `/<path>/` but instead to `/static/<path>/`
204    ///
205    /// The default is the empty string.
206    pub fn redirect_path_prefix(mut self, prefix: impl Into<String>) -> Self {
207        self.redirect_path_prefix = prefix.into();
208        self
209    }
210
211    /// Set a specific read buffer chunk size.
212    ///
213    /// The default capacity is 64kb.
214    pub fn with_buf_chunk_size(mut self, chunk_size: usize) -> Self {
215        self.buf_chunk_size = chunk_size;
216        self
217    }
218
219    /// Configure whether syntactically valid multi-range requests should be ignored.
220    ///
221    /// When enabled, a request containing multiple byte ranges is served as a normal full
222    /// response with status `200 OK`, as if the `Range` header were absent. This check happens
223    /// before semantic range validation, so overlapping, reversed, or otherwise unsatisfiable
224    /// multi-range requests are also ignored. Malformed range headers and unsatisfiable
225    /// single-range requests still result in `416 Range Not Satisfiable`.
226    ///
227    /// Defaults to `false`.
228    pub fn ignore_multi_range_requests(mut self, ignore: bool) -> Self {
229        self.ignore_multi_range_requests = ignore;
230        self
231    }
232
233    /// Informs the service that it should also look for a precompressed gzip
234    /// version of _any_ file in the directory.
235    ///
236    /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
237    /// a client with an `Accept-Encoding` header that allows the gzip encoding
238    /// will receive the file `dir/foo.txt.gz` instead of `dir/foo.txt`.
239    /// If the precompressed file is not available, or the client doesn't support it,
240    /// the uncompressed version will be served instead.
241    /// Both the precompressed version and the uncompressed version are expected
242    /// to be present in the directory. Different precompressed variants can be combined.
243    pub fn precompressed_gzip(mut self) -> Self {
244        self.precompressed_variants
245            .get_or_insert(Default::default())
246            .gzip = true;
247        self
248    }
249
250    /// Informs the service that it should also look for a precompressed brotli
251    /// version of _any_ file in the directory.
252    ///
253    /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
254    /// a client with an `Accept-Encoding` header that allows the brotli encoding
255    /// will receive the file `dir/foo.txt.br` instead of `dir/foo.txt`.
256    /// If the precompressed file is not available, or the client doesn't support it,
257    /// the uncompressed version will be served instead.
258    /// Both the precompressed version and the uncompressed version are expected
259    /// to be present in the directory. Different precompressed variants can be combined.
260    pub fn precompressed_br(mut self) -> Self {
261        self.precompressed_variants
262            .get_or_insert(Default::default())
263            .br = true;
264        self
265    }
266
267    /// Informs the service that it should also look for a precompressed deflate
268    /// version of _any_ file in the directory.
269    ///
270    /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
271    /// a client with an `Accept-Encoding` header that allows the deflate encoding
272    /// will receive the file `dir/foo.txt.zz` instead of `dir/foo.txt`.
273    /// If the precompressed file is not available, or the client doesn't support it,
274    /// the uncompressed version will be served instead.
275    /// Both the precompressed version and the uncompressed version are expected
276    /// to be present in the directory. Different precompressed variants can be combined.
277    pub fn precompressed_deflate(mut self) -> Self {
278        self.precompressed_variants
279            .get_or_insert(Default::default())
280            .deflate = true;
281        self
282    }
283
284    /// Informs the service that it should also look for a precompressed zstd
285    /// version of _any_ file in the directory.
286    ///
287    /// Assuming the `dir` directory is being served and `dir/foo.txt` is requested,
288    /// a client with an `Accept-Encoding` header that allows the zstd encoding
289    /// will receive the file `dir/foo.txt.zst` instead of `dir/foo.txt`.
290    /// If the precompressed file is not available, or the client doesn't support it,
291    /// the uncompressed version will be served instead.
292    /// Both the precompressed version and the uncompressed version are expected
293    /// to be present in the directory. Different precompressed variants can be combined.
294    pub fn precompressed_zstd(mut self) -> Self {
295        self.precompressed_variants
296            .get_or_insert(Default::default())
297            .zstd = true;
298        self
299    }
300
301    /// Set the fallback service.
302    ///
303    /// This service will be called if there is no file at the path of the request.
304    ///
305    /// The status code returned by the fallback will not be altered. Use
306    /// [`ServeDir::not_found_service`] to set a fallback and always respond with `404 Not Found`.
307    ///
308    /// # Example
309    ///
310    /// This can be used to respond with a different file:
311    ///
312    /// ```rust
313    /// use tower_http::services::{ServeDir, ServeFile};
314    ///
315    /// let service = ServeDir::new("assets")
316    ///     // respond with `not_found.html` for missing files
317    ///     .fallback(ServeFile::new("assets/not_found.html"));
318    /// ```
319    pub fn fallback<F2>(self, new_fallback: F2) -> ServeDir<F2, B> {
320        ServeDir {
321            redirect_path_prefix: self.redirect_path_prefix,
322            base: self.base,
323            buf_chunk_size: self.buf_chunk_size,
324            ignore_multi_range_requests: self.ignore_multi_range_requests,
325            precompressed_variants: self.precompressed_variants,
326            variant: self.variant,
327            fallback: Some(new_fallback),
328            call_fallback_on_method_not_allowed: self.call_fallback_on_method_not_allowed,
329            backend: self.backend,
330        }
331    }
332
333    /// Set the fallback service and override the fallback's status code to `404 Not Found`.
334    ///
335    /// This service will be called if there is no file at the path of the request.
336    ///
337    /// # Example
338    ///
339    /// This can be used to respond with a different file:
340    ///
341    /// ```rust
342    /// use tower_http::services::{ServeDir, ServeFile};
343    ///
344    /// let service = ServeDir::new("assets")
345    ///     // respond with `404 Not Found` and the contents of `not_found.html` for missing files
346    ///     .not_found_service(ServeFile::new("assets/not_found.html"));
347    /// ```
348    ///
349    /// Setups like this are often found in single page applications.
350    pub fn not_found_service<F2>(self, new_fallback: F2) -> ServeDir<SetStatus<F2>, B> {
351        self.fallback(SetStatus::new(new_fallback, StatusCode::NOT_FOUND))
352    }
353
354    /// Customize whether or not to call the fallback for requests that aren't `GET` or `HEAD`.
355    ///
356    /// Defaults to not calling the fallback and instead returning `405 Method Not Allowed`.
357    pub fn call_fallback_on_method_not_allowed(mut self, call_fallback: bool) -> Self {
358        self.call_fallback_on_method_not_allowed = call_fallback;
359        self
360    }
361
362    /// Call the service and get a future that contains any `std::io::Error` that might have
363    /// happened.
364    ///
365    /// This only returns I/O errors encountered while serving the request. Invalid request paths
366    /// and unsupported methods are represented as responses. When a fallback is configured,
367    /// errors that the default service would convert to `404 Not Found` call the fallback instead
368    /// of being returned.
369    ///
370    /// By default `<ServeDir as Service<_>>::call` will handle IO errors and convert them into
371    /// responses. It does that by converting [`std::io::ErrorKind::NotFound`] and
372    /// [`std::io::ErrorKind::PermissionDenied`] to `404 Not Found`. On Unix, errors indicating
373    /// that a path component is not a directory are also converted to `404 Not Found`. Any other
374    /// error is converted to `500 Internal Server Error` and will also be logged with `tracing` in
375    /// case the `tracing` crate feature is enabled.
376    ///
377    /// If you want to manually control how the error response is generated you can make a new
378    /// service that wraps a `ServeDir` and calls `try_call` instead of `call`.
379    ///
380    /// # Example
381    ///
382    /// ```
383    /// use tower_http::services::ServeDir;
384    /// use std::{io, convert::Infallible};
385    /// use http::{Request, Response, StatusCode};
386    /// use http_body::Body as _;
387    /// use http_body_util::{Full, BodyExt, combinators::UnsyncBoxBody};
388    /// use bytes::Bytes;
389    /// use tower::{service_fn, ServiceExt, BoxError};
390    ///
391    /// async fn serve_dir(
392    ///     request: Request<Full<Bytes>>
393    /// ) -> Result<Response<UnsyncBoxBody<Bytes, BoxError>>, Infallible> {
394    ///     let mut service = ServeDir::new("assets");
395    ///
396    ///     // You only need to worry about backpressure, and thus call `ServiceExt::ready`, if
397    ///     // you are adding a fallback to `ServeDir` that cares about backpressure.
398    ///     //
399    ///     // Its shown here for demonstration but you can do `service.try_call(request)`
400    ///     // otherwise
401    ///     let ready_service = match ServiceExt::<Request<Full<Bytes>>>::ready(&mut service).await {
402    ///         Ok(ready_service) => ready_service,
403    ///         Err(infallible) => match infallible {},
404    ///     };
405    ///
406    ///     match ready_service.try_call(request).await {
407    ///         Ok(response) => {
408    ///             Ok(response.map(|body| body.map_err(Into::into).boxed_unsync()))
409    ///         }
410    ///         Err(err) => {
411    ///             let not_found = matches!(
412    ///                 err.kind(),
413    ///                 io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied
414    ///             ) || cfg!(unix) && err.raw_os_error() == Some(20);
415    ///             let (status, message) = if not_found {
416    ///                 (StatusCode::NOT_FOUND, "Not found")
417    ///             } else {
418    ///                 (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
419    ///             };
420    ///             let body = Full::from(message)
421    ///                 .map_err(Into::into)
422    ///                 .boxed_unsync();
423    ///             let response = Response::builder()
424    ///                 .status(status)
425    ///                 .body(body)
426    ///                 .unwrap();
427    ///             Ok(response)
428    ///         }
429    ///     }
430    /// }
431    /// ```
432    pub fn try_call<ReqBody, FResBody>(
433        &mut self,
434        req: Request<ReqBody>,
435    ) -> ResponseFuture<ReqBody, F>
436    where
437        F: Service<Request<ReqBody>, Response = Response<FResBody>, Error = Infallible> + Clone,
438        F::Future: Send + 'static,
439        FResBody: http_body::Body<Data = Bytes> + Send + 'static,
440        FResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
441    {
442        if req.method() != Method::GET && req.method() != Method::HEAD {
443            if self.call_fallback_on_method_not_allowed {
444                if let Some(fallback) = &mut self.fallback {
445                    return ResponseFuture {
446                        inner: future::call_fallback(fallback, req),
447                    };
448                }
449            }
450
451            return ResponseFuture::method_not_allowed();
452        }
453
454        // `ServeDir` doesn't care about the request body but the fallback might. So move out the
455        // body and pass it to the fallback, leaving an empty body in its place
456        //
457        // this is necessary because we cannot clone bodies
458        let (mut parts, body) = req.into_parts();
459        // same goes for extensions
460        let extensions = std::mem::take(&mut parts.extensions);
461        let req = Request::from_parts(parts, Empty::<Bytes>::new());
462
463        let fallback_and_request = self.fallback.as_mut().map(|fallback| {
464            let mut fallback_req = Request::new(body);
465            *fallback_req.method_mut() = req.method().clone();
466            *fallback_req.uri_mut() = req.uri().clone();
467            *fallback_req.headers_mut() = req.headers().clone();
468            *fallback_req.extensions_mut() = extensions;
469
470            // get the ready fallback and leave a non-ready clone in its place
471            let clone = fallback.clone();
472            let fallback = std::mem::replace(fallback, clone);
473
474            (fallback, fallback_req)
475        });
476
477        let path_to_file = match self
478            .variant
479            .build_and_validate_path(&self.base, req.uri().path())
480        {
481            Some(path_to_file) => path_to_file,
482            None => {
483                return ResponseFuture::invalid_path(fallback_and_request);
484            }
485        };
486
487        let redirect_path_prefix = self.redirect_path_prefix.clone();
488
489        let buf_chunk_size = self.buf_chunk_size;
490        let ignore_multi_range_requests = self.ignore_multi_range_requests;
491        let range_header = req
492            .headers()
493            .get(header::RANGE)
494            .and_then(|value| value.to_str().ok())
495            .map(|s| s.to_owned());
496
497        let precompression_configured = self.precompressed_variants.is_some();
498        let negotiated_encodings: Vec<_> = encodings(
499            req.headers(),
500            self.precompressed_variants.unwrap_or_default(),
501        )
502        .collect();
503
504        let open_file_future = Box::pin(open_file::open_file(open_file::OpenFileRequest {
505            variant: self.variant.clone(),
506            redirect_path_prefix,
507            path_to_file,
508            req,
509            negotiated_encodings,
510            range_header,
511            buf_chunk_size,
512            ignore_multi_range_requests,
513            precompression_configured,
514            backend: self.backend.clone(),
515        }));
516
517        ResponseFuture::open_file_future(open_file_future, fallback_and_request)
518    }
519}
520
521impl<ReqBody, F, FResBody, B> Service<Request<ReqBody>> for ServeDir<F, B>
522where
523    F: Service<Request<ReqBody>, Response = Response<FResBody>, Error = Infallible> + Clone,
524    F::Future: Send + 'static,
525    FResBody: http_body::Body<Data = Bytes> + Send + 'static,
526    FResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
527    B: Backend,
528{
529    type Response = Response<ResponseBody>;
530    type Error = Infallible;
531    type Future = InfallibleResponseFuture<ReqBody, F>;
532
533    #[inline]
534    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
535        if let Some(fallback) = &mut self.fallback {
536            fallback.poll_ready(cx)
537        } else {
538            Poll::Ready(Ok(()))
539        }
540    }
541
542    fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
543        let future = self
544            .try_call(req)
545            .map(|result: Result<_, _>| -> Result<_, Infallible> {
546                let response = result.unwrap_or_else(|err| {
547                    let status = if should_return_not_found(&err) {
548                        StatusCode::NOT_FOUND
549                    } else {
550                        #[cfg(feature = "tracing")]
551                        tracing::error!(error = %err, "Failed to read file");
552
553                        StatusCode::INTERNAL_SERVER_ERROR
554                    };
555
556                    let body = ResponseBody::new(UnsyncBoxBody::from_inner(
557                        Empty::new().map_err(|err| match err {}).boxed_unsync(),
558                    ));
559                    Response::builder().status(status).body(body).unwrap()
560                });
561                Ok(response)
562            } as _);
563
564        InfallibleResponseFuture::new(future)
565    }
566}
567
568fn should_return_not_found(err: &io::Error) -> bool {
569    #[cfg(unix)]
570    // 20 = libc::ENOTDIR => "not a directory".
571    // When `io_error_more` lands, this can be changed
572    // to checking for `io::ErrorKind::NotADirectory`.
573    // https://github.com/rust-lang/rust/issues/86442
574    let error_is_not_a_directory = err.raw_os_error() == Some(20);
575    #[cfg(not(unix))]
576    let error_is_not_a_directory = false;
577
578    matches!(
579        err.kind(),
580        io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied
581    ) || error_is_not_a_directory
582}
583
584opaque_future! {
585    /// Response future of [`ServeDir`].
586    pub type InfallibleResponseFuture<ReqBody, F> =
587        futures_util::future::Map<
588            ResponseFuture<ReqBody, F>,
589            fn(Result<Response<ResponseBody>, io::Error>) -> Result<Response<ResponseBody>, Infallible>,
590        >;
591}
592
593// Allow the ServeDir service to be used in the ServeFile service
594// with almost no overhead
595#[derive(Clone, Debug)]
596enum ServeVariant {
597    Directory {
598        append_index_html_on_directories: bool,
599        redirect_to_trailing_slash: bool,
600        html_as_default_extension: bool,
601    },
602    SingleFile {
603        mime: HeaderValue,
604    },
605}
606
607impl ServeVariant {
608    fn build_and_validate_path(&self, base_path: &Path, requested_path: &str) -> Option<PathBuf> {
609        match self {
610            ServeVariant::Directory {
611                append_index_html_on_directories: _,
612                redirect_to_trailing_slash: _,
613                html_as_default_extension: _,
614            } => {
615                let path = requested_path.trim_start_matches('/');
616
617                let path_decoded = percent_decode(path.as_ref()).decode_utf8().ok()?;
618                let path_decoded = Path::new(&*path_decoded);
619
620                let mut path_to_file = base_path.to_path_buf();
621                for component in path_decoded.components() {
622                    match component {
623                        Component::Normal(comp) => {
624                            // protect against paths like `/foo/c:/bar/baz` (#204)
625                            if Path::new(&comp)
626                                .components()
627                                .all(|c| matches!(c, Component::Normal(_)))
628                            {
629                                #[cfg(windows)]
630                                {
631                                    use std::os::windows::ffi::OsStrExt;
632                                    if is_reserved_dos_name(|| comp.encode_wide()) {
633                                        return None;
634                                    }
635                                }
636
637                                path_to_file.push(comp)
638                            } else {
639                                return None;
640                            }
641                        }
642                        Component::CurDir => {}
643                        Component::Prefix(_) | Component::RootDir | Component::ParentDir => {
644                            return None;
645                        }
646                    }
647                }
648                Some(path_to_file)
649            }
650            ServeVariant::SingleFile { mime: _ } => Some(base_path.to_path_buf()),
651        }
652    }
653}
654
655/// Check whether a component name matches a reserved Windows DOS device name.
656/// See: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
657///
658/// We explicitly check for Unicode superscript characters `¹` (0x00B9), `²` (0x00B2),
659/// and `³` (0x00B3) because older character tables (ISO/IEC 8859-1) define these values,
660/// which legacy Win32 file parsing resolves natively as valid port numbers (0..9).
661///
662/// This uses an iterator and stack array to avoid allocating. A closure is used because it
663/// iterates the characters twice. The closure must return the same iterator each time it is
664/// called.
665#[cfg(any(windows, test))]
666fn is_reserved_dos_name<F, I>(mut get_iter: F) -> bool
667where
668    F: FnMut() -> I,
669    I: Iterator<Item = u16>,
670{
671    const CON: [u16; 3] = [b'C' as u16, b'O' as u16, b'N' as u16];
672    const PRN: [u16; 3] = [b'P' as u16, b'R' as u16, b'N' as u16];
673    const AUX: [u16; 3] = [b'A' as u16, b'U' as u16, b'X' as u16];
674    const NUL: [u16; 3] = [b'N' as u16, b'U' as u16, b'L' as u16];
675    const CONIN: [u16; 6] = [
676        b'C' as u16,
677        b'O' as u16,
678        b'N' as u16,
679        b'I' as u16,
680        b'N' as u16,
681        b'$' as u16,
682    ];
683    const CONOUT: [u16; 7] = [
684        b'C' as u16,
685        b'O' as u16,
686        b'N' as u16,
687        b'O' as u16,
688        b'U' as u16,
689        b'T' as u16,
690        b'$' as u16,
691    ];
692
693    const COM: [u16; 3] = [b'C' as u16, b'O' as u16, b'M' as u16];
694    const LPT: [u16; 3] = [b'L' as u16, b'P' as u16, b'T' as u16];
695
696    const ZERO: u16 = b'0' as u16;
697    const NINE: u16 = b'9' as u16;
698    const SUPERSCRIPT_ONE: u16 = 0x00B9;
699    const SUPERSCRIPT_TWO: u16 = 0x00B2;
700    const SUPERSCRIPT_THREE: u16 = 0x00B3;
701
702    fn is_whitespace(c: u16) -> bool {
703        c <= 0x7F && ((c as u8).is_ascii_whitespace() || c == 0x000B)
704    }
705
706    // In a first pass over the string, obtain the length of the basename.
707    let trimmed_len = get_iter()
708        .enumerate()
709        // We want the base name, so stop at '.' or ':' characters.
710        .take_while(|&(_idx, c)| c != b'.' as u16 && c != b':' as u16)
711        // We want to trim whitespace from the end, so ignore whitespace chars.
712        .filter(|&(_idx, c)| !is_whitespace(c))
713        // Get the last non-whitespace char before the first '.'/':' character.
714        .last()
715        // Convert index of that char into length of string.
716        .map(|(idx, _)| idx + 1)
717        .unwrap_or(0);
718
719    // If the trimmed base name is longer than 7, it cannot be a reserved name.
720    if trimmed_len > 7 {
721        return false;
722    }
723
724    // At this point, we can store the string in an array, which is more convenient to work with.
725    let mut buf = [0u16; 7];
726    get_iter()
727        .take(trimmed_len)
728        .enumerate()
729        .for_each(|(i, c)| buf[i] = c);
730
731    for b in &mut buf {
732        if *b <= 0x7F {
733            *b = (*b as u8).to_ascii_uppercase() as u16;
734        }
735        if *b == SUPERSCRIPT_ONE {
736            *b = b'1' as u16;
737        }
738        if *b == SUPERSCRIPT_TWO {
739            *b = b'2' as u16;
740        }
741        if *b == SUPERSCRIPT_THREE {
742            *b = b'3' as u16;
743        }
744    }
745    let name = &buf[..trimmed_len];
746
747    // Check basic fixed-length strings
748    if name == CON || name == PRN || name == AUX || name == NUL || name == CONIN || name == CONOUT {
749        return true;
750    }
751
752    // COMx / LPTx
753    if name.len() == 4 {
754        let prefix = &name[..3];
755        let suffix = name[3];
756
757        if (prefix == COM || prefix == LPT) && matches!(suffix, ZERO..=NINE) {
758            return true;
759        }
760    }
761
762    false
763}
764
765opaque_body! {
766    /// Response body for [`ServeDir`] and [`ServeFile`][super::ServeFile].
767    #[derive(Default)]
768    pub type ResponseBody = UnsyncBoxBody<Bytes, io::Error>;
769}
770
771impl From<ResponseBody> for UnsyncBoxBody<Bytes, io::Error> {
772    fn from(body: ResponseBody) -> Self {
773        body.inner
774    }
775}
776
777/// The default fallback service used with [`ServeDir`].
778#[derive(Debug, Clone, Copy)]
779pub struct DefaultServeDirFallback(Infallible);
780
781impl<ReqBody> Service<Request<ReqBody>> for DefaultServeDirFallback
782where
783    ReqBody: Send + 'static,
784{
785    type Response = Response<ResponseBody>;
786    type Error = Infallible;
787    type Future = InfallibleResponseFuture<ReqBody, Self>;
788
789    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
790        match self.0 {}
791    }
792
793    fn call(&mut self, _req: Request<ReqBody>) -> Self::Future {
794        match self.0 {}
795    }
796}
797
798#[derive(Clone, Copy, Debug, Default)]
799struct PrecompressedVariants {
800    gzip: bool,
801    deflate: bool,
802    br: bool,
803    zstd: bool,
804}
805
806impl SupportedEncodings for PrecompressedVariants {
807    fn gzip(&self) -> bool {
808        self.gzip
809    }
810
811    fn deflate(&self) -> bool {
812        self.deflate
813    }
814
815    fn br(&self) -> bool {
816        self.br
817    }
818
819    fn zstd(&self) -> bool {
820        self.zstd
821    }
822}