1
- use super :: { cache:: CachePolicy , error:: Nope , redirect, redirect_base} ;
1
+ use super :: {
2
+ cache:: CachePolicy ,
3
+ error:: { AxumNope , AxumResult } ,
4
+ } ;
2
5
use crate :: utils:: report_error;
3
6
use anyhow:: Context ;
4
- use chrono:: prelude:: * ;
5
- use iron:: {
6
- headers:: { ContentLength , ContentType , LastModified } ,
7
- status:: Status ,
8
- IronResult , Request , Response , Url ,
7
+ use axum:: {
8
+ extract:: { Extension , Path } ,
9
+ http:: {
10
+ header:: { CONTENT_LENGTH , CONTENT_TYPE , LAST_MODIFIED } ,
11
+ StatusCode ,
12
+ } ,
13
+ response:: { IntoResponse , Response } ,
9
14
} ;
15
+ use chrono:: prelude:: * ;
16
+ use httpdate:: fmt_http_date;
17
+ use mime:: Mime ;
10
18
use mime_guess:: MimeGuess ;
11
- use std:: { ffi:: OsStr , fs, path:: Path } ;
19
+ use std:: { ffi:: OsStr , path, time:: SystemTime } ;
20
+ use tokio:: fs;
12
21
13
22
const VENDORED_CSS : & str = include_str ! ( concat!( env!( "OUT_DIR" ) , "/vendored.css" ) ) ;
14
23
const STYLE_CSS : & str = include_str ! ( concat!( env!( "OUT_DIR" ) , "/style.css" ) ) ;
@@ -17,29 +26,27 @@ const RUSTDOC_2021_12_05_CSS: &str =
17
26
include_str ! ( concat!( env!( "OUT_DIR" ) , "/rustdoc-2021-12-05.css" ) ) ;
18
27
const STATIC_SEARCH_PATHS : & [ & str ] = & [ "static" , "vendor" ] ;
19
28
20
- pub ( crate ) fn static_handler ( req : & mut Request ) -> IronResult < Response > {
21
- let mut file = req. url . path ( ) ;
22
- file. drain ( ..2 ) . for_each ( std:: mem:: drop) ;
23
- let file = file. join ( "/" ) ;
24
-
25
- Ok ( match file. as_str ( ) {
26
- "vendored.css" => serve_resource ( VENDORED_CSS , ContentType ( "text/css" . parse ( ) . unwrap ( ) ) ) ,
27
- "style.css" => serve_resource ( STYLE_CSS , ContentType ( "text/css" . parse ( ) . unwrap ( ) ) ) ,
28
- "rustdoc.css" => serve_resource ( RUSTDOC_CSS , ContentType ( "text/css" . parse ( ) . unwrap ( ) ) ) ,
29
- "rustdoc-2021-12-05.css" => serve_resource (
30
- RUSTDOC_2021_12_05_CSS ,
31
- ContentType ( "text/css" . parse ( ) . unwrap ( ) ) ,
32
- ) ,
33
- file => serve_file ( file) ?,
29
+ pub ( crate ) async fn static_handler ( Path ( path) : Path < String > ) -> AxumResult < impl IntoResponse > {
30
+ let text_css: Mime = "text/css" . parse ( ) . unwrap ( ) ;
31
+
32
+ Ok ( match path. as_str ( ) {
33
+ "/vendored.css" => build_response ( VENDORED_CSS , text_css) ,
34
+ "/style.css" => build_response ( STYLE_CSS , text_css) ,
35
+ "/rustdoc.css" => build_response ( RUSTDOC_CSS , text_css) ,
36
+ "/rustdoc-2021-12-05.css" => build_response ( RUSTDOC_2021_12_05_CSS , text_css) ,
37
+ file => match serve_file ( & file[ 1 ..] ) . await {
38
+ Ok ( response) => response. into_response ( ) ,
39
+ Err ( err) => return Err ( err) ,
40
+ } ,
34
41
} )
35
42
}
36
43
37
- fn serve_file ( file : & str ) -> IronResult < Response > {
44
+ async fn serve_file ( file : & str ) -> AxumResult < impl IntoResponse > {
38
45
// Find the first path that actually exists
39
46
let path = STATIC_SEARCH_PATHS
40
47
. iter ( )
41
48
. find_map ( |root| {
42
- let path = Path :: new ( root) . join ( file) ;
49
+ let path = path :: Path :: new ( root) . join ( file) ;
43
50
if !path. exists ( ) {
44
51
return None ;
45
52
}
@@ -55,96 +62,59 @@ fn serve_file(file: &str) -> IronResult<Response> {
55
62
None
56
63
}
57
64
} )
58
- . ok_or ( Nope :: ResourceNotFound ) ?;
65
+ . ok_or ( AxumNope :: ResourceNotFound ) ?;
59
66
60
67
let contents = fs:: read ( & path)
68
+ . await
61
69
. with_context ( || format ! ( "failed to read static file {}" , path. display( ) ) )
62
70
. map_err ( |e| {
63
71
report_error ( & e) ;
64
- Nope :: InternalServerError
72
+ AxumNope :: InternalServerError
65
73
} ) ?;
66
74
67
75
// If we can detect the file's mime type, set it
68
76
// MimeGuess misses a lot of the file types we need, so there's a small wrapper
69
77
// around it
70
- let mut content_type = path
71
- . extension ( )
72
- . and_then ( OsStr :: to_str)
73
- . and_then ( |ext| match ext {
74
- "eot" => Some ( ContentType (
75
- "application/vnd.ms-fontobject" . parse ( ) . unwrap ( ) ,
76
- ) ) ,
77
- "woff2" => Some ( ContentType ( "application/font-woff2" . parse ( ) . unwrap ( ) ) ) ,
78
- "ttf" => Some ( ContentType ( "application/x-font-ttf" . parse ( ) . unwrap ( ) ) ) ,
79
-
80
- _ => MimeGuess :: from_path ( & path)
81
- . first ( )
82
- . map ( |mime| ContentType ( mime. as_ref ( ) . parse ( ) . unwrap ( ) ) ) ,
83
- } ) ;
84
-
85
- if file == "opensearch.xml" {
86
- content_type = Some ( ContentType (
87
- "application/opensearchdescription+xml" . parse ( ) . unwrap ( ) ,
88
- ) ) ;
89
- }
78
+ let content_type: Mime = if file == "opensearch.xml" {
79
+ "application/opensearchdescription+xml" . parse ( ) . unwrap ( )
80
+ } else {
81
+ path. extension ( )
82
+ . and_then ( OsStr :: to_str)
83
+ . and_then ( |ext| match ext {
84
+ "eot" => Some ( "application/vnd.ms-fontobject" . parse ( ) . unwrap ( ) ) ,
85
+ "woff2" => Some ( "application/font-woff2" . parse ( ) . unwrap ( ) ) ,
86
+ "ttf" => Some ( "application/x-font-ttf" . parse ( ) . unwrap ( ) ) ,
87
+ _ => MimeGuess :: from_path ( & path) . first ( ) ,
88
+ } )
89
+ . unwrap_or ( mime:: APPLICATION_OCTET_STREAM )
90
+ } ;
90
91
91
- Ok ( serve_resource ( contents, content_type) )
92
+ Ok ( build_response ( contents, content_type) )
92
93
}
93
94
94
- fn serve_resource < R , C > ( resource : R , content_type : C ) -> Response
95
+ fn build_response < R > ( resource : R , content_type : Mime ) -> Response
95
96
where
96
97
R : AsRef < [ u8 ] > ,
97
- C : Into < Option < ContentType > > ,
98
98
{
99
- let mut response = Response :: with ( ( Status :: Ok , resource. as_ref ( ) ) ) ;
100
-
101
- response
102
- . extensions
103
- . insert :: < CachePolicy > ( CachePolicy :: ForeverInCdnAndBrowser ) ;
104
-
105
- response
106
- . headers
107
- . set ( ContentLength ( resource. as_ref ( ) . len ( ) as u64 ) ) ;
108
- response. headers . set ( LastModified (
109
- Utc :: now ( )
110
- . format ( "%a, %d %b %Y %T %Z" )
111
- . to_string ( )
112
- . parse ( )
113
- . unwrap ( ) ,
114
- ) ) ;
115
-
116
- if let Some ( content_type) = content_type. into ( ) {
117
- response. headers . set ( content_type) ;
118
- }
119
-
120
- response
121
- }
122
-
123
- pub ( super ) fn ico_handler ( req : & mut Request ) -> IronResult < Response > {
124
- if let Some ( & "favicon.ico" ) = req. url . path ( ) . last ( ) {
125
- // if we're looking for exactly "favicon.ico", we need to defer to the handler that
126
- // actually serves it, so return a 404 here to make the main handler carry on
127
- Err ( Nope :: ResourceNotFound . into ( ) )
128
- } else {
129
- // if we're looking for something like "favicon-20190317-1.35.0-nightly-c82834e2b.ico",
130
- // redirect to the plain one so that the above branch can trigger with the correct filename
131
- let url = ctry ! (
132
- req,
133
- Url :: parse( & format!( "{}/favicon.ico" , redirect_base( req) ) ) ,
134
- ) ;
135
-
136
- Ok ( redirect ( url) )
137
- }
99
+ (
100
+ StatusCode :: OK ,
101
+ Extension ( CachePolicy :: ForeverInCdnAndBrowser ) ,
102
+ [
103
+ ( CONTENT_LENGTH , resource. as_ref ( ) . len ( ) . to_string ( ) ) ,
104
+ ( CONTENT_TYPE , content_type. to_string ( ) ) ,
105
+ ( LAST_MODIFIED , fmt_http_date ( SystemTime :: from ( Utc :: now ( ) ) ) ) ,
106
+ ] ,
107
+ resource. as_ref ( ) . to_vec ( ) ,
108
+ )
109
+ . into_response ( )
138
110
}
139
111
140
112
#[ cfg( test) ]
141
113
mod tests {
142
- use iron:: status:: Status ;
143
-
144
114
use super :: { serve_file, STATIC_SEARCH_PATHS , STYLE_CSS , VENDORED_CSS } ;
145
115
use crate :: {
146
116
test:: { assert_cache_control, wrapper} ,
147
- web:: cache:: CachePolicy ,
117
+ web:: { cache:: CachePolicy , error :: AxumNope } ,
148
118
} ;
149
119
use reqwest:: StatusCode ;
150
120
use std:: fs;
@@ -276,8 +246,8 @@ mod tests {
276
246
} ) ;
277
247
}
278
248
279
- #[ test]
280
- fn directory_traversal ( ) {
249
+ #[ tokio :: test]
250
+ async fn directory_traversal ( ) {
281
251
const PATHS : & [ & str ] = & [
282
252
"../LICENSE" ,
283
253
"%2e%2e%2fLICENSE" ,
@@ -293,9 +263,8 @@ mod tests {
293
263
// Still, the test ensures the underlying function called by the request handler to
294
264
// serve the file also includes protection for path traversal, in the event we switch
295
265
// to a framework that doesn't include builtin protection in the future.
296
- assert_eq ! (
297
- Some ( Status :: NotFound ) ,
298
- serve_file( path) . unwrap_err( ) . response. status,
266
+ assert ! (
267
+ matches!( serve_file( path) . await , Err ( AxumNope :: ResourceNotFound ) ) ,
299
268
"{} did not return a 404" ,
300
269
path
301
270
) ;
0 commit comments