Skip to content

Commit

Permalink
Clean up docs. Rename 'FileServer::empty()'.
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioBenitez committed Jul 6, 2024
1 parent 482a047 commit fb8df12
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 107 deletions.
13 changes: 7 additions & 6 deletions core/http/src/uri/segments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@ impl<'a> Segments<'a, Path> {
}

/// Creates a `PathBuf` from `self`. The returned `PathBuf` is
/// percent-decoded. If a segment is equal to `..`, the previous segment (if
/// any) is skipped.
/// percent-decoded and guaranteed to be relative. If a segment is equal to
/// `.`, it is skipped. If a segment is equal to `..`, the previous segment
/// (if any) is skipped.
///
/// For security purposes, if a segment meets any of the following
/// conditions, an `Err` is returned indicating the condition met:
Expand All @@ -193,7 +194,7 @@ impl<'a> Segments<'a, Path> {
/// Additionally, if `allow_dotfiles` is `false`, an `Err` is returned if
/// the following condition is met:
///
/// * Decoded segment starts with any of: `.` (except `..`)
/// * Decoded segment starts with any of: `.` (except `..` and `.`)
///
/// As a result of these conditions, a `PathBuf` derived via `FromSegments`
/// is safe to interpolate within, or use as a suffix of, a path without
Expand All @@ -216,10 +217,10 @@ impl<'a> Segments<'a, Path> {
pub fn to_path_buf(&self, allow_dotfiles: bool) -> Result<PathBuf, PathError> {
let mut buf = PathBuf::new();
for segment in self.clone() {
if segment == ".." {
buf.pop();
} else if segment == "." {
if segment == "." {
continue;
} else if segment == ".." {
buf.pop();
} else if !allow_dotfiles && segment.starts_with('.') {
return Err(PathError::BadStart('.'))
} else if segment.starts_with('*') {
Expand Down
43 changes: 13 additions & 30 deletions core/lib/src/fs/rewrite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,20 @@ use crate::Request;
use crate::http::{ext::IntoOwned, HeaderMap};
use crate::response::Redirect;

/// Trait used to implement [`FileServer`] customization.
/// A file server [`Rewrite`] rewriter.
///
/// A [`FileServer`] is a sequence of `Rewriter`s which transform the incoming
/// request path into a [`Rewrite`] or `None`.
///
/// If the final rewrite is `None` or a nonexistent path or a directory,
/// [`FileServer`] responds with [`Status::NotFound`]. Otherwise it responds
/// with the file contents, if [`Rewrite::File`] is specified, or a redirect, if
/// A [`FileServer`] is a sequence of [`Rewriter`]s which transform the incoming
/// request path into a [`Rewrite`] or `None`. The first rewriter is called with
/// the request path as a [`Rewrite::File`]. Each `Rewriter` thereafter is
/// called in-turn with the previously returned [`Rewrite`], and the value
/// returned from the last `Rewriter` is used to respond to the request. If the
/// final rewrite is `None` or a nonexistent path or a directory, [`FileServer`]
/// responds with [`Status::NotFound`]. Otherwise it responds with the file
/// contents, if [`Rewrite::File`] is specified, or a redirect, if
/// [`Rewrite::Redirect`] is specified.
///
/// # Creating a [`FileServer`]
///
/// The primary way to create a [`FileServer`] is via [`FileServer::new()`] which
/// creates a new [`FileServer`] with a default set of `Rewriter`s: a filter for
/// dotfiles, a root path to apply as a prefix, an index file rewriter, and a
/// rewriter to normalize directories to always include a trailing slash.
///
/// [`FileServer`]: super::FileServer
/// [`FileServer::new()`]: super::FileServer::new
/// [`Status::NotFound`]: crate::http::Status::NotFound
pub trait Rewriter: Send + Sync + 'static {
/// Alter the [`Rewrite`] as needed.
fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, req: &'r Request<'_>) -> Option<Rewrite<'r>>;
Expand Down Expand Up @@ -109,7 +104,7 @@ impl<'r> File<'r> {
/// use rocket::fs::FileServer;
/// use rocket::fs::rewrite::Prefix;
///
/// FileServer::empty()
/// FileServer::identity()
/// .filter(|f, _| f.is_visible())
/// .rewrite(Prefix::checked("static"));
/// ```
Expand Down Expand Up @@ -160,7 +155,7 @@ impl Rewriter for PathBuf {
/// use rocket::fs::FileServer;
/// use rocket::fs::rewrite::{Prefix, TrailingDirs};
///
/// FileServer::empty()
/// FileServer::identity()
/// .filter(|f, _| f.is_visible())
/// .rewrite(TrailingDirs);
/// ```
Expand Down Expand Up @@ -190,7 +185,7 @@ impl Rewriter for TrailingDirs {
/// use rocket::fs::FileServer;
/// use rocket::fs::rewrite::DirIndex;
///
/// FileServer::new_without_index("static")
/// FileServer::without_index("static")
/// .rewrite(DirIndex::if_exists("index.htm"))
/// .rewrite(DirIndex::unconditional("index.html"));
/// ```
Expand Down Expand Up @@ -233,24 +228,12 @@ impl<'r> From<File<'r>> for Rewrite<'r> {
}
}

impl<'r> From<File<'r>> for Option<Rewrite<'r>> {
fn from(value: File<'r>) -> Self {
Some(Rewrite::File(value))
}
}

impl<'r> From<Redirect> for Rewrite<'r> {
fn from(value: Redirect) -> Self {
Self::Redirect(value)
}
}

impl<'r> From<Redirect> for Option<Rewrite<'r>> {
fn from(value: Redirect) -> Self {
Some(Rewrite::Redirect(value))
}
}

impl<F: Send + Sync + 'static> Rewriter for F
where F: for<'r> Fn(Option<Rewrite<'r>>, &Request<'_>) -> Option<Rewrite<'r>>
{
Expand Down
172 changes: 110 additions & 62 deletions core/lib/src/fs/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,38 @@ use crate::fs::rewrite::*;
///
/// This handler makes is simple to serve static files from a directory on the
/// local file system. To use it, construct a `FileServer` using
/// [`FileServer::new()`], then simply `mount` the handler. When mounted, the
/// handler serves files from the specified directory. If the file is not found,
/// the handler _forwards_ the request. By default, `FileServer` has a rank of
/// `10`. Use [`FileServer::new()`] to create a handler with a custom rank.
/// [`FileServer::new()`], then `mount` the handler.
///
/// # Customization
/// ```rust,no_run
/// # #[macro_use] extern crate rocket;
/// use rocket::fs::FileServer;
///
/// How `FileServer` responds to specific requests can be customized through
/// the use of [`Rewriter`]s. See [`Rewriter`] for more detailed documentation
/// on how to take full advantage of `FileServer`'s extensibility.
/// #[launch]
/// fn rocket() -> _ {
/// rocket::build()
/// .mount("/", FileServer::new("/www/static"))
/// }
/// ```
///
/// When mounted, the handler serves files from the specified path. If a
/// requested file does not exist, the handler _forwards_ the request with a
/// `404` status.
///
/// By default, the route has a rank of `10` which can be changed with
/// [`FileServer::rank()`].
///
/// [`FileServer::new()`] construct a `FileServer`
/// with common rewrites: they filter out dotfiles, redirect requests to
/// directories to include a trailing slash, and use `index.html` to respond to
/// requests for a directory. If you want to customize or replace these default
/// rewrites, see [`FileServer::empty()`].
/// # Customization
///
/// `FileServer` works through a pipeline of _rewrites_ in which a requested
/// path is transformed into a `PathBuf` via [`Segments::to_path_buf()`] and
/// piped through a series of [`Rewriter`]s to obtain a final [`Rewrite`] which
/// is then used to generate a final response. See [`Rewriter`] for complete
/// details on implementing your own `Rewriter`s.
///
/// # Example
///
/// Serve files from the `/static` directory on the local file system at the
/// `/public` path, with the default rewrites.
/// `/public` path:
///
/// ```rust,no_run
/// # #[macro_use] extern crate rocket;
Expand Down Expand Up @@ -68,6 +79,8 @@ use crate::fs::rewrite::*;
/// rocket::build().mount("/", FileServer::new(relative!("static")))
/// }
/// ```
///
/// [`relative!`]: crate::fs::relative!
#[derive(Clone)]
pub struct FileServer {
rewrites: Vec<Arc<dyn Rewriter>>,
Expand All @@ -79,50 +92,53 @@ impl FileServer {
const DEFAULT_RANK: isize = 10;

/// Constructs a new `FileServer` that serves files from the file system
/// `path` with a default rank.
/// `path` with the following rewrites:
///
/// Adds a set of default rewrites:
/// - `|f, _| f.is_visible()`: Hides all dotfiles.
/// - [`Prefix::checked(path)`]: Applies the root path.
/// - [`TrailingDirs`]: Normalizes directories to have a trailing slash.
/// - [`DirIndex::unconditional("index.html")`](DirIndex::unconditional):
/// Appends `index.html` to directory requests.
/// - `|f, _| f.is_visible()`: Serve only visible files (hide dotfiles).
/// - [`Prefix::checked(path)`]: Prefix requests with `path`.
/// - [`TrailingDirs`]: Ensure directory have a trailing slash.
/// - [`DirIndex::unconditional("index.html")`]: Serve `$dir/index.html` for
/// requests to directory `$dir`.
///
/// If you don't want to serve requests for directories, or want to
/// customize what files are served when a directory is requested, see
/// [`Self::new_without_index`].
///
/// If you need to allow requests for dotfiles, or make any other changes
/// to the default rewrites, see [`Self::empty`].
/// If you don't want to serve index files or want a different index file,
/// use [`Self::without_index`]. To customize the entire request to file
/// path rewrite pipeline, use [`Self::identity`].
///
/// [`Prefix::checked(path)`]: crate::fs::rewrite::Prefix::checked
/// [`TrailingDirs`]: crate::fs::rewrite::TrailingDirs
/// [`DirIndex::unconditional`]: crate::fs::DirIndex::unconditional
/// [`DirIndex::unconditional("index.html")`]: DirIndex::unconditional()
///
/// # Example
///
/// ```rust,no_run
/// # #[macro_use] extern crate rocket;
/// use rocket::fs::FileServer;
///
/// #[launch]
/// fn rocket() -> _ {
/// rocket::build()
/// .mount("/", FileServer::new("/www/static"))
/// }
/// ```
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self::empty()
Self::identity()
.filter(|f, _| f.is_visible())
.rewrite(Prefix::checked(path))
.rewrite(TrailingDirs)
.rewrite(DirIndex::unconditional("index.html"))
}

/// Constructs a new `FileServer` that serves files from the file system
/// `path` with a default rank. This variant does not add a default
/// directory index option.
/// Exactly like [`FileServer::new()`] except it _does not_ serve directory
/// index files via [`DirIndex`]. It rewrites with the following:
///
/// Adds a set of default rewrites:
/// - `|f, _| f.is_visible()`: Hides all dotfiles.
/// - [`Prefix::checked(path)`]: Applies the root path.
/// - [`TrailingDirs`]: Normalizes directories to have a trailing slash.
///
/// In most cases, [`Self::new`] is good enough. However, if you do not want
/// to automatically respond to requests for directories with `index.html`,
/// this method is provided.
/// - `|f, _| f.is_visible()`: Serve only visible files (hide dotfiles).
/// - [`Prefix::checked(path)`]: Prefix requests with `path`.
/// - [`TrailingDirs`]: Ensure directory have a trailing slash.
///
/// # Example
///
/// Constructs a default file server to server files from `./static`, but
/// uses `index.txt` if `index.html` doesn't exist.
/// Constructs a default file server to serve files from `./static` using
/// `index.txt` as the index file if `index.html` doesn't exist.
///
/// ```rust,no_run
/// # #[macro_use] extern crate rocket;
Expand All @@ -141,15 +157,43 @@ impl FileServer {
///
/// [`Prefix::checked(path)`]: crate::fs::rewrite::Prefix::checked
/// [`TrailingDirs`]: crate::fs::rewrite::TrailingDirs
pub fn new_without_index<P: AsRef<Path>>(path: P) -> Self {
Self::empty()
pub fn without_index<P: AsRef<Path>>(path: P) -> Self {
Self::identity()
.filter(|f, _| f.is_visible())
.rewrite(Prefix::checked(path))
.rewrite(TrailingDirs)
}

/// Constructs a new `FileServer`, with default rank, and no rewrites.
pub fn empty() -> Self {
/// Constructs a new `FileServer` with no rewrites.
///
/// Without any rewrites, a `FileServer` will try to serve the requested
/// file from the current working directory. In other words, it represents
/// the identity rewrite. For example, a request `GET /foo/bar` will be
/// passed through unmodified and thus `./foo/bar` will be served. This is
/// very unlikely to be what you want.
///
/// Prefer to use [`FileServer::new()`] or [`FileServer::without_index()`]
/// whenever possible and otherwise use one or more of the rewrites in
/// [`rocket::fs::rewrite`] or your own custom rewrites.
///
/// # Example
///
/// ```rust,no_run
/// # #[macro_use] extern crate rocket;
/// use rocket::fs::{FileServer, rewrite};
///
/// #[launch]
/// fn rocket() -> _ {
/// // A file server that serves exactly one file: /www/foo.html. The
/// // file is served irrespective of what's requested.
/// let server = FileServer::identity()
/// .rewrite(rewrite::File::checked("/www/foo.html"));
///
/// rocket::build()
/// .mount("/", server)
/// }
/// ```
pub fn identity() -> Self {
Self {
rewrites: vec![],
rank: Self::DEFAULT_RANK
Expand All @@ -163,34 +207,34 @@ impl FileServer {
/// ```rust,no_run
/// # use rocket::fs::FileServer;
/// # fn make_server() -> FileServer {
/// FileServer::empty()
/// FileServer::identity()
/// .rank(5)
/// # }
pub fn rank(mut self, rank: isize) -> Self {
self.rank = rank;
self
}

/// Generic rewrite to transform one Rewrite to another.
/// Add `rewriter` to the rewrite pipeline.
///
/// # Example
///
/// Redirects all requests that have been filtered to the root of the `FileServer`.
/// Redirect filtered requests (`None`) to `/`.
///
/// ```rust,no_run
/// # use rocket::fs::{FileServer, rewrite::Rewrite};
/// # use rocket::{response::Redirect, uri, Build, Rocket, Request};
/// # #[macro_use] extern crate rocket;
/// use rocket::fs::{FileServer, rewrite::Rewrite};
/// use rocket::{request::Request, response::Redirect};
///
/// fn redir_missing<'r>(p: Option<Rewrite<'r>>, _req: &Request<'_>) -> Option<Rewrite<'r>> {
/// match p {
/// None => Redirect::temporary(uri!("/")).into(),
/// p => p,
/// }
/// Some(p.unwrap_or_else(|| Redirect::temporary(uri!("/")).into()))
/// }
///
/// # fn launch() -> Rocket<Build> {
/// rocket::build()
/// .mount("/", FileServer::new("static").rewrite(redir_missing))
/// # }
/// #[launch]
/// fn rocket() -> _ {
/// rocket::build()
/// .mount("/", FileServer::new("static").rewrite(redir_missing))
/// }
/// ```
///
/// Note that `redir_missing` is not a closure in this example. Making it a closure
Expand All @@ -200,11 +244,13 @@ impl FileServer {
self
}

/// Filter what files this `FileServer` will respond with
/// Adds a rewriter to the pipeline that returns `Some` only when the
/// function `f` returns `true`, filtering out all other files.
///
/// # Example
///
/// Filter out all paths with a filename of `hidden`.
/// Allow all files that don't have a file name or have a file name other
/// than "hidden".
///
/// ```rust,no_run
/// # #[macro_use] extern crate rocket;
Expand Down Expand Up @@ -238,7 +284,9 @@ impl FileServer {
self.rewrite(Filter(f))
}

/// Change what files this `FileServer` will respond with
/// Adds a rewriter to the pipeline that maps the current `File` to another
/// `Rewrite` using `f`. If the current `Rewrite` is a `Redirect`, it is
/// passed through without calling `f`.
///
/// # Example
///
Expand Down
Loading

0 comments on commit fb8df12

Please sign in to comment.