hyper_staticfile_jsutf8/
response_builder.rs

1use crate::resolve::ResolveResult;
2use crate::util::{FileResponseBuilder, RequestedPath};
3use http::response::Builder as HttpResponseBuilder;
4use http::{header, HeaderMap, Method, Request, Response, Result, StatusCode, Uri};
5use hyper::Body;
6
7/// Utility to build the default response for a `resolve` result.
8///
9/// This struct allows direct access to its fields, but these fields are typically initialized by
10/// the accessors, using the builder pattern. The fields are basically a bunch of settings that
11/// determine the response details.
12#[derive(Clone, Debug, Default)]
13pub struct ResponseBuilder<'a> {
14    /// The request path.
15    pub path: &'a str,
16    /// The request query string.
17    pub query: Option<&'a str>,
18    /// Inner file response builder.
19    pub file_response_builder: FileResponseBuilder,
20}
21
22impl<'a> ResponseBuilder<'a> {
23    /// Create a new builder with a default configuration.
24    pub fn new() -> Self {
25        Self::default()
26    }
27
28    /// Apply parameters based on a request.
29    pub fn request<B>(&mut self, req: &'a Request<B>) -> &mut Self {
30        self.request_parts(req.method(), req.uri(), req.headers());
31        self
32    }
33
34    /// Apply parameters based on request parts.
35    pub fn request_parts(
36        &mut self,
37        method: &Method,
38        uri: &'a Uri,
39        headers: &'a HeaderMap,
40    ) -> &mut Self {
41        self.request_uri(uri);
42        self.file_response_builder.request_parts(method, headers);
43        self
44    }
45
46    /// Apply parameters based on a request URI.
47    pub fn request_uri(&mut self, uri: &'a Uri) -> &mut Self {
48        self.path(uri.path());
49        self.query(uri.query());
50        self
51    }
52
53    /// Add cache headers to responses for the given lifespan.
54    pub fn cache_headers(&mut self, value: Option<u32>) -> &mut Self {
55        self.file_response_builder.cache_headers(value);
56        self
57    }
58
59    /// Set the request path.
60    pub fn path(&mut self, value: &'a str) -> &mut Self {
61        self.path = value;
62        self
63    }
64
65    /// Set the request query string.
66    pub fn query(&mut self, value: Option<&'a str>) -> &mut Self {
67        self.query = value;
68        self
69    }
70
71    /// Build a response for the given request and `resolve` result.
72    ///
73    /// This function may error if it response could not be constructed, but this should be a
74    /// seldom occurrence.
75    pub fn build(&self, result: ResolveResult) -> Result<Response<Body>> {
76        match result {
77            ResolveResult::MethodNotMatched => HttpResponseBuilder::new()
78                .status(StatusCode::BAD_REQUEST)
79                .body(Body::empty()),
80            ResolveResult::NotFound => HttpResponseBuilder::new()
81                .status(StatusCode::NOT_FOUND)
82                .body(Body::empty()),
83            ResolveResult::PermissionDenied => HttpResponseBuilder::new()
84                .status(StatusCode::FORBIDDEN)
85                .body(Body::empty()),
86            ResolveResult::IsDirectory => {
87                // NOTE: We are doing an origin-relative redirect, but need to use the sanitized
88                // path in order to prevent a malicious redirect to `//foo` (schema-relative).
89                // With the current API, we have no other option here than to do sanitization
90                // again, but a future version may reuse the earlier sanitization result.
91                let resolved = RequestedPath::resolve(self.path);
92
93                let mut target_len = resolved.sanitized.as_os_str().len() + 2;
94                if let Some(query) = self.query {
95                    target_len += query.len() + 1;
96                }
97
98                let mut target = String::with_capacity(target_len);
99                target.push('/');
100                // On Windows, we can't just append the entire path, because it contains Windows
101                // path separators. Append per-component instead.
102                for component in resolved.sanitized.components() {
103                    target.push_str(&component.as_os_str().to_string_lossy());
104                    target.push('/');
105                }
106
107                // Preserve any query string from the original request.
108                if let Some(query) = self.query {
109                    target.push('?');
110                    target.push_str(query);
111                }
112
113                HttpResponseBuilder::new()
114                    .status(StatusCode::MOVED_PERMANENTLY)
115                    .header(header::LOCATION, target)
116                    .body(Body::empty())
117            }
118            ResolveResult::Found(file, metadata, mime) => {
119                self.file_response_builder
120                    .build(file, metadata, mime.to_string())
121            }
122        }
123    }
124}