1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-24 00:21:08 +01:00

Merge branch 'master' of github.com:actix/actix-web

This commit is contained in:
Nikolay Kim 2018-06-10 10:25:05 -07:00
commit ef88fc78d0
3 changed files with 167 additions and 43 deletions

View File

@ -4,6 +4,12 @@
### Added ### Added
* Add `.set_content_type()` and `.set_content_disposition()` methods
to `fs::NamedFile` to allow overriding the values inferred by default
* Add `fs::file_extension_to_mime()` helper function to get the MIME
type for a file extension
* Add `.content_disposition()` method to parse Content-Disposition of * Add `.content_disposition()` method to parse Content-Disposition of
multipart fields multipart fields

193
src/fs.rs
View File

@ -29,12 +29,21 @@ use param::FromParam;
/// Env variable for default cpu pool size for `StaticFiles` /// Env variable for default cpu pool size for `StaticFiles`
const ENV_CPU_POOL_VAR: &str = "ACTIX_FS_POOL"; const ENV_CPU_POOL_VAR: &str = "ACTIX_FS_POOL";
/// A file with an associated name; responds with the Content-Type based on the /// Return the MIME type associated with a filename extension (case-insensitive).
/// file extension. /// If `ext` is empty or no associated type for the extension was found, returns
/// the type `application/octet-stream`.
#[inline]
pub fn file_extension_to_mime(ext: &str) -> mime::Mime {
get_mime_type(ext)
}
/// A file with an associated name.
#[derive(Debug)] #[derive(Debug)]
pub struct NamedFile { pub struct NamedFile {
path: PathBuf, path: PathBuf,
file: File, file: File,
content_type: mime::Mime,
content_disposition: header::ContentDisposition,
md: Metadata, md: Metadata,
modified: Option<SystemTime>, modified: Option<SystemTime>,
cpu_pool: Option<CpuPool>, cpu_pool: Option<CpuPool>,
@ -54,15 +63,48 @@ impl NamedFile {
/// let file = NamedFile::open("foo.txt"); /// let file = NamedFile::open("foo.txt");
/// ``` /// ```
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> { pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = File::open(path.as_ref())?; use header::{ContentDisposition, DispositionType, DispositionParam};
let md = file.metadata()?;
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
// Get the name of the file and use it to construct default Content-Type
// and Content-Disposition values
let (content_type, content_disposition) =
{
let filename = match path.file_name() {
Some(name) => name.to_string_lossy(),
None => return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Provided path has no filename")),
};
let ct = guess_mime_type(&path);
let disposition_type = match ct.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
_ => DispositionType::Attachment,
};
let cd = ContentDisposition {
disposition: disposition_type,
parameters: vec![
DispositionParam::Filename(
header::Charset::Ext("UTF-8".to_owned()),
None,
filename.as_bytes().to_vec(),
)
],
};
(ct, cd)
};
let file = File::open(&path)?;
let md = file.metadata()?;
let modified = md.modified().ok(); let modified = md.modified().ok();
let cpu_pool = None; let cpu_pool = None;
let encoding = None; let encoding = None;
Ok(NamedFile { Ok(NamedFile {
path, path,
file, file,
content_type,
content_disposition,
md, md,
modified, modified,
cpu_pool, cpu_pool,
@ -117,6 +159,27 @@ impl NamedFile {
self self
} }
/// Set the MIME Content-Type for serving this file. By default
/// the Content-Type is inferred from the filename extension.
#[inline]
pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self {
self.content_type = mime_type;
self
}
/// Set the Content-Disposition for serving this file. This allows
/// changing the inline/attachment disposition as well as the filename
/// sent to the peer. By default the disposition is `inline` for text,
/// image, and video content types, and `attachment` otherwise, and
/// the filename is taken from the path provided in the `open` method
/// after converting it to UTF-8 using
/// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy).
#[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
self.content_disposition = cd;
self
}
/// Set content encoding for serving this file /// Set content encoding for serving this file
#[inline] #[inline]
pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self { pub fn set_content_encoding(mut self, enc: ContentEncoding) -> Self {
@ -212,23 +275,9 @@ impl Responder for NamedFile {
fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, io::Error> { fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, io::Error> {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
resp.if_some(self.path().extension(), |ext, resp| { resp.set(header::ContentType(self.content_type.clone()))
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy()))); .header("Content-Disposition", format!("{}", &self.content_disposition));
}).if_some(self.path().file_name(), |file_name, resp| {
let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
_ => "attachment",
};
resp.header(
"Content-Disposition",
format!(
"{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy()
),
);
});
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
resp.content_encoding(current_encoding); resp.content_encoding(current_encoding);
} }
@ -277,27 +326,14 @@ impl Responder for NamedFile {
}; };
let mut resp = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone()))
.header("Content-Disposition", format!("{}", &self.content_disposition));
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
resp.content_encoding(current_encoding); resp.content_encoding(current_encoding);
} }
resp.if_some(self.path().extension(), |ext, resp| { resp
resp.set(header::ContentType(get_mime_type(&ext.to_string_lossy())));
}).if_some(self.path().file_name(), |file_name, resp| {
let mime_type = guess_mime_type(self.path());
let inline_or_attachment = match mime_type.type_() {
mime::IMAGE | mime::TEXT | mime::VIDEO => "inline",
_ => "attachment",
};
resp.header(
"Content-Disposition",
format!(
"{inline_or_attachment}; filename={filename}",
inline_or_attachment = inline_or_attachment,
filename = file_name.to_string_lossy()
),
);
})
.if_some(last_modified, |lm, resp| { .if_some(last_modified, |lm, resp| {
resp.set(header::LastModified(lm)); resp.set(header::LastModified(lm));
}) })
@ -692,6 +728,18 @@ mod tests {
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
use test::{self, TestRequest}; use test::{self, TestRequest};
#[test]
fn test_file_extension_to_mime() {
let m = file_extension_to_mime("jpg");
assert_eq!(m, mime::IMAGE_JPEG);
let m = file_extension_to_mime("invalid extension!!");
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
let m = file_extension_to_mime("");
assert_eq!(m, mime::APPLICATION_OCTET_STREAM);
}
#[test] #[test]
fn test_named_file_text() { fn test_named_file_text() {
assert!(NamedFile::open("test--").is_err()); assert!(NamedFile::open("test--").is_err());
@ -713,7 +761,32 @@ mod tests {
); );
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=Cargo.toml" "inline; filename=\"Cargo.toml\""
);
}
#[test]
fn test_named_file_set_content_type() {
let mut file = NamedFile::open("Cargo.toml")
.unwrap()
.set_content_type(mime::TEXT_XML)
.set_cpu_pool(CpuPool::new(1));
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/xml"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=\"Cargo.toml\""
); );
} }
@ -737,7 +810,43 @@ mod tests {
); );
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=test.png" "inline; filename=\"test.png\""
);
}
#[test]
fn test_named_file_image_attachment() {
use header::{ContentDisposition, DispositionType, DispositionParam};
let cd = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![
DispositionParam::Filename(
header::Charset::Ext("UTF-8".to_owned()),
None,
"test.png".as_bytes().to_vec(),
)
],
};
let mut file = NamedFile::open("tests/test.png")
.unwrap()
.set_content_disposition(cd)
.set_cpu_pool(CpuPool::new(1));
{
file.file();
let _f: &File = &file;
}
{
let _f: &mut File = &mut file;
}
let resp = file.respond_to(&HttpRequest::default()).unwrap();
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"image/png"
);
assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=\"test.png\""
); );
} }
@ -761,7 +870,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"attachment; filename=test.binary" "attachment; filename=\"test.binary\""
); );
} }
@ -786,7 +895,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
"inline; filename=Cargo.toml" "inline; filename=\"Cargo.toml\""
); );
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
} }

View File

@ -70,7 +70,7 @@ pub enum DispositionParam {
/// ``` /// ```
/// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset}; /// use actix_web::http::header::{ContentDisposition, DispositionType, DispositionParam, Charset};
/// ///
/// let cd = ContentDisposition { /// let cd1 = ContentDisposition {
/// disposition: DispositionType::Attachment, /// disposition: DispositionType::Attachment,
/// parameters: vec![DispositionParam::Filename( /// parameters: vec![DispositionParam::Filename(
/// Charset::Iso_8859_1, // The character set for the bytes of the filename /// Charset::Iso_8859_1, // The character set for the bytes of the filename
@ -78,6 +78,15 @@ pub enum DispositionParam {
/// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename /// b"\xa9 Copyright 1989.txt".to_vec() // the actual bytes of the filename
/// )] /// )]
/// }; /// };
///
/// let cd2 = ContentDisposition {
/// disposition: DispositionType::Inline,
/// parameters: vec![DispositionParam::Filename(
/// Charset::Ext("UTF-8".to_owned()),
/// None,
/// "\u{2764}".as_bytes().to_vec()
/// )]
/// };
/// ``` /// ```
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct ContentDisposition { pub struct ContentDisposition {