1- use super :: { cache:: CachePolicy , error:: Nope , redirect, redirect_base} ;
1+ use super :: {
2+ cache:: CachePolicy ,
3+ error:: { AxumNope , AxumResult } ,
4+ } ;
25use crate :: utils:: report_error;
36use 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 } ,
914} ;
15+ use chrono:: prelude:: * ;
16+ use httpdate:: fmt_http_date;
17+ use mime:: Mime ;
1018use mime_guess:: MimeGuess ;
11- use std:: { ffi:: OsStr , fs, path:: Path } ;
19+ use std:: { ffi:: OsStr , path, time:: SystemTime } ;
20+ use tokio:: fs;
1221
1322const VENDORED_CSS : & str = include_str ! ( concat!( env!( "OUT_DIR" ) , "/vendored.css" ) ) ;
1423const STYLE_CSS : & str = include_str ! ( concat!( env!( "OUT_DIR" ) , "/style.css" ) ) ;
@@ -17,29 +26,27 @@ const RUSTDOC_2021_12_05_CSS: &str =
1726 include_str ! ( concat!( env!( "OUT_DIR" ) , "/rustdoc-2021-12-05.css" ) ) ;
1827const STATIC_SEARCH_PATHS : & [ & str ] = & [ "static" , "vendor" ] ;
1928
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+ } ,
3441 } )
3542}
3643
37- fn serve_file ( file : & str ) -> IronResult < Response > {
44+ async fn serve_file ( file : & str ) -> AxumResult < impl IntoResponse > {
3845 // Find the first path that actually exists
3946 let path = STATIC_SEARCH_PATHS
4047 . iter ( )
4148 . find_map ( |root| {
42- let path = Path :: new ( root) . join ( file) ;
49+ let path = path :: Path :: new ( root) . join ( file) ;
4350 if !path. exists ( ) {
4451 return None ;
4552 }
@@ -55,96 +62,59 @@ fn serve_file(file: &str) -> IronResult<Response> {
5562 None
5663 }
5764 } )
58- . ok_or ( Nope :: ResourceNotFound ) ?;
65+ . ok_or ( AxumNope :: ResourceNotFound ) ?;
5966
6067 let contents = fs:: read ( & path)
68+ . await
6169 . with_context ( || format ! ( "failed to read static file {}" , path. display( ) ) )
6270 . map_err ( |e| {
6371 report_error ( & e) ;
64- Nope :: InternalServerError
72+ AxumNope :: InternalServerError
6573 } ) ?;
6674
6775 // If we can detect the file's mime type, set it
6876 // MimeGuess misses a lot of the file types we need, so there's a small wrapper
6977 // 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+ } ;
9091
91- Ok ( serve_resource ( contents, content_type) )
92+ Ok ( build_response ( contents, content_type) )
9293}
9394
94- fn serve_resource < R , C > ( resource : R , content_type : C ) -> Response
95+ fn build_response < R > ( resource : R , content_type : Mime ) -> Response
9596where
9697 R : AsRef < [ u8 ] > ,
97- C : Into < Option < ContentType > > ,
9898{
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 ( )
138110}
139111
140112#[ cfg( test) ]
141113mod tests {
142- use iron:: status:: Status ;
143-
144114 use super :: { serve_file, STATIC_SEARCH_PATHS , STYLE_CSS , VENDORED_CSS } ;
145115 use crate :: {
146116 test:: { assert_cache_control, wrapper} ,
147- web:: cache:: CachePolicy ,
117+ web:: { cache:: CachePolicy , error :: AxumNope } ,
148118 } ;
149119 use reqwest:: StatusCode ;
150120 use std:: fs;
@@ -276,8 +246,8 @@ mod tests {
276246 } ) ;
277247 }
278248
279- #[ test]
280- fn directory_traversal ( ) {
249+ #[ tokio :: test]
250+ async fn directory_traversal ( ) {
281251 const PATHS : & [ & str ] = & [
282252 "../LICENSE" ,
283253 "%2e%2e%2fLICENSE" ,
@@ -293,9 +263,8 @@ mod tests {
293263 // Still, the test ensures the underlying function called by the request handler to
294264 // serve the file also includes protection for path traversal, in the event we switch
295265 // 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 ) ) ,
299268 "{} did not return a 404" ,
300269 path
301270 ) ;
0 commit comments