diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 1e9757e05..ab65c5420 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -12,7 +12,7 @@ use actix_web::{ web, }; use bytes::Bytes; -use futures_core::Stream; +use futures_core::{ready, Stream}; use futures_util::future::{FutureExt, LocalBoxFuture}; use crate::handle_error; @@ -39,16 +39,17 @@ impl Stream for ChunkedReadFile { cx: &mut Context<'_>, ) -> Poll> { if let Some(ref mut fut) = self.fut { - return match Pin::new(fut).poll(cx) { - Poll::Ready(Ok((file, bytes))) => { + return match ready!(Pin::new(fut).poll(cx)) { + Ok((file, bytes)) => { self.fut.take(); self.file = Some(file); + self.offset += bytes.len() as u64; self.counter += bytes.len() as u64; + Poll::Ready(Some(Ok(bytes))) } - Poll::Ready(Err(e)) => Poll::Ready(Some(Err(handle_error(e)))), - Poll::Pending => Poll::Pending, + Err(e) => Poll::Ready(Some(Err(handle_error(e)))), }; } @@ -60,21 +61,27 @@ impl Stream for ChunkedReadFile { Poll::Ready(None) } else { let mut file = self.file.take().expect("Use after completion"); + self.fut = Some( web::block(move || { - let max_bytes: usize; - max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; + let max_bytes = + cmp::min(size.saturating_sub(counter), 65_536) as usize; + let mut buf = Vec::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; - let nbytes = + + let n_bytes = file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if nbytes == 0 { + + if n_bytes == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); } + Ok((file, Bytes::from(buf))) }) .boxed_local(), ); + self.poll_next(cx) } } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 842bd1c9b..89107afc0 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -93,8 +93,10 @@ impl NamedFile { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, _ => DispositionType::Attachment, }; + let mut parameters = vec![DispositionParam::Filename(String::from(filename.as_ref()))]; + if !filename.is_ascii() { parameters.push(DispositionParam::FilenameExt(ExtendedValue { charset: Charset::Ext(String::from("UTF-8")), @@ -102,16 +104,19 @@ impl NamedFile { value: filename.into_owned().into_bytes(), })) } + let cd = ContentDisposition { disposition, parameters, }; + (ct, cd) }; let md = file.metadata()?; let modified = md.modified().ok(); let encoding = None; + Ok(NamedFile { path, file, @@ -259,6 +264,7 @@ impl NamedFile { pub fn into_response(self, req: &HttpRequest) -> Result { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); + resp.set(header::ContentType(self.content_type.clone())) .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { res.header( @@ -266,9 +272,11 @@ impl NamedFile { self.content_disposition.to_string(), ); }); + if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); } + let reader = ChunkedReadFile { size: self.md.len(), offset: 0, @@ -276,6 +284,7 @@ impl NamedFile { fut: None, counter: 0, }; + return Ok(resp.streaming(reader)); } @@ -284,6 +293,7 @@ impl NamedFile { } else { None }; + let last_modified = if self.flags.contains(Flags::LAST_MD) { self.last_modified() } else { @@ -298,6 +308,7 @@ impl NamedFile { { let t1: SystemTime = m.clone().into(); let t2: SystemTime = since.clone().into(); + match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { (Ok(t1), Ok(t2)) => t1 > t2, _ => false, @@ -316,6 +327,7 @@ impl NamedFile { { let t1: SystemTime = m.clone().into(); let t2: SystemTime = since.clone().into(); + match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { (Ok(t1), Ok(t2)) => t1 <= t2, _ => false, @@ -332,6 +344,7 @@ impl NamedFile { self.content_disposition.to_string(), ); }); + // default compressing if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); @@ -355,6 +368,7 @@ impl NamedFile { if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) { length = rangesvec[0].length; offset = rangesvec[0].start; + resp.encoding(ContentEncoding::Identity); resp.header( header::CONTENT_RANGE, @@ -414,6 +428,7 @@ impl DerefMut for NamedFile { fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { None | Some(header::IfMatch::Any) => true, + Some(header::IfMatch::Items(ref items)) => { if let Some(some_etag) = etag { for item in items { @@ -422,6 +437,7 @@ fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } } } + false } } @@ -431,6 +447,7 @@ fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { match req.get_header::() { Some(header::IfNoneMatch::Any) => false, + Some(header::IfNoneMatch::Items(ref items)) => { if let Some(some_etag) = etag { for item in items { @@ -439,8 +456,10 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } } } + true } + None => true, } } diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index f77f1920a..6dd8dd429 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -16,6 +16,7 @@ impl FromStr for PathBufWrap { fn from_str(path: &str) -> Result { let mut buf = PathBuf::new(); + for segment in path.split('/') { if segment == ".." { buf.pop(); diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index 47673b0b0..2cdeca795 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -5,7 +5,7 @@ pub struct HttpRange { pub length: u64, } -static PREFIX: &str = "bytes="; +const PREFIX: &str = "bytes="; const PREFIX_LEN: usize = 6; impl HttpRange { diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 9351e39d7..f16806b13 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -32,17 +32,15 @@ pub struct FilesService { pub(crate) guards: Option>, } +type FilesServiceFuture = Either< + Ready>, + LocalBoxFuture<'static, Result>, +>; + impl FilesService { - #[allow(clippy::type_complexity)] - fn handle_err( - &mut self, - e: io::Error, - req: ServiceRequest, - ) -> Either< - Ready>, - LocalBoxFuture<'static, Result>, - > { - log::debug!("Files: Failed to handle {}: {}", req.path(), e); + fn handle_err(&mut self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { + log::debug!("Failed to handle {}: {}", req.path(), e); + if let Some(ref mut default) = self.default { Either::Right(default.call(req)) } else { @@ -55,11 +53,7 @@ impl Service for FilesService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - #[allow(clippy::type_complexity)] - type Future = Either< - Ready>, - LocalBoxFuture<'static, Result>, - >; + type Future = FilesServiceFuture; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -97,6 +91,7 @@ impl Service for FilesService { if let Some(ref redir_index) = self.index { if self.redirect_to_slash && !req.path().ends_with('/') { let redirect_to = format!("{}/", req.path()); + return Either::Left(ok(req.into_response( HttpResponse::Found() .header(header::LOCATION, redirect_to) @@ -114,8 +109,8 @@ impl Service for FilesService { mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } - named_file.flags = self.file_flags; + let (req, _) = req.into_parts(); Either::Left(ok(match named_file.into_response(&req) { Ok(item) => ServiceResponse::new(req, item), @@ -126,8 +121,10 @@ impl Service for FilesService { } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); + let (req, _) = req.into_parts(); let x = (self.renderer)(&dir, &req); + match x { Ok(resp) => Either::Left(ok(resp)), Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), @@ -146,8 +143,8 @@ impl Service for FilesService { mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } - named_file.flags = self.file_flags; + let (req, _) = req.into_parts(); match named_file.into_response(&req) { Ok(item) => {