33
44use 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
810use axum:: body:: Body ;
911use 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