tower_http/services/fs/serve_dir/
future.rs

1use super::{
2    open_file::{FileOpened, FileRequestExtent, OpenFileOutput},
3    DefaultServeDirFallback, ResponseBody,
4};
5use crate::{
6    body::UnsyncBoxBody, content_encoding::Encoding, services::fs::AsyncReadBody, BoxError,
7};
8use bytes::Bytes;
9use futures_util::future::{BoxFuture, FutureExt, TryFutureExt};
10use http::{
11    header::{self, ALLOW},
12    HeaderValue, Request, Response, StatusCode,
13};
14use http_body_util::{BodyExt, Empty, Full};
15use pin_project_lite::pin_project;
16use std::{
17    convert::Infallible,
18    future::Future,
19    io,
20    pin::Pin,
21    task::{ready, Context, Poll},
22};
23use tower_service::Service;
24
25pin_project! {
26    /// Response future of [`ServeDir::try_call()`][`super::ServeDir::try_call()`].
27    pub struct ResponseFuture<ReqBody, F = DefaultServeDirFallback> {
28        #[pin]
29        pub(super) inner: ResponseFutureInner<ReqBody, F>,
30    }
31}
32
33impl<ReqBody, F> ResponseFuture<ReqBody, F> {
34    pub(super) fn open_file_future(
35        future: BoxFuture<'static, io::Result<OpenFileOutput>>,
36        fallback_and_request: Option<(F, Request<ReqBody>)>,
37    ) -> Self {
38        Self {
39            inner: ResponseFutureInner::OpenFileFuture {
40                future,
41                fallback_and_request,
42            },
43        }
44    }
45
46    pub(super) fn invalid_path(fallback_and_request: Option<(F, Request<ReqBody>)>) -> Self {
47        Self {
48            inner: ResponseFutureInner::InvalidPath {
49                fallback_and_request,
50            },
51        }
52    }
53
54    pub(super) fn method_not_allowed() -> Self {
55        Self {
56            inner: ResponseFutureInner::MethodNotAllowed,
57        }
58    }
59}
60
61pin_project! {
62    #[project = ResponseFutureInnerProj]
63    pub(super) enum ResponseFutureInner<ReqBody, F> {
64        OpenFileFuture {
65            #[pin]
66            future: BoxFuture<'static, io::Result<OpenFileOutput>>,
67            fallback_and_request: Option<(F, Request<ReqBody>)>,
68        },
69        FallbackFuture {
70            future: BoxFuture<'static, Result<Response<ResponseBody>, Infallible>>,
71        },
72        InvalidPath {
73            fallback_and_request: Option<(F, Request<ReqBody>)>,
74        },
75        MethodNotAllowed,
76    }
77}
78
79impl<F, ReqBody, ResBody> Future for ResponseFuture<ReqBody, F>
80where
81    F: Service<Request<ReqBody>, Response = Response<ResBody>, Error = Infallible> + Clone,
82    F::Future: Send + 'static,
83    ResBody: http_body::Body<Data = Bytes> + Send + 'static,
84    ResBody::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
85{
86    type Output = io::Result<Response<ResponseBody>>;
87
88    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
89        loop {
90            let mut this = self.as_mut().project();
91
92            let new_state = match this.inner.as_mut().project() {
93                ResponseFutureInnerProj::OpenFileFuture {
94                    future: open_file_future,
95                    fallback_and_request,
96                } => match ready!(open_file_future.poll(cx)) {
97                    Ok(OpenFileOutput::FileOpened(file_output)) => {
98                        break Poll::Ready(Ok(build_response(*file_output)));
99                    }
100
101                    Ok(OpenFileOutput::Redirect { location }) => {
102                        let mut res = response_with_status(StatusCode::TEMPORARY_REDIRECT);
103                        res.headers_mut().insert(http::header::LOCATION, location);
104                        break Poll::Ready(Ok(res));
105                    }
106
107                    Ok(OpenFileOutput::FileNotFound) => {
108                        if let Some((mut fallback, request)) = fallback_and_request.take() {
109                            call_fallback(&mut fallback, request)
110                        } else {
111                            break Poll::Ready(Ok(not_found()));
112                        }
113                    }
114
115                    Ok(OpenFileOutput::PreconditionFailed) => {
116                        break Poll::Ready(Ok(response_with_status(
117                            StatusCode::PRECONDITION_FAILED,
118                        )));
119                    }
120
121                    Ok(OpenFileOutput::NotModified) => {
122                        break Poll::Ready(Ok(response_with_status(StatusCode::NOT_MODIFIED)));
123                    }
124
125                    Ok(OpenFileOutput::InvalidRedirectUri) => {
126                        break Poll::Ready(Ok(response_with_status(
127                            StatusCode::INTERNAL_SERVER_ERROR,
128                        )));
129                    }
130
131                    Err(err) => {
132                        #[cfg(unix)]
133                        // 20 = libc::ENOTDIR => "not a directory
134                        // when `io_error_more` landed, this can be changed
135                        // to checking for `io::ErrorKind::NotADirectory`.
136                        // https://github.com/rust-lang/rust/issues/86442
137                        let error_is_not_a_directory = err.raw_os_error() == Some(20);
138                        #[cfg(not(unix))]
139                        let error_is_not_a_directory = false;
140
141                        if matches!(
142                            err.kind(),
143                            io::ErrorKind::NotFound | io::ErrorKind::PermissionDenied
144                        ) || error_is_not_a_directory
145                        {
146                            if let Some((mut fallback, request)) = fallback_and_request.take() {
147                                call_fallback(&mut fallback, request)
148                            } else {
149                                break Poll::Ready(Ok(not_found()));
150                            }
151                        } else {
152                            break Poll::Ready(Err(err));
153                        }
154                    }
155                },
156
157                ResponseFutureInnerProj::FallbackFuture { future } => {
158                    break Pin::new(future).poll(cx).map_err(|err| match err {})
159                }
160
161                ResponseFutureInnerProj::InvalidPath {
162                    fallback_and_request,
163                } => {
164                    if let Some((mut fallback, request)) = fallback_and_request.take() {
165                        call_fallback(&mut fallback, request)
166                    } else {
167                        break Poll::Ready(Ok(not_found()));
168                    }
169                }
170
171                ResponseFutureInnerProj::MethodNotAllowed => {
172                    let mut res = response_with_status(StatusCode::METHOD_NOT_ALLOWED);
173                    res.headers_mut()
174                        .insert(ALLOW, HeaderValue::from_static("GET,HEAD"));
175                    break Poll::Ready(Ok(res));
176                }
177            };
178
179            this.inner.set(new_state);
180        }
181    }
182}
183
184fn response_with_status(status: StatusCode) -> Response<ResponseBody> {
185    Response::builder()
186        .status(status)
187        .body(empty_body())
188        .unwrap()
189}
190
191fn not_found() -> Response<ResponseBody> {
192    response_with_status(StatusCode::NOT_FOUND)
193}
194
195pub(super) fn call_fallback<F, B, FResBody>(
196    fallback: &mut F,
197    req: Request<B>,
198) -> ResponseFutureInner<B, F>
199where
200    F: Service<Request<B>, Response = Response<FResBody>, Error = Infallible> + Clone,
201    F::Future: Send + 'static,
202    FResBody: http_body::Body<Data = Bytes> + Send + 'static,
203    FResBody::Error: Into<BoxError>,
204{
205    let future = fallback
206        .call(req)
207        .map_ok(|response| {
208            response
209                .map(|body| {
210                    UnsyncBoxBody::new(
211                        body.map_err(|err| match err.into().downcast::<io::Error>() {
212                            Ok(err) => *err,
213                            Err(err) => io::Error::new(io::ErrorKind::Other, err),
214                        })
215                        .boxed_unsync(),
216                    )
217                })
218                .map(ResponseBody::new)
219        })
220        .boxed();
221
222    ResponseFutureInner::FallbackFuture { future }
223}
224
225fn build_response(output: FileOpened) -> Response<ResponseBody> {
226    let (maybe_file, size) = match output.extent {
227        FileRequestExtent::Full(file, meta) => (Some(file), meta.len()),
228        FileRequestExtent::Head(meta) => (None, meta.len()),
229    };
230
231    let mut builder = Response::builder()
232        .header(header::CONTENT_TYPE, output.mime_header_value)
233        .header(header::ACCEPT_RANGES, "bytes");
234
235    if let Some(encoding) = output
236        .maybe_encoding
237        .filter(|encoding| *encoding != Encoding::Identity)
238    {
239        builder = builder.header(header::CONTENT_ENCODING, encoding.into_header_value());
240    }
241
242    if let Some(last_modified) = output.last_modified {
243        builder = builder.header(header::LAST_MODIFIED, last_modified.0.to_string());
244    }
245
246    match output.maybe_range {
247        Some(Ok(ranges)) => {
248            if let Some(range) = ranges.first() {
249                if ranges.len() > 1 {
250                    builder
251                        .header(header::CONTENT_RANGE, format!("bytes */{}", size))
252                        .status(StatusCode::RANGE_NOT_SATISFIABLE)
253                        .body(body_from_bytes(Bytes::from(
254                            "Cannot serve multipart range requests",
255                        )))
256                        .unwrap()
257                } else {
258                    let body = if let Some(file) = maybe_file {
259                        let range_size = range.end() - range.start() + 1;
260                        ResponseBody::new(UnsyncBoxBody::new(
261                            AsyncReadBody::with_capacity_limited(
262                                file,
263                                output.chunk_size,
264                                range_size,
265                            )
266                            .boxed_unsync(),
267                        ))
268                    } else {
269                        empty_body()
270                    };
271
272                    let content_length = if size == 0 {
273                        0
274                    } else {
275                        range.end() - range.start() + 1
276                    };
277
278                    builder
279                        .header(
280                            header::CONTENT_RANGE,
281                            format!("bytes {}-{}/{}", range.start(), range.end(), size),
282                        )
283                        .header(header::CONTENT_LENGTH, content_length)
284                        .status(StatusCode::PARTIAL_CONTENT)
285                        .body(body)
286                        .unwrap()
287                }
288            } else {
289                builder
290                    .header(header::CONTENT_RANGE, format!("bytes */{}", size))
291                    .status(StatusCode::RANGE_NOT_SATISFIABLE)
292                    .body(body_from_bytes(Bytes::from(
293                        "No range found after parsing range header, please file an issue",
294                    )))
295                    .unwrap()
296            }
297        }
298
299        Some(Err(_)) => builder
300            .header(header::CONTENT_RANGE, format!("bytes */{}", size))
301            .status(StatusCode::RANGE_NOT_SATISFIABLE)
302            .body(empty_body())
303            .unwrap(),
304
305        // Not a range request
306        None => {
307            let body = if let Some(file) = maybe_file {
308                ResponseBody::new(UnsyncBoxBody::new(
309                    AsyncReadBody::with_capacity(file, output.chunk_size).boxed_unsync(),
310                ))
311            } else {
312                empty_body()
313            };
314
315            builder
316                .header(header::CONTENT_LENGTH, size.to_string())
317                .body(body)
318                .unwrap()
319        }
320    }
321}
322
323fn body_from_bytes(bytes: Bytes) -> ResponseBody {
324    let body = Full::from(bytes).map_err(|err| match err {}).boxed_unsync();
325    ResponseBody::new(UnsyncBoxBody::new(body))
326}
327
328fn empty_body() -> ResponseBody {
329    let body = Empty::new().map_err(|err| match err {}).boxed_unsync();
330    ResponseBody::new(UnsyncBoxBody::new(body))
331}