feat: expose PathBufWrap utility for public access (#3694)

* feat: expose PathBufWrap utility for public access

* fix: rename to `parse_unprocessed_req`

---------

Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
This commit is contained in:
Andrew Scott 2026-02-02 05:15:47 -07:00 committed by GitHub
parent 9021b82b91
commit 5ee6b59b1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 4 deletions

View File

@ -3,6 +3,9 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.88.
- `PathBufWrap` & `UriSegmentError` made public. [#3694]
[#3694]: https://github.com/actix/actix-web/pull/3694
## 0.6.9

View File

@ -21,6 +21,7 @@ impl ResponseError for FilesError {
}
}
/// Error which can occur with parsing/validating a request-uri path
#[derive(Debug, PartialEq, Eq, Display)]
#[non_exhaustive]
pub enum UriSegmentError {

View File

@ -37,13 +37,12 @@ mod range;
mod service;
pub use self::{
chunked::ChunkedReadFile, directory::Directory, files::Files, named::NamedFile,
range::HttpRange, service::FilesService,
chunked::ChunkedReadFile, directory::Directory, error::UriSegmentError, files::Files,
named::NamedFile, path_buf::PathBufWrap, range::HttpRange, service::FilesService,
};
use self::{
directory::{directory_listing, DirectoryRenderer},
error::FilesError,
path_buf::PathBufWrap,
};
type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;

View File

@ -8,8 +8,11 @@ use actix_web::{dev::Payload, FromRequest, HttpRequest};
use crate::error::UriSegmentError;
/// Secure Path Traversal Guard
///
/// This struct parses a request-uri [`PathBuf`](std::path::PathBuf)
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct PathBufWrap(PathBuf);
pub struct PathBufWrap(PathBuf);
impl FromStr for PathBufWrap {
type Err = UriSegmentError;
@ -20,6 +23,37 @@ impl FromStr for PathBufWrap {
}
impl PathBufWrap {
/// Parse a safe path from the unprocessed tail of a supplied
/// [`HttpRequest`](actix_web::HttpRequest), given the choice of allowing hidden files to be
/// considered valid segments.
///
/// This uses [`HttpRequest::match_info`](actix_web::HttpRequest::match_info) and
/// [`Path::unprocessed`](actix_web::dev::Path::unprocessed), which returns the part of the
/// path not matched by route patterns. This is useful for mounted services (eg. `Files`),
/// where only the tail should be parsed.
///
/// Path traversal is guarded by this method.
#[inline]
pub fn parse_unprocessed_req(
req: &HttpRequest,
hidden_files: bool,
) -> Result<Self, UriSegmentError> {
Self::parse_path(req.match_info().unprocessed(), hidden_files)
}
/// Parse a safe path from the full request path of a supplied
/// [`HttpRequest`](actix_web::HttpRequest), given the choice of allowing hidden files to be
/// considered valid segments.
///
/// This uses [`HttpRequest::path`](actix_web::HttpRequest::path), and is more appropriate
/// for non-mounted handlers that want the entire request path.
///
/// Path traversal is guarded by this method.
#[inline]
pub fn parse_req_path(req: &HttpRequest, hidden_files: bool) -> Result<Self, UriSegmentError> {
Self::parse_path(req.path(), hidden_files)
}
/// Parse a path, giving the choice of allowing hidden files to be considered valid segments.
///
/// Path traversal is guarded by this method.
@ -91,6 +125,7 @@ impl FromRequest for PathBufWrap {
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
// Uses the unprocessed tail of the request path and disallows hidden files.
ready(req.match_info().unprocessed().parse())
}
}