1use core::fmt;
4
5use crate::{
6 io::{Read, Write},
7 request::Path,
8 routing::{PathRouter, PathRouterService, RequestHandler, RequestHandlerService},
9 ResponseSent,
10};
11
12use super::{IntoResponse, StatusCode};
13
14#[derive(Clone, PartialEq, Eq)]
15struct ETag([u8; 20]);
16
17impl fmt::Debug for ETag {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 write!(f, "ETag({self})")
20 }
21}
22
23impl fmt::Display for ETag {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "\"")?;
26 for b in self.0 {
27 write!(f, "{b:02x}")?;
28 }
29 write!(f, "\"")?;
30
31 Ok(())
32 }
33}
34
35impl PartialEq<[u8]> for ETag {
36 fn eq(&self, other: &[u8]) -> bool {
37 struct Eq;
38
39 fn eq(self_bytes: &[u8], other_str_bytes: &[u8]) -> Option<Eq> {
40 fn decode_hex_nibble(c: u8) -> Option<u8> {
41 Some(match c {
42 c @ b'0'..=b'9' => c - b'0',
43 c @ b'a'..=b'f' => 10 + c - b'a',
44 c @ b'A'..=b'F' => 10 + c - b'A',
45 _ => return None,
46 })
47 }
48
49 let mut other_str_bytes = other_str_bytes
50 .strip_prefix(b"\"")?
51 .strip_suffix(b"\"")?
52 .iter()
53 .copied();
54
55 for &self_byte in self_bytes {
56 let other_byte0 = decode_hex_nibble(other_str_bytes.next()?)?;
57 let other_byte1 = decode_hex_nibble(other_str_bytes.next()?)?;
58
59 let other_byte = 0x10 * other_byte0 + other_byte1;
60
61 if self_byte != other_byte {
62 return None;
63 }
64 }
65
66 other_str_bytes.next().is_none().then_some(Eq)
67 }
68
69 matches!(eq(&self.0, other), Some(Eq))
70 }
71}
72
73impl PartialEq<&[u8]> for ETag {
74 fn eq(&self, other: &&[u8]) -> bool {
75 *self == **other
76 }
77}
78
79impl super::HeadersIter for ETag {
80 async fn for_each_header<F: super::ForEachHeader>(
81 self,
82 mut f: F,
83 ) -> Result<F::Output, F::Error> {
84 f.call("ETag", self).await?;
85 f.finalize().await
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct File {
92 content_type: &'static str,
93 body: &'static [u8],
94 etag: ETag,
95 headers: &'static [(&'static str, &'static str)],
96}
97
98impl File {
99 pub const MIME_HTML: &'static str = "text/html; charset=utf-8";
100 pub const MIME_CSS: &'static str = "text/css";
101 pub const MIME_JS: &'static str = "application/javascript; charset=utf-8";
102
103 pub const fn with_content_type(content_type: &'static str, body: &'static [u8]) -> Self {
105 Self {
106 content_type,
107 body,
108 etag: ETag(const_sha1::sha1(body).as_bytes()),
109 headers: &[],
110 }
111 }
112
113 pub const fn with_content_type_and_headers(
115 content_type: &'static str,
116 body: &'static [u8],
117 headers: &'static [(&'static str, &'static str)],
118 ) -> Self {
119 Self {
120 content_type,
121 body,
122 etag: ETag(const_sha1::sha1(body).as_bytes()),
123 headers,
124 }
125 }
126
127 pub const fn html(body: &'static str) -> Self {
129 Self::with_content_type(Self::MIME_HTML, body.as_bytes())
130 }
131
132 pub const fn css(body: &'static str) -> Self {
134 Self::with_content_type(Self::MIME_CSS, body.as_bytes())
135 }
136
137 pub const fn javascript(body: &'static str) -> Self {
139 Self::with_content_type(Self::MIME_JS, body.as_bytes())
140 }
141}
142
143impl<State, PathParameters> crate::routing::RequestHandlerService<State, PathParameters> for File {
144 async fn call_request_handler_service<R: Read, W: super::ResponseWriter<Error = R::Error>>(
145 &self,
146 _state: &State,
147 _path_parameters: PathParameters,
148 request: crate::request::Request<'_, R>,
149 response_writer: W,
150 ) -> Result<ResponseSent, W::Error> {
151 if let Some(if_none_match) = request.parts.headers().get("If-None-Match") {
152 if if_none_match
153 .split(b',')
154 .any(|etag| self.etag == etag.as_raw())
155 {
156 return response_writer
157 .write_response(
158 request.body_connection.finalize().await?,
159 super::Response {
160 status_code: StatusCode::NOT_MODIFIED,
161 headers: self.etag.clone(),
162 body: super::NoBody,
163 },
164 )
165 .await;
166 }
167 }
168
169 struct FileContent<'a>(&'a File);
170
171 impl super::Content for FileContent<'_> {
172 fn content_type(&self) -> &'static str {
173 self.0.content_type
174 }
175
176 fn content_length(&self) -> usize {
177 self.0.body.len()
178 }
179
180 async fn write_content<W: Write>(self, mut writer: W) -> Result<(), W::Error> {
181 writer.write_all(self.0.body).await
182 }
183 }
184
185 super::Response::ok(FileContent(self))
186 .with_headers(self.headers)
187 .with_headers(self.etag.clone())
188 .write_to(request.body_connection.finalize().await?, response_writer)
189 .await
190 }
191}
192
193#[derive(Debug, Default)]
195pub struct Directory {
196 pub files: &'static [(&'static str, File)],
198
199 pub sub_directories: &'static [(&'static str, Directory)],
201}
202
203impl Directory {
204 pub const DEFAULT: Self = Self {
205 files: &[],
206 sub_directories: &[],
207 };
208
209 fn matching_file(&self, path: crate::request::Path) -> Option<&File> {
210 for (name, file) in self.files.iter() {
211 if let Some(crate::request::Path(crate::url_encoded::UrlEncodedString(""))) =
212 path.strip_slash_and_prefix(name)
213 {
214 return Some(file);
215 } else {
216 continue;
217 }
218 }
219
220 for (name, sub_directory) in self.sub_directories.iter() {
221 if let Some(path) = path.strip_slash_and_prefix(name) {
222 return sub_directory.matching_file(path);
223 } else {
224 continue;
225 }
226 }
227
228 None
229 }
230}
231
232impl<State, CurrentPathParameters> PathRouterService<State, CurrentPathParameters> for Directory {
233 async fn call_request_handler_service<R: Read, W: super::ResponseWriter<Error = R::Error>>(
234 &self,
235 state: &State,
236 current_path_parameters: CurrentPathParameters,
237 path: Path<'_>,
238 request: crate::request::Request<'_, R>,
239 response_writer: W,
240 ) -> Result<ResponseSent, W::Error> {
241 if !request.parts.method().eq_ignore_ascii_case("get") {
242 return crate::routing::MethodNotAllowed
243 .call_request_handler(state, current_path_parameters, request, response_writer)
244 .await;
245 }
246
247 if let Some(file) = self.matching_file(path) {
248 file.call_request_handler_service(
249 state,
250 current_path_parameters,
251 request,
252 response_writer,
253 )
254 .await
255 } else {
256 crate::routing::NotFound
257 .call_path_router(
258 state,
259 current_path_parameters,
260 path,
261 request,
262 response_writer,
263 )
264 .await
265 }
266 }
267}