Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a feature to ignore empty path segments (// in path). #993

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ default = ["multipart", "websocket"]
websocket = ["tokio-tungstenite"]
tls = ["tokio-rustls"]

# allow double slashes in paths
ignore-empty-path-segments = []

# Enable compression-related filters
compression = ["compression-brotli", "compression-gzip"]
compression-brotli = ["async-compression/brotli"]
Expand All @@ -79,6 +82,10 @@ required-features = ["multipart"]
name = "ws"
required-features = ["websocket"]

[[test]]
name = "path_empty_segments"
required-features = ["ignore-empty-path-segments"]

[[example]]
name = "compression"
required-features = ["compression"]
Expand Down
10 changes: 5 additions & 5 deletions examples/todos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ mod filters {
/// The 4 TODOs filters combined.
pub fn todos(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
todos_list(db.clone())
.or(todos_create(db.clone()))
.or(todos_update(db.clone()))
Expand All @@ -48,7 +48,7 @@ mod filters {
/// GET /todos?offset=3&limit=5
pub fn todos_list(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
warp::path!("todos")
.and(warp::get())
.and(warp::query::<ListOptions>())
Expand All @@ -59,7 +59,7 @@ mod filters {
/// POST /todos with JSON body
pub fn todos_create(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
warp::path!("todos")
.and(warp::post())
.and(json_body())
Expand All @@ -70,7 +70,7 @@ mod filters {
/// PUT /todos/:id with JSON body
pub fn todos_update(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
warp::path!("todos" / u64)
.and(warp::put())
.and(json_body())
Expand All @@ -81,7 +81,7 @@ mod filters {
/// DELETE /todos/:id
pub fn todos_delete(
db: Db,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
// We'll make one of our endpoints admin-only to show how authentication filters are used
let admin_only = warp::header::exact("authorization", "Bearer admin");

Expand Down
1 change: 1 addition & 0 deletions src/filters/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ impl Peek {

/// Get an iterator over the segments of the peeked path.
pub fn segments(&self) -> impl Iterator<Item = &str> {
// NOTE: this ignores empty segments regardless of feature settings
self.as_str().split('/').filter(|seg| !seg.is_empty())
}
}
Expand Down
25 changes: 17 additions & 8 deletions src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@ enum BodyState {

impl Route {
pub(crate) fn new(req: Request, remote_addr: Option<SocketAddr>) -> RefCell<Route> {
let segments_index = if req.uri().path().starts_with('/') {
// Skip the beginning slash.
1
} else {
0
};
let mut segments_index: usize = 0;
if req.uri().path().starts_with('/') {
segments_index += 1;
#[cfg(feature = "ignore-empty-path-segments")]
{
let path = req.uri().path().as_bytes();
while segments_index < path.len() && path[segments_index] == b'/' {
segments_index += 1;
}
}
}

RefCell::new(Route {
body: BodyState::Ready,
Expand Down Expand Up @@ -93,15 +98,19 @@ impl Route {

pub(crate) fn set_unmatched_path(&mut self, index: usize) {
let index = self.segments_index + index;
let path = self.req.uri().path();
let path = self.req.uri().path().as_bytes();
if path.is_empty() {
// malformed path
return;
} else if path.len() == index {
self.segments_index = index;
} else {
debug_assert_eq!(path.as_bytes()[index], b'/');
debug_assert_eq!(path[index], b'/');
self.segments_index = index + 1;
#[cfg(feature = "ignore-empty-path-segments")]
while self.segments_index < path.len() && path[self.segments_index] == b'/' {
self.segments_index += 1;
}
}
}

Expand Down