pub enum PathPolicy {
Strict,
Redirect,
Collapse,
}Expand description
What to do when a request path is a non-canonical spelling of a route.
Interior empty segments — //a, /a//b — were once collapsed silently,
which gave every resource several URLs. That is not a traversal problem, but
it is an aliasing one, and aliases have teeth:
- Middleware, guards and proxy rules that key on a literal prefix
(
path.starts_with("/admin")) are bypassable with//admin. - Caches key on the URL, so one resource occupies several entries and an intermediary can disagree with the origin about identity.
A trailing slash is not treated as an alias — see
canonical_path for why removing it would break directory listings.
Whatever the policy, %2F is never decoded into a separator: an encoded
slash is data inside one segment, and decoding it early is how a
normalisation change turns into a traversal bug.
use churust_core::{Churust, Call, PathPolicy, TestClient};
let app = Churust::server()
.path_policy(PathPolicy::Redirect)
.routing(|r| { r.get("/a", |_c: Call| async { "a" }); })
.build();
let res = TestClient::new(app).get("//a").send().await;
assert_eq!(res.status(), http::StatusCode::PERMANENT_REDIRECT);
assert_eq!(res.header("location"), Some("/a"));Variants§
Strict
Refuse an alias: //a is not /a, and gets 404.
The default. Fewest surprises and the strongest cache identity — one resource, one URL.
Redirect
Redirect an alias to its canonical form with 308 Permanent Redirect.
Kind to hand-typed and hand-written URLs. 308 rather than 301 so a
POST stays a POST; 301 permits a client to retry as GET, which
silently drops the body.
Collapse
Collapse aliases silently, serving them as if canonical.
The pre-PathPolicy behaviour, kept so an application with
alias-shaped links has somewhere to stand while it fixes them. It
creates URL aliases by design; prefer Strict or Redirect.
The collapsing happens in the router, for matching only: the URI is
not rewritten, so call.path() still reports the spelling the client
sent — //admin/secret, not /admin/secret — in middleware and in the
handler alike. That is what makes the prefix-check bypass above live
under this policy and only under this policy. Under Strict and
Redirect a middleware also sees the raw spelling, but the request is
refused or redirected before any handler runs, so nothing is served.
Trait Implementations§
Source§impl Clone for PathPolicy
impl Clone for PathPolicy
Source§fn clone(&self) -> PathPolicy
fn clone(&self) -> PathPolicy
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for PathPolicy
Source§impl Debug for PathPolicy
impl Debug for PathPolicy
Source§impl Default for PathPolicy
impl Default for PathPolicy
Source§fn default() -> PathPolicy
fn default() -> PathPolicy
Source§impl<'de> Deserialize<'de> for PathPolicy
impl<'de> Deserialize<'de> for PathPolicy
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for PathPolicy
Source§impl PartialEq for PathPolicy
impl PartialEq for PathPolicy
impl StructuralPartialEq for PathPolicy
Auto Trait Implementations§
impl Freeze for PathPolicy
impl RefUnwindSafe for PathPolicy
impl Send for PathPolicy
impl Sync for PathPolicy
impl Unpin for PathPolicy
impl UnsafeUnpin for PathPolicy
impl UnwindSafe for PathPolicy
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.