1
1
use std:: {
2
- io:: { Error as IoError , ErrorKind as IoErrorKind } ,
2
+ future:: Future ,
3
+ io:: { Error as IoError , ErrorKind as IoErrorKind , Result as IoResult } ,
3
4
ops:: BitAnd ,
4
5
path:: PathBuf ,
5
6
sync:: Arc ,
6
7
time:: SystemTime ,
7
8
} ;
8
9
10
+ use futures_util:: future:: BoxFuture ;
9
11
use http:: { header, HeaderValue , Method , Request } ;
10
12
use mime_guess:: MimeGuess ;
11
13
use tokio:: fs:: File ;
@@ -70,6 +72,32 @@ pub struct Resolver<O = TokioFileOpener> {
70
72
///
71
73
/// Typically initialized with `AcceptEncoding::all()` or `AcceptEncoding::none()`.
72
74
pub allowed_encodings : AcceptEncoding ,
75
+
76
+ /// Optional function that can rewrite requests.
77
+ ///
78
+ /// This function is called after parsing the request and before querying the filesystem.
79
+ ///
80
+ /// See `set_rewrite` for a convenience setter that simplifies these types.
81
+ pub rewrite : Option < Arc < dyn ( Fn ( ResolveParams ) -> BoxRewriteFuture ) + Send + Sync > > ,
82
+ }
83
+
84
+ /// Future returned by a rewrite function. See `Resolver::set_rewrite`.
85
+ pub type BoxRewriteFuture = BoxFuture < ' static , IoResult < ResolveParams > > ;
86
+
87
+ /// All of the parsed request parameters used in resolving a file.
88
+ ///
89
+ /// This struct is primarily used for `Resolver::rewrite` / `Resolver::set_rewrite`.
90
+ #[ derive( Debug , Clone ) ]
91
+ pub struct ResolveParams {
92
+ /// Sanitized path of the request.
93
+ pub path : PathBuf ,
94
+ /// Whether a directory was requested. (The request path ended with a slash.)
95
+ pub is_dir_request : bool ,
96
+ /// Intersection of the request `Accept-Encoding` header and `allowed_encodings`.
97
+ ///
98
+ /// Only modify this field to disable encodings. Enabling additional encodings here may cause
99
+ /// a client to receive encodings it does not understand.
100
+ pub accept_encoding : AcceptEncoding ,
73
101
}
74
102
75
103
/// The result of `Resolver` methods.
@@ -93,7 +121,7 @@ pub enum ResolveResult<F = File> {
93
121
}
94
122
95
123
/// Some IO errors are expected when serving files, and mapped to a regular result here.
96
- fn map_open_err < F > ( err : IoError ) -> Result < ResolveResult < F > , IoError > {
124
+ fn map_open_err < F > ( err : IoError ) -> IoResult < ResolveResult < F > > {
97
125
match err. kind ( ) {
98
126
IoErrorKind :: NotFound => Ok ( ResolveResult :: NotFound ) ,
99
127
IoErrorKind :: PermissionDenied => Ok ( ResolveResult :: PermissionDenied ) ,
@@ -114,18 +142,38 @@ impl<O: FileOpener> Resolver<O> {
114
142
Self {
115
143
opener : Arc :: new ( opener) ,
116
144
allowed_encodings : AcceptEncoding :: none ( ) ,
145
+ rewrite : None ,
117
146
}
118
147
}
119
148
149
+ /// Configure a function that can rewrite requests.
150
+ ///
151
+ /// This function is called after parsing the request and before querying the filesystem.
152
+ ///
153
+ /// ```rust
154
+ /// let mut resolver = hyper_staticfile::Resolver::new("/");
155
+ /// resolver.set_rewrite(|mut params| async move {
156
+ /// if params.path.extension() == Some("htm".as_ref()) {
157
+ /// params.path.set_extension("html");
158
+ /// }
159
+ /// Ok(params)
160
+ /// });
161
+ /// ```
162
+ pub fn set_rewrite < R , F > ( & mut self , rewrite : F ) -> & mut Self
163
+ where
164
+ R : Future < Output = IoResult < ResolveParams > > + Send + ' static ,
165
+ F : ( Fn ( ResolveParams ) -> R ) + Send + Sync + ' static ,
166
+ {
167
+ self . rewrite = Some ( Arc :: new ( move |params| Box :: pin ( rewrite ( params) ) ) ) ;
168
+ self
169
+ }
170
+
120
171
/// Resolve the request by trying to find the file in the root.
121
172
///
122
173
/// The returned future may error for unexpected IO errors, passing on the `std::io::Error`.
123
174
/// Certain expected IO errors are handled, though, and simply reflected in the result. These are
124
175
/// `NotFound` and `PermissionDenied`.
125
- pub async fn resolve_request < B > (
126
- & self ,
127
- req : & Request < B > ,
128
- ) -> Result < ResolveResult < O :: File > , IoError > {
176
+ pub async fn resolve_request < B > ( & self , req : & Request < B > ) -> IoResult < ResolveResult < O :: File > > {
129
177
// Handle only `GET`/`HEAD` and absolute paths.
130
178
match * req. method ( ) {
131
179
Method :: HEAD | Method :: GET => { }
@@ -157,12 +205,26 @@ impl<O: FileOpener> Resolver<O> {
157
205
& self ,
158
206
request_path : & str ,
159
207
accept_encoding : AcceptEncoding ,
160
- ) -> Result < ResolveResult < O :: File > , IoError > {
208
+ ) -> IoResult < ResolveResult < O :: File > > {
161
209
// Sanitize input path.
162
- let RequestedPath {
163
- sanitized : mut path,
210
+ let requested_path = RequestedPath :: resolve ( request_path) ;
211
+
212
+ // Apply optional rewrite.
213
+ let ResolveParams {
214
+ mut path,
164
215
is_dir_request,
165
- } = RequestedPath :: resolve ( request_path) ;
216
+ accept_encoding,
217
+ } = {
218
+ let mut params = ResolveParams {
219
+ path : requested_path. sanitized ,
220
+ is_dir_request : requested_path. is_dir_request ,
221
+ accept_encoding,
222
+ } ;
223
+ if let Some ( ref rewrite) = self . rewrite {
224
+ params = rewrite ( params) . await ?;
225
+ }
226
+ params
227
+ } ;
166
228
167
229
// Try to open the file.
168
230
let file = match self . opener . open ( & path) . await {
@@ -219,7 +281,7 @@ impl<O: FileOpener> Resolver<O> {
219
281
file : FileWithMetadata < O :: File > ,
220
282
path : PathBuf ,
221
283
accept_encoding : AcceptEncoding ,
222
- ) -> Result < ResolveResult < O :: File > , IoError > {
284
+ ) -> IoResult < ResolveResult < O :: File > > {
223
285
// Determine MIME-type. This needs to happen before we resolve a pre-encoded file.
224
286
let mime = MimeGuess :: from_path ( & path)
225
287
. first ( )
@@ -263,6 +325,7 @@ impl<O> Clone for Resolver<O> {
263
325
Self {
264
326
opener : self . opener . clone ( ) ,
265
327
allowed_encodings : self . allowed_encodings ,
328
+ rewrite : self . rewrite . clone ( ) ,
266
329
}
267
330
}
268
331
}
0 commit comments