1use bytes::Bytes;
3
4use crate::config::{FileServeConfig, FsTaskSpawner};
5use crate::headers::cd::{self, ContentDisposition, DispositionType};
6use crate::utils;
7use crate::headers::range::HttpRange;
8use crate::body::Body;
9
10use std::io::{self, Seek, Read};
11use std::path::Path;
12use std::fs;
13use core::{mem, cmp, task};
14use core::str::FromStr;
15use core::marker::PhantomData;
16use core::future::Future;
17use core::pin::Pin;
18
19pub type FileReadResult = Result<Bytes, io::Error>;
21
22pub fn if_match(etag: &etag::EntityTag, headers: &http::header::HeaderMap) -> bool {
26 match headers.get(http::header::IF_MATCH).and_then(|header| header.to_str().ok()) {
27 Some(header) => {
28 for header_tag in header.split(',').map(|tag| tag.trim()) {
29 match header_tag.parse::<etag::EntityTag>() {
30 Ok(header_tag) => match etag.strong_eq(&header_tag) {
31 true => return true,
32 false => (),
33 },
34 Err(_) => ()
35 }
36 }
37 false
38 },
39 None => true
40 }
41}
42
43pub fn if_none_match(etag: &etag::EntityTag, headers: &http::header::HeaderMap) -> bool {
47 match headers.get(http::header::IF_NONE_MATCH).and_then(|header| header.to_str().ok()) {
48 Some(header) => {
49 for header_tag in header.split(',').map(|tag| tag.trim()) {
50 match header_tag.parse::<etag::EntityTag>() {
51 Ok(header_tag) => match etag.weak_eq(&header_tag) {
52 true => return false,
53 false => (),
54 },
55 Err(_) => ()
56 }
57 }
58 true
59 },
60 None => true
61 }
62}
63
64pub fn if_unmodified_since(last_modified: httpdate::HttpDate, headers: &http::header::HeaderMap) -> bool {
69 match headers.get(http::header::IF_UNMODIFIED_SINCE).and_then(|header| header.to_str().ok()).and_then(|header| httpdate::HttpDate::from_str(header.trim()).ok()) {
70 Some(header) => last_modified <= header,
71 None => true,
72 }
73}
74
75pub fn if_modified_since(last_modified: httpdate::HttpDate, headers: &http::header::HeaderMap) -> bool {
80 if headers.contains_key(http::header::IF_NONE_MATCH) {
81 return true;
82 }
83
84 match headers.get(http::header::IF_MODIFIED_SINCE).and_then(|header| header.to_str().ok()).and_then(|header| httpdate::HttpDate::from_str(header.trim()).ok()) {
85 Some(header) => last_modified > header,
86 None => true,
87 }
88}
89
90pub struct ServeFile<W, C> {
92 file: fs::File,
93 meta: fs::Metadata,
94 pub content_type: mime::Mime,
96 pub content_disposition: ContentDisposition,
98 _config: PhantomData<(W, C)>,
99}
100
101impl<W: FsTaskSpawner, C: FileServeConfig> ServeFile<W, C> {
102 pub fn open(path: &Path) -> io::Result<Self> {
104 let file = fs::File::open(path)?;
105 let meta = file.metadata()?;
106
107 if let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) {
108 Ok(Self::from_parts_with_cfg(file_name, file, meta))
109 } else {
110 Err(io::Error::new(io::ErrorKind::InvalidInput, "Provided path has no filename"))
111 }
112 }
113
114 #[inline]
115 pub fn from_parts(file_name: &str, file: fs::File, meta: fs::Metadata) -> Self {
117 Self::from_parts_with_cfg(file_name, file, meta)
118 }
119
120 pub fn from_parts_with_cfg(file_name: &str, file: fs::File, meta: fs::Metadata) -> Self {
122 let (content_type, content_disposition) = {
123 let content_type = mime_guess::from_path(&file_name).first_or_octet_stream();
124 let content_disposition = match C::content_disposition_map(content_type.type_()) {
125 DispositionType::Inline => ContentDisposition::Inline,
126 DispositionType::Attachment => ContentDisposition::Attachment(cd::Filename::with_encoded_name(file_name.into())),
127 };
128
129 (content_type, content_disposition)
130 };
131
132 Self {
133 file,
134 meta,
135 content_type,
136 content_disposition,
137 _config: PhantomData,
138 }
139 }
140
141 #[inline]
142 pub fn etag(&self) -> etag::EntityTag {
144 etag::EntityTag::from_file_meta(&self.meta)
145 }
146
147 #[inline]
148 pub fn last_modified(&self) -> Option<httpdate::HttpDate> {
150 self.meta.modified().map(|modified| modified.into()).ok()
151 }
152
153 #[inline]
154 pub fn len(&self) -> u64 {
156 self.meta.len()
157 }
158
159 pub fn prepare(self, path: &Path, method: http::Method, headers: &http::HeaderMap, out_headers: &mut http::HeaderMap) -> (http::StatusCode, Body<W, C>) {
161 if C::is_use_etag(&path) {
163 let etag = self.etag();
164
165 out_headers.insert(http::header::ETAG, utils::display_to_header(&etag));
168
169 if !if_match(&etag, headers) {
170 return (http::StatusCode::PRECONDITION_FAILED, Body::empty());
171 } else if !if_none_match(&etag, headers) {
172 return (http::StatusCode::NOT_MODIFIED, Body::empty());
173 }
174 }
175
176 if C::is_use_last_modifier(&path) {
177 if let Some(last_modified) = self.last_modified() {
178 out_headers.insert(http::header::LAST_MODIFIED, utils::display_to_header(&last_modified));
179
180 if !if_unmodified_since(last_modified, headers) {
181 return (http::StatusCode::PRECONDITION_FAILED, Body::empty());
182 } else if !if_modified_since(last_modified, headers) {
183 return (http::StatusCode::NOT_MODIFIED, Body::empty());
184 }
185 }
186 }
187
188 out_headers.insert(http::header::CONTENT_TYPE, utils::display_to_header(&self.content_type));
189 out_headers.insert(http::header::CONTENT_DISPOSITION, utils::display_to_header(&self.content_disposition));
190 out_headers.insert(http::header::ACCEPT_RANGES, http::header::HeaderValue::from_static("bytes"));
191
192 let mut length = self.len();
193 let mut offset = 0;
194
195 if let Some(ranges) = headers.get(http::header::RANGE) {
197 if let Ok(ranges_header) = ranges.to_str() {
198 if let Ok(ranges_vec) = HttpRange::parse(ranges_header, length) {
199 length = ranges_vec[0].length;
200 offset = ranges_vec[0].start;
201 let content_range = utils::display_to_header(&format_args!("bytes {}-{}/{}", offset, offset + length - 1, self.len()));
202 out_headers.insert(http::header::CONTENT_RANGE, content_range);
203 } else {
204 let content_range = utils::display_to_header(&format_args!("bytes */{}", length));
205 out_headers.insert(http::header::CONTENT_RANGE, content_range);
206 return (http::StatusCode::RANGE_NOT_SATISFIABLE, Body::empty());
207 }
208 } else {
209 return (http::StatusCode::BAD_REQUEST, Body::empty());
210 };
211 };
212
213 out_headers.insert(http::header::CONTENT_LENGTH, utils::display_to_header(&length));
214
215 match method {
216 http::Method::HEAD => (http::StatusCode::OK, Body::empty()),
217 _ => {
218 let code = if offset != 0 || length != self.len() {
219 http::StatusCode::PARTIAL_CONTENT
220 } else {
221 http::StatusCode::OK
222 };
223
224 let reader = ChunkedReadFile::<W, C>::new(length, offset, self.into());
225 (code, Body::Chunked(reader))
226 },
227 }
228 }
229}
230
231impl<W, C> Into<fs::File> for ServeFile<W, C> {
232 fn into(self) -> fs::File {
233 self.file
234 }
235}
236
237#[cold]
238#[inline(never)]
239fn map_spawn_error<T: Into<Box<dyn std::error::Error + Send + Sync>>>(error: T) -> io::Error {
240 io::Error::new(io::ErrorKind::Other, error)
241}
242
243pub struct ChunkedReadFile<W: FsTaskSpawner, C> {
245 pub size: u64,
247 offset: u64,
248 ongoing: Option<W::FileReadFut>,
249 file: fs::File,
250 counter: u64,
251 _config: PhantomData<(W, C)>,
252}
253
254impl<W: FsTaskSpawner, C: FileServeConfig> ChunkedReadFile<W, C> {
255 pub fn new(size: u64, offset: u64, file: fs::File) -> Self {
257 Self {
258 size,
259 offset,
260 ongoing: None,
261 file,
262 counter: 0,
263 _config: PhantomData
264 }
265 }
266
267 fn next_read(&mut self) -> W::FileReadFut {
268 #[cfg(not(windows))]
269 use std::os::fd::{AsRawFd, FromRawFd};
270 #[cfg(windows)]
271 use std::os::windows::io::{AsRawHandle, FromRawHandle};
272
273 #[cfg(windows)]
274 pub struct Handle(std::os::windows::io::RawHandle);
275 #[cfg(windows)]
276 unsafe impl Send for Handle {}
277
278 #[cfg(not(windows))]
279 let fd = self.file.as_raw_fd();
280 #[cfg(windows)]
281 let fd = Handle(self.file.as_raw_handle());
282
283 let size = self.size;
284 let offset = self.offset;
285 let counter = self.counter;
286
287 W::spawn_file_read(move || {
288 #[cfg(not(windows))]
289 let mut file = unsafe {
290 fs::File::from_raw_fd(fd)
291 };
292 #[cfg(windows)]
293 let mut file = unsafe {
294 fs::File::from_raw_handle(fd.0)
295 };
296
297 let max_bytes = cmp::min(size.saturating_sub(counter), C::max_buffer_size());
298 let mut buf = Vec::with_capacity(max_bytes as usize);
299 let result = match file.seek(io::SeekFrom::Start(offset)) {
300 Ok(_) => match file.by_ref().take(max_bytes).read_to_end(&mut buf) {
301 Ok(0) => Err(io::ErrorKind::UnexpectedEof.into()),
302 Ok(_) => Ok(Bytes::from(buf)),
303 Err(error) => Err(error),
304 },
305 Err(error) => Err(error),
306 };
307 mem::forget(file);
308 result
309 })
310 }
311
312 pub async fn next(&mut self) -> Result<Option<Bytes>, io::Error> {
314 self.await
315 }
316
317 #[inline(always)]
318 pub fn is_finished(&self) -> bool {
320 self.size == self.counter
321 }
322
323 #[inline(always)]
324 pub fn remaining(&self) -> u64 {
326 self.size.saturating_sub(self.offset)
327 }
328}
329
330impl<W: FsTaskSpawner, C: FileServeConfig> Future for ChunkedReadFile<W, C> {
331 type Output = Result<Option<Bytes>, io::Error>;
332
333 fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
334 if self.is_finished() {
335 return task::Poll::Ready(Ok(None));
336 }
337 let this = self.get_mut();
338
339 loop {
340 if let Some(ongoing) = this.ongoing.as_mut() {
341 break match Future::poll(Pin::new(ongoing), ctx) {
342 task::Poll::Pending => task::Poll::Pending,
343 task::Poll::Ready(result) => {
344 this.ongoing = None;
345 match result {
346 Ok(Ok(bytes)) => task::Poll::Ready(Ok(Some(bytes))),
347 Ok(Err(error)) => task::Poll::Ready(Err(error)),
348 Err(error) => task::Poll::Ready(Err(map_spawn_error(error))),
349 }
350 }
351 }
352 } else {
353 this.ongoing = Some(this.next_read());
354 }
355 }
356 }
357}