3
3
4
4
use super :: { repos, tags, trees, users} ;
5
5
6
- use drawbridge_type:: { RepositoryName , TagName , TreePath , UserName } ;
6
+ use drawbridge_type:: { RepositoryName , TagName , TreeName , TreePath , UserName } ;
7
+
8
+ use std:: str:: FromStr ;
7
9
8
10
use axum:: body:: Body ;
9
11
use axum:: handler:: Handler ;
@@ -28,7 +30,7 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
28
30
}
29
31
30
32
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 ( '/' ) ;
32
34
let ( ver, path) = path
33
35
. strip_prefix ( "api" )
34
36
. ok_or_else ( || not_found ( path) ) ?
@@ -52,23 +54,29 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
52
54
let ( head, tail) = path
53
55
. trim_start_matches ( '/' )
54
56
. 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 ( ) ) ) ;
57
59
if head. is_empty ( ) {
58
60
return Err ( not_found ( path) ) ;
59
61
}
60
62
61
63
let extensions = req. extensions_mut ( ) ;
62
64
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
+ } ) ?;
70
77
trace ! ( target: "app::handle" , "parsed user name: `{user}`" ) ;
71
78
assert_eq ! ( extensions. insert( user) , None , "duplicate user name" ) ;
79
+ let head = head. trim_start_matches ( '/' ) ;
72
80
if head. is_empty ( ) {
73
81
return match * req. method ( ) {
74
82
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 {
81
89
} ;
82
90
}
83
91
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
+ } ) ?;
90
101
trace ! ( target: "app::handle" , "parsed repository name: `{repo}`" ) ;
91
102
assert_eq ! ( extensions. insert( repo) , None , "duplicate repository name" ) ;
92
103
93
- let mut tail = tail. splitn ( 4 , '/' ) ;
104
+ let mut tail = tail. split ( '/' ) . filter ( |x| !x. is_empty ( ) ) ;
105
+
94
106
match ( tail. next ( ) , tail. next ( ) , tail. next ( ) ) {
95
107
( None | Some ( "" ) , None , None ) => match * req. method ( ) {
96
108
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 {
109
121
) ) ,
110
122
} ,
111
123
( 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
+ } ) ?;
118
134
trace ! ( target: "app::handle" , "parsed tag name: `{tag}`" ) ;
119
135
assert_eq ! ( extensions. insert( tag) , None , "duplicate tag name" ) ;
120
136
@@ -130,12 +146,15 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
130
146
} ;
131
147
}
132
148
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
+ } ) ?;
139
158
trace ! ( target: "app::handle" , "parsed tree path: `{path}`" ) ;
140
159
assert_eq ! ( extensions. insert( path) , None , "duplicate tree path" ) ;
141
160
match * req. method ( ) {
@@ -154,3 +173,58 @@ pub async fn handle(mut req: Request<Body>) -> impl IntoResponse {
154
173
) ) ,
155
174
}
156
175
}
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