Skip to content

Commit cdde52e

Browse files
Sriramrvolosatovs
Sriram
andcommitted
feat: parse path with multiple slashes
Co-authored-by: Roman <[email protected]> Signed-off by: Sriram <[email protected]> Signed-off-by: Sriram <[email protected]>
1 parent cf6efc6 commit cdde52e

File tree

1 file changed

+104
-30
lines changed

1 file changed

+104
-30
lines changed

crates/server/src/handle.rs

+104-30
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
use super::{repos, tags, trees, users};
55

6-
use drawbridge_type::{RepositoryName, TagName, TreePath, UserName};
6+
use drawbridge_type::{RepositoryName, TagName, TreeName, TreePath, UserName};
7+
8+
use std::str::FromStr;
79

810
use axum::body::Body;
911
use axum::handler::Handler;
@@ -28,7 +30,7 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
2830
}
2931

3032
trace!(target: "app::handle", "begin HTTP request handling {:?}", req);
31-
let path = req.uri().path().trim_start_matches('/');
33+
let path = req.uri().path().trim_matches('/');
3234
let (ver, path) = path
3335
.strip_prefix("api")
3436
.ok_or_else(|| not_found(path))?
@@ -52,23 +54,29 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
5254
let (head, tail) = path
5355
.trim_start_matches('/')
5456
.split_once("/_")
55-
.map(|(left, right)| (left.to_string(), format!("_{right}")))
56-
.unwrap_or((path.to_string(), "".into()));
57+
.map(|(left, right)| (left.trim_end_matches('/').to_string(), format!("_{right}")))
58+
.unwrap_or((path.to_string(), "".to_string()));
5759
if head.is_empty() {
5860
return Err(not_found(path));
5961
}
6062

6163
let extensions = req.extensions_mut();
6264

63-
let (user, head) = head.split_once('/').unwrap_or((&head, ""));
64-
let user = user.parse::<UserName>().map_err(|e| {
65-
(
66-
StatusCode::BAD_REQUEST,
67-
format!("Failed to parse user name: {e}"),
68-
)
69-
})?;
65+
let (user, head) = head
66+
.split_once('/')
67+
.unwrap_or((head.trim_start_matches('/'), ""));
68+
let user = user
69+
.trim_end_matches('/')
70+
.parse::<UserName>()
71+
.map_err(|e| {
72+
(
73+
StatusCode::BAD_REQUEST,
74+
format!("Failed to parse user name: {e}"),
75+
)
76+
})?;
7077
trace!(target: "app::handle", "parsed user name: `{user}`");
7178
assert_eq!(extensions.insert(user), None, "duplicate user name");
79+
let head = head.trim_start_matches('/');
7280
if head.is_empty() {
7381
return match *req.method() {
7482
Method::HEAD => Ok(users::head.into_service().call(req).await.into_response()),
@@ -81,16 +89,20 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
8189
};
8290
}
8391

84-
let repo = head.parse::<RepositoryName>().map_err(|e| {
85-
(
86-
StatusCode::BAD_REQUEST,
87-
format!("Failed to parse repository name: {e}"),
88-
)
89-
})?;
92+
let repo = head
93+
.trim_end_matches('/')
94+
.parse::<RepositoryName>()
95+
.map_err(|e| {
96+
(
97+
StatusCode::BAD_REQUEST,
98+
format!("Failed to parse repository name: {e}"),
99+
)
100+
})?;
90101
trace!(target: "app::handle", "parsed repository name: `{repo}`");
91102
assert_eq!(extensions.insert(repo), None, "duplicate repository name");
92103

93-
let mut tail = tail.splitn(4, '/');
104+
let mut tail = tail.split('/').filter(|x| !x.is_empty());
105+
94106
match (tail.next(), tail.next(), tail.next()) {
95107
(None | Some(""), None, None) => match *req.method() {
96108
Method::HEAD => Ok(repos::head.into_service().call(req).await.into_response()),
@@ -109,12 +121,16 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
109121
)),
110122
},
111123
(Some("_tag"), Some(tag), prop @ (None | Some("tree"))) => {
112-
let tag = tag.parse::<TagName>().map_err(|e| {
113-
(
114-
StatusCode::BAD_REQUEST,
115-
format!("Failed to parse tag name: {e}"),
116-
)
117-
})?;
124+
let tag = tag
125+
.trim_start_matches('/')
126+
.trim_end_matches('/')
127+
.parse::<TagName>()
128+
.map_err(|e| {
129+
(
130+
StatusCode::BAD_REQUEST,
131+
format!("Failed to parse tag name: {e}"),
132+
)
133+
})?;
118134
trace!(target: "app::handle", "parsed tag name: `{tag}`");
119135
assert_eq!(extensions.insert(tag), None, "duplicate tag name");
120136

@@ -130,12 +146,15 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
130146
};
131147
}
132148

133-
let path = tail.next().unwrap_or("").parse::<TreePath>().map_err(|e| {
134-
(
135-
StatusCode::BAD_REQUEST,
136-
format!("Failed to parse tree path: {e}"),
137-
)
138-
})?;
149+
let path = tail
150+
.map(TreeName::from_str)
151+
.collect::<Result<TreePath, _>>()
152+
.map_err(|e| {
153+
(
154+
StatusCode::BAD_REQUEST,
155+
format!("Failed to parse tree path: {e}"),
156+
)
157+
})?;
139158
trace!(target: "app::handle", "parsed tree path: `{path}`");
140159
assert_eq!(extensions.insert(path), None, "duplicate tree path");
141160
match *req.method() {
@@ -154,3 +173,58 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
154173
)),
155174
}
156175
}
176+
177+
#[async_std::test]
178+
async fn multiple_slashes_missing() {
179+
let request = Request::builder()
180+
.uri("https://www.rust-lang.org/")
181+
.header("User-Agent", "my-awesome-agent/1.0")
182+
.body(hyper::Body::empty());
183+
println!("{:?}", request);
184+
185+
let res = handle(request.unwrap()).await;
186+
187+
// Temporary print to ensure test is working as intended.
188+
// println!("{}", res.into_response().status());
189+
assert_eq!(res.into_response().status(), 404);
190+
191+
let request = Request::builder()
192+
.uri("https://www.rust-lang.org///")
193+
.header("User-Agent", "my-awesome-agent///1.0")
194+
.body(hyper::Body::empty());
195+
println!("{:?}", request);
196+
197+
let res = handle(request.unwrap()).await;
198+
199+
// Temporary print to ensure test is working as intended.
200+
// println!("{}", res.into_response().status());
201+
assert_eq!(res.into_response().status(), 404);
202+
return ();
203+
}
204+
205+
/// Unit test to handle multiple slash path parsing
206+
#[async_std::test]
207+
async fn multiple_slashes_found() {
208+
let request = Request::builder()
209+
.uri("http://httpbin.org/ip")
210+
.body(hyper::Body::empty());
211+
println!("{:?}", request);
212+
213+
let res = handle(request.unwrap()).await;
214+
215+
// Temporary print to ensure test is working as intended.
216+
// println!("{}", res.into_response().status());
217+
assert_eq!(res.into_response().status(), 200);
218+
219+
let request = Request::builder()
220+
.uri("http://httpbin.org///ip")
221+
.body(hyper::Body::empty());
222+
println!("{:?}", request);
223+
224+
let res = handle(request.unwrap()).await;
225+
226+
// Temporary print to ensure test is working as intended.
227+
// println!("{}", res.into_response().status());
228+
assert_eq!(res.into_response().status(), 200);
229+
return ();
230+
}

0 commit comments

Comments
 (0)