mirror of https://github.com/fafhrd91/actix-web
perf(files): drop some needless allocations
This commit is contained in:
parent
b6f93ba9a0
commit
e8785d3b7f
|
|
@ -117,14 +117,35 @@ pub(crate) fn get_content_type_and_disposition(
|
|||
};
|
||||
|
||||
// replace special characters in filenames which could occur on some filesystems
|
||||
let filename_s = filename
|
||||
.replace('\n', "%0A") // \n line break
|
||||
.replace('\x0B', "%0B") // \v vertical tab
|
||||
.replace('\x0C', "%0C") // \f form feed
|
||||
.replace('\r', "%0D"); // \r carriage return
|
||||
let mut parameters = vec![DispositionParam::Filename(filename_s)];
|
||||
let mut escaped_len = filename.len();
|
||||
for byte in filename.bytes() {
|
||||
if matches!(byte, b'\n' | b'\x0B' | b'\x0C' | b'\r') {
|
||||
escaped_len += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if !filename.is_ascii() {
|
||||
let filename_s = if escaped_len == filename.len() {
|
||||
filename.to_string()
|
||||
} else {
|
||||
let mut escaped = String::with_capacity(escaped_len);
|
||||
for ch in filename.chars() {
|
||||
match ch {
|
||||
'\n' => escaped.push_str("%0A"), // \n line break
|
||||
'\x0B' => escaped.push_str("%0B"), // \v vertical tab
|
||||
'\x0C' => escaped.push_str("%0C"), // \f form feed
|
||||
'\r' => escaped.push_str("%0D"), // \r carriage return
|
||||
ch => escaped.push(ch),
|
||||
}
|
||||
}
|
||||
escaped
|
||||
};
|
||||
|
||||
let is_ascii = filename.is_ascii();
|
||||
|
||||
let mut parameters = Vec::with_capacity(if is_ascii { 1 } else { 2 });
|
||||
parameters.push(DispositionParam::Filename(filename_s));
|
||||
|
||||
if !is_ascii {
|
||||
parameters.push(DispositionParam::FilenameExt(ExtendedValue {
|
||||
charset: Charset::Ext(String::from("UTF-8")),
|
||||
language_tag: None,
|
||||
|
|
@ -735,4 +756,15 @@ mod tests {
|
|||
let (_ct, cd) = get_content_type_and_disposition(Path::new("sound.mp3")).unwrap();
|
||||
assert_eq!(cd.disposition, DispositionType::Inline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_chars_are_escaped_in_content_disposition_filename() {
|
||||
let (_ct, cd) =
|
||||
get_content_type_and_disposition(Path::new("test\n\x0B\x0C\rnewline.text")).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
cd.to_string(),
|
||||
"inline; filename=\"test%0A%0B%0C%0Dnewline.text\"",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Component, Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
|
@ -70,8 +71,10 @@ impl PathBufWrap {
|
|||
.map_err(|_| UriSegmentError::NotValidUtf8)?;
|
||||
|
||||
// disallow decoding `%2F` into `/`
|
||||
if segment_count != path.matches('/').count() + 1 {
|
||||
return Err(UriSegmentError::BadChar('/'));
|
||||
if let Cow::Owned(ref path) = path {
|
||||
if segment_count != path.matches('/').count() + 1 {
|
||||
return Err(UriSegmentError::BadChar('/'));
|
||||
}
|
||||
}
|
||||
|
||||
for segment in path.split('/') {
|
||||
|
|
@ -199,6 +202,14 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoded_slash_is_rejected() {
|
||||
assert_eq!(
|
||||
PathBufWrap::parse_path("/test%2Ffile.txt", false),
|
||||
Err(UriSegmentError::BadChar('/'))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(windows, should_panic)]
|
||||
fn windows_drive_traversal() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue