use bytes::Bytes;
use crate::config::{FileServeConfig, FsTaskSpawner};
use crate::headers::cd::{self, ContentDisposition, DispositionType};
use crate::utils;
use crate::headers::range::HttpRange;
use crate::body::Body;
use std::io::{self, Seek, Read};
use std::path::Path;
use std::fs;
use core::{mem, cmp, task};
use core::str::FromStr;
use core::marker::PhantomData;
use core::future::Future;
use core::pin::Pin;
pub type FileReadResult = Result<Bytes, io::Error>;
pub fn if_match(etag: &etag::EntityTag, headers: &http::header::HeaderMap) -> bool {
match headers.get(http::header::IF_MATCH).and_then(|header| header.to_str().ok()) {
Some(header) => {
for header_tag in header.split(',').map(|tag| tag.trim()) {
match header_tag.parse::<etag::EntityTag>() {
Ok(header_tag) => match etag.strong_eq(&header_tag) {
true => return true,
false => (),
},
Err(_) => ()
}
}
false
},
None => true
}
}
pub fn if_none_match(etag: &etag::EntityTag, headers: &http::header::HeaderMap) -> bool {
match headers.get(http::header::IF_NONE_MATCH).and_then(|header| header.to_str().ok()) {
Some(header) => {
for header_tag in header.split(',').map(|tag| tag.trim()) {
match header_tag.parse::<etag::EntityTag>() {
Ok(header_tag) => match etag.weak_eq(&header_tag) {
true => return false,
false => (),
},
Err(_) => ()
}
}
true
},
None => true
}
}
pub fn if_unmodified_since(last_modified: httpdate::HttpDate, headers: &http::header::HeaderMap) -> bool {
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()) {
Some(header) => last_modified <= header,
None => true,
}
}
pub fn if_modified_since(last_modified: httpdate::HttpDate, headers: &http::header::HeaderMap) -> bool {
if headers.contains_key(http::header::IF_NONE_MATCH) {
return true;
}
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()) {
Some(header) => last_modified > header,
None => true,
}
}
pub struct ServeFile<W, C> {
file: fs::File,
meta: fs::Metadata,
pub content_type: mime::Mime,
pub content_disposition: ContentDisposition,
_config: PhantomData<(W, C)>,
}
impl<W: FsTaskSpawner, C: FileServeConfig> ServeFile<W, C> {
pub fn open(path: &Path) -> io::Result<Self> {
let file = fs::File::open(path)?;
let meta = file.metadata()?;
if let Some(file_name) = path.file_name().and_then(|file_name| file_name.to_str()) {
Ok(Self::from_parts_with_cfg(file_name, file, meta))
} else {
Err(io::Error::new(io::ErrorKind::InvalidInput, "Provided path has no filename"))
}
}
#[inline]
pub fn from_parts(file_name: &str, file: fs::File, meta: fs::Metadata) -> Self {
Self::from_parts_with_cfg(file_name, file, meta)
}
pub fn from_parts_with_cfg(file_name: &str, file: fs::File, meta: fs::Metadata) -> Self {
let (content_type, content_disposition) = {
let content_type = mime_guess::from_path(&file_name).first_or_octet_stream();
let content_disposition = match C::content_disposition_map(content_type.type_()) {
DispositionType::Inline => ContentDisposition::Inline,
DispositionType::Attachment => ContentDisposition::Attachment(cd::Filename::with_encoded_name(file_name.into())),
};
(content_type, content_disposition)
};
Self {
file,
meta,
content_type,
content_disposition,
_config: PhantomData,
}
}
#[inline]
pub fn etag(&self) -> etag::EntityTag {
etag::EntityTag::from_file_meta(&self.meta)
}
#[inline]
pub fn last_modified(&self) -> Option<httpdate::HttpDate> {
self.meta.modified().map(|modified| modified.into()).ok()
}
#[inline]
pub fn len(&self) -> u64 {
self.meta.len()
}
pub fn prepare(self, path: &Path, method: http::Method, headers: &http::HeaderMap, out_headers: &mut http::HeaderMap) -> (http::StatusCode, Body<W, C>) {
if C::is_use_etag(&path) {
let etag = self.etag();
out_headers.insert(http::header::ETAG, utils::display_to_header(&etag));
if !if_match(&etag, headers) {
return (http::StatusCode::PRECONDITION_FAILED, Body::empty());
} else if !if_none_match(&etag, headers) {
return (http::StatusCode::NOT_MODIFIED, Body::empty());
}
}
if C::is_use_last_modifier(&path) {
if let Some(last_modified) = self.last_modified() {
out_headers.insert(http::header::LAST_MODIFIED, utils::display_to_header(&last_modified));
if !if_unmodified_since(last_modified, headers) {
return (http::StatusCode::PRECONDITION_FAILED, Body::empty());
} else if !if_modified_since(last_modified, headers) {
return (http::StatusCode::NOT_MODIFIED, Body::empty());
}
}
}
out_headers.insert(http::header::CONTENT_TYPE, utils::display_to_header(&self.content_type));
out_headers.insert(http::header::CONTENT_DISPOSITION, utils::display_to_header(&self.content_disposition));
out_headers.insert(http::header::ACCEPT_RANGES, http::header::HeaderValue::from_static("bytes"));
let mut length = self.len();
let mut offset = 0;
if let Some(ranges) = headers.get(http::header::RANGE) {
if let Ok(ranges_header) = ranges.to_str() {
if let Ok(ranges_vec) = HttpRange::parse(ranges_header, length) {
length = ranges_vec[0].length;
offset = ranges_vec[0].start;
let content_range = utils::display_to_header(&format_args!("bytes {}-{}/{}", offset, offset + length - 1, self.len()));
out_headers.insert(http::header::CONTENT_RANGE, content_range);
} else {
let content_range = utils::display_to_header(&format_args!("bytes */{}", length));
out_headers.insert(http::header::CONTENT_RANGE, content_range);
return (http::StatusCode::RANGE_NOT_SATISFIABLE, Body::empty());
}
} else {
return (http::StatusCode::BAD_REQUEST, Body::empty());
};
};
out_headers.insert(http::header::CONTENT_LENGTH, utils::display_to_header(&length));
match method {
http::Method::HEAD => (http::StatusCode::OK, Body::empty()),
_ => {
let code = if offset != 0 || length != self.len() {
http::StatusCode::PARTIAL_CONTENT
} else {
http::StatusCode::OK
};
let reader = ChunkedReadFile::<W, C>::new(length, offset, self.into());
(code, Body::Chunked(reader))
},
}
}
}
impl<W, C> Into<fs::File> for ServeFile<W, C> {
fn into(self) -> fs::File {
self.file
}
}
#[cold]
#[inline(never)]
fn map_spawn_error<T: Into<Box<dyn std::error::Error + Send + Sync>>>(error: T) -> io::Error {
io::Error::new(io::ErrorKind::Other, error)
}
pub struct ChunkedReadFile<W: FsTaskSpawner, C> {
pub size: u64,
offset: u64,
ongoing: Option<W::FileReadFut>,
file: fs::File,
counter: u64,
_config: PhantomData<(W, C)>,
}
impl<W: FsTaskSpawner, C: FileServeConfig> ChunkedReadFile<W, C> {
pub fn new(size: u64, offset: u64, file: fs::File) -> Self {
Self {
size,
offset,
ongoing: None,
file,
counter: 0,
_config: PhantomData
}
}
fn next_read(&mut self) -> W::FileReadFut {
#[cfg(not(windows))]
use std::os::fd::{AsRawFd, FromRawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, FromRawHandle};
#[cfg(windows)]
pub struct Handle(std::os::windows::io::RawHandle);
#[cfg(windows)]
unsafe impl Send for Handle {}
#[cfg(not(windows))]
let fd = self.file.as_raw_fd();
#[cfg(windows)]
let fd = Handle(self.file.as_raw_handle());
let size = self.size;
let offset = self.offset;
let counter = self.counter;
W::spawn_file_read(move || {
#[cfg(not(windows))]
let mut file = unsafe {
fs::File::from_raw_fd(fd)
};
#[cfg(windows)]
let mut file = unsafe {
fs::File::from_raw_handle(fd.0)
};
let max_bytes = cmp::min(size.saturating_sub(counter), C::max_buffer_size());
let mut buf = Vec::with_capacity(max_bytes as usize);
let result = match file.seek(io::SeekFrom::Start(offset)) {
Ok(_) => match file.by_ref().take(max_bytes).read_to_end(&mut buf) {
Ok(0) => Err(io::ErrorKind::UnexpectedEof.into()),
Ok(_) => Ok(Bytes::from(buf)),
Err(error) => Err(error),
},
Err(error) => Err(error),
};
mem::forget(file);
result
})
}
pub async fn next(&mut self) -> Result<Option<Bytes>, io::Error> {
self.await
}
#[inline(always)]
pub fn is_finished(&self) -> bool {
self.size == self.counter
}
#[inline(always)]
pub fn remaining(&self) -> u64 {
self.size.saturating_sub(self.offset)
}
}
impl<W: FsTaskSpawner, C: FileServeConfig> Future for ChunkedReadFile<W, C> {
type Output = Result<Option<Bytes>, io::Error>;
fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
if self.is_finished() {
return task::Poll::Ready(Ok(None));
}
let this = self.get_mut();
loop {
if let Some(ongoing) = this.ongoing.as_mut() {
break match Future::poll(Pin::new(ongoing), ctx) {
task::Poll::Pending => task::Poll::Pending,
task::Poll::Ready(result) => {
this.ongoing = None;
match result {
Ok(Ok(bytes)) => task::Poll::Ready(Ok(Some(bytes))),
Ok(Err(error)) => task::Poll::Ready(Err(error)),
Err(error) => task::Poll::Ready(Err(map_spawn_error(error))),
}
}
}
} else {
this.ongoing = Some(this.next_read());
}
}
}
}