mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-06 19:00:18 +02:00
Compare commits
6 Commits
codegen-v0
...
http-v0.2.
Author | SHA1 | Date | |
---|---|---|---|
f7f410d033 | |||
885ff7396e | |||
61b38e8d0d | |||
edcde67076 | |||
f0612f7570 | |||
ace98e3a1e |
@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110)
|
* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* Support `Host` guards when the `Host` header is unset (e.g. HTTP/2 requests) (#1129)
|
||||||
|
|
||||||
## [1.0.8] - 2019-09-25
|
## [1.0.8] - 2019-09-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -89,7 +89,7 @@ bytes = "0.4"
|
|||||||
derive_more = "0.15.0"
|
derive_more = "0.15.0"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures = "0.1.25"
|
futures = "0.1.25"
|
||||||
hashbrown = "0.5.0"
|
hashbrown = "0.6.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
net2 = "0.2.33"
|
net2 = "0.2.33"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.1.7] - 2019-11-06
|
||||||
|
|
||||||
|
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
||||||
|
|
||||||
## [0.1.6] - 2019-10-14
|
## [0.1.6] - 2019-10-14
|
||||||
|
|
||||||
* Add option to redirect to a slash-ended path `Files` #1132
|
* Add option to redirect to a slash-ended path `Files` #1132
|
||||||
|
@ -733,6 +733,31 @@ mod tests {
|
|||||||
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
|
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_named_file_non_ascii_file_name() {
|
||||||
|
let mut file =
|
||||||
|
NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml")
|
||||||
|
.unwrap();
|
||||||
|
{
|
||||||
|
file.file();
|
||||||
|
let _f: &File = &file;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let _f: &mut File = &mut file;
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
let resp = file.respond_to(&req).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
"text/x-toml"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||||
|
"inline; filename=\"貨物.toml\"; filename*=UTF-8''%E8%B2%A8%E7%89%A9.toml"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_named_file_set_content_type() {
|
fn test_named_file_set_content_type() {
|
||||||
let mut file = NamedFile::open("Cargo.toml")
|
let mut file = NamedFile::open("Cargo.toml")
|
||||||
|
@ -13,7 +13,7 @@ use mime_guess::from_path;
|
|||||||
|
|
||||||
use actix_http::body::SizedStream;
|
use actix_http::body::SizedStream;
|
||||||
use actix_web::http::header::{
|
use actix_web::http::header::{
|
||||||
self, ContentDisposition, DispositionParam, DispositionType,
|
self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue,
|
||||||
};
|
};
|
||||||
use actix_web::http::{ContentEncoding, StatusCode};
|
use actix_web::http::{ContentEncoding, StatusCode};
|
||||||
use actix_web::middleware::BodyEncoding;
|
use actix_web::middleware::BodyEncoding;
|
||||||
@ -93,9 +93,18 @@ impl NamedFile {
|
|||||||
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
|
||||||
_ => DispositionType::Attachment,
|
_ => 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")),
|
||||||
|
language_tag: None,
|
||||||
|
value: filename.into_owned().into_bytes(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
let cd = ContentDisposition {
|
let cd = ContentDisposition {
|
||||||
disposition: disposition_type,
|
disposition: disposition_type,
|
||||||
parameters: vec![DispositionParam::Filename(filename.into_owned())],
|
parameters: parameters,
|
||||||
};
|
};
|
||||||
(ct, cd)
|
(ct, cd)
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## Not released yet
|
## [0.2.11] - 2019-11-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
|
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
|
||||||
|
|
||||||
|
* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151)
|
||||||
|
|
||||||
|
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
|
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
|
||||||
@ -17,9 +21,6 @@
|
|||||||
|
|
||||||
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
|
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
|
||||||
|
|
||||||
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
* h2 will use error response #1080
|
* h2 will use error response #1080
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "0.2.10"
|
version = "0.2.11"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix http primitives"
|
description = "Actix http primitives"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -62,7 +62,7 @@ derive_more = "0.15.0"
|
|||||||
either = "1.5.2"
|
either = "1.5.2"
|
||||||
encoding_rs = "0.8"
|
encoding_rs = "0.8"
|
||||||
futures = "0.1.25"
|
futures = "0.1.25"
|
||||||
hashbrown = "0.5.0"
|
hashbrown = "0.6.3"
|
||||||
h2 = "0.1.16"
|
h2 = "0.1.16"
|
||||||
http = "0.1.17"
|
http = "0.1.17"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
|
@ -548,10 +548,11 @@ mod tests {
|
|||||||
ConnectionType::Close,
|
ConnectionType::Close,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap();
|
||||||
bytes.take().freeze(),
|
assert!(data.contains("Content-Length: 0\r\n"));
|
||||||
Bytes::from_static(b"\r\nContent-Length: 0\r\nConnection: close\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n")
|
assert!(data.contains("Connection: close\r\n"));
|
||||||
);
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
|
assert!(data.contains("Date: date\r\n"));
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
&mut bytes,
|
&mut bytes,
|
||||||
@ -560,10 +561,10 @@ mod tests {
|
|||||||
ConnectionType::KeepAlive,
|
ConnectionType::KeepAlive,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap();
|
||||||
bytes.take().freeze(),
|
assert!(data.contains("Transfer-Encoding: chunked\r\n"));
|
||||||
Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n")
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
);
|
assert!(data.contains("Date: date\r\n"));
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
&mut bytes,
|
&mut bytes,
|
||||||
@ -572,10 +573,10 @@ mod tests {
|
|||||||
ConnectionType::KeepAlive,
|
ConnectionType::KeepAlive,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap();
|
||||||
bytes.take().freeze(),
|
assert!(data.contains("Content-Length: 100\r\n"));
|
||||||
Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n")
|
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||||
);
|
assert!(data.contains("Date: date\r\n"));
|
||||||
|
|
||||||
let mut head = RequestHead::default();
|
let mut head = RequestHead::default();
|
||||||
head.set_camel_case_headers(false);
|
head.set_camel_case_headers(false);
|
||||||
@ -586,7 +587,6 @@ mod tests {
|
|||||||
.append(CONTENT_TYPE, HeaderValue::from_static("xml"));
|
.append(CONTENT_TYPE, HeaderValue::from_static("xml"));
|
||||||
|
|
||||||
let mut head = RequestHeadType::Owned(head);
|
let mut head = RequestHeadType::Owned(head);
|
||||||
|
|
||||||
let _ = head.encode_headers(
|
let _ = head.encode_headers(
|
||||||
&mut bytes,
|
&mut bytes,
|
||||||
Version::HTTP_11,
|
Version::HTTP_11,
|
||||||
@ -594,10 +594,11 @@ mod tests {
|
|||||||
ConnectionType::KeepAlive,
|
ConnectionType::KeepAlive,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap();
|
||||||
bytes.take().freeze(),
|
assert!(data.contains("transfer-encoding: chunked\r\n"));
|
||||||
Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n")
|
assert!(data.contains("content-type: xml\r\n"));
|
||||||
);
|
assert!(data.contains("content-type: plain/text\r\n"));
|
||||||
|
assert!(data.contains("date: date\r\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -626,9 +627,10 @@ mod tests {
|
|||||||
ConnectionType::Close,
|
ConnectionType::Close,
|
||||||
&ServiceConfig::default(),
|
&ServiceConfig::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
let data = String::from_utf8(Vec::from(bytes.take().freeze().as_ref())).unwrap();
|
||||||
bytes.take().freeze(),
|
assert!(data.contains("content-length: 0\r\n"));
|
||||||
Bytes::from_static(b"\r\ncontent-length: 0\r\nconnection: close\r\nauthorization: another authorization\r\ndate: date\r\n\r\n")
|
assert!(data.contains("connection: close\r\n"));
|
||||||
);
|
assert!(data.contains("authorization: another authorization\r\n"));
|
||||||
|
assert!(data.contains("date: date\r\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,11 @@ pub enum DispositionParam {
|
|||||||
/// the form.
|
/// the form.
|
||||||
Name(String),
|
Name(String),
|
||||||
/// A plain file name.
|
/// A plain file name.
|
||||||
|
///
|
||||||
|
/// It is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any
|
||||||
|
/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where
|
||||||
|
/// [`FilenameExt`](DispositionParam::FilenameExt) with charset UTF-8 may be used instead
|
||||||
|
/// in case there are Unicode characters in file names.
|
||||||
Filename(String),
|
Filename(String),
|
||||||
/// An extended file name. It must not exist for `ContentType::Formdata` according to
|
/// An extended file name. It must not exist for `ContentType::Formdata` according to
|
||||||
/// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2).
|
/// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2).
|
||||||
@ -220,7 +225,16 @@ impl DispositionParam {
|
|||||||
/// ext-token = <the characters in token, followed by "*">
|
/// ext-token = <the characters in token, followed by "*">
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within
|
/// # Note
|
||||||
|
///
|
||||||
|
/// filename is [not supposed](https://tools.ietf.org/html/rfc6266#appendix-D) to contain any
|
||||||
|
/// non-ASCII characters when used in a *Content-Disposition* HTTP response header, where
|
||||||
|
/// filename* with charset UTF-8 may be used instead in case there are Unicode characters in file
|
||||||
|
/// names.
|
||||||
|
/// filename is [acceptable](https://tools.ietf.org/html/rfc7578#section-4.2) to be UTF-8 encoded
|
||||||
|
/// directly in a *Content-Disposition* header for *multipart/form-data*, though.
|
||||||
|
///
|
||||||
|
/// filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within
|
||||||
/// *multipart/form-data*.
|
/// *multipart/form-data*.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -251,6 +265,22 @@ impl DispositionParam {
|
|||||||
/// };
|
/// };
|
||||||
/// assert_eq!(cd2.get_name(), Some("file")); // field name
|
/// assert_eq!(cd2.get_name(), Some("file")); // field name
|
||||||
/// assert_eq!(cd2.get_filename(), Some("bill.odt"));
|
/// assert_eq!(cd2.get_filename(), Some("bill.odt"));
|
||||||
|
///
|
||||||
|
/// // HTTP response header with Unicode characters in file names
|
||||||
|
/// let cd3 = ContentDisposition {
|
||||||
|
/// disposition: DispositionType::Attachment,
|
||||||
|
/// parameters: vec![
|
||||||
|
/// DispositionParam::FilenameExt(ExtendedValue {
|
||||||
|
/// charset: Charset::Ext(String::from("UTF-8")),
|
||||||
|
/// language_tag: None,
|
||||||
|
/// value: String::from("\u{1f600}.svg").into_bytes(),
|
||||||
|
/// }),
|
||||||
|
/// // fallback for better compatibility
|
||||||
|
/// DispositionParam::Filename(String::from("Grinning-Face-Emoji.svg"))
|
||||||
|
/// ],
|
||||||
|
/// };
|
||||||
|
/// assert_eq!(cd3.get_filename_ext().map(|ev| ev.value.as_ref()),
|
||||||
|
/// Some("\u{1f600}.svg".as_bytes()));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # WARN
|
/// # WARN
|
||||||
@ -333,15 +363,17 @@ impl ContentDisposition {
|
|||||||
// token: won't contains semicolon according to RFC 2616 Section 2.2
|
// token: won't contains semicolon according to RFC 2616 Section 2.2
|
||||||
let (token, new_left) = split_once_and_trim(left, ';');
|
let (token, new_left) = split_once_and_trim(left, ';');
|
||||||
left = new_left;
|
left = new_left;
|
||||||
|
if token.is_empty() {
|
||||||
|
// quoted-string can be empty, but token cannot be empty
|
||||||
|
return Err(crate::error::ParseError::Header);
|
||||||
|
}
|
||||||
token.to_owned()
|
token.to_owned()
|
||||||
};
|
};
|
||||||
if value.is_empty() {
|
|
||||||
return Err(crate::error::ParseError::Header);
|
|
||||||
}
|
|
||||||
|
|
||||||
let param = if param_name.eq_ignore_ascii_case("name") {
|
let param = if param_name.eq_ignore_ascii_case("name") {
|
||||||
DispositionParam::Name(value)
|
DispositionParam::Name(value)
|
||||||
} else if param_name.eq_ignore_ascii_case("filename") {
|
} else if param_name.eq_ignore_ascii_case("filename") {
|
||||||
|
// See also comments in test_from_raw_uncessary_percent_decode.
|
||||||
DispositionParam::Filename(value)
|
DispositionParam::Filename(value)
|
||||||
} else {
|
} else {
|
||||||
DispositionParam::Unknown(param_name.to_owned(), value)
|
DispositionParam::Unknown(param_name.to_owned(), value)
|
||||||
@ -466,11 +498,40 @@ impl fmt::Display for DispositionType {
|
|||||||
|
|
||||||
impl fmt::Display for DispositionParam {
|
impl fmt::Display for DispositionParam {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
// All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and
|
// All ASCII control characters (0-30, 127) including horizontal tab, double quote, and
|
||||||
// backslash should be escaped in quoted-string (i.e. "foobar").
|
// backslash should be escaped in quoted-string (i.e. "foobar").
|
||||||
// Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... .
|
// Ref: RFC6266 S4.1 -> RFC2616 S3.6
|
||||||
|
// filename-parm = "filename" "=" value
|
||||||
|
// value = token | quoted-string
|
||||||
|
// quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||||||
|
// qdtext = <any TEXT except <">>
|
||||||
|
// quoted-pair = "\" CHAR
|
||||||
|
// TEXT = <any OCTET except CTLs,
|
||||||
|
// but including LWS>
|
||||||
|
// LWS = [CRLF] 1*( SP | HT )
|
||||||
|
// OCTET = <any 8-bit sequence of data>
|
||||||
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||||
|
// CTL = <any US-ASCII control character
|
||||||
|
// (octets 0 - 31) and DEL (127)>
|
||||||
|
//
|
||||||
|
// Ref: RFC7578 S4.2 -> RFC2183 S2 -> RFC2045 S5.1
|
||||||
|
// parameter := attribute "=" value
|
||||||
|
// attribute := token
|
||||||
|
// ; Matching of attributes
|
||||||
|
// ; is ALWAYS case-insensitive.
|
||||||
|
// value := token / quoted-string
|
||||||
|
// token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
|
||||||
|
// or tspecials>
|
||||||
|
// tspecials := "(" / ")" / "<" / ">" / "@" /
|
||||||
|
// "," / ";" / ":" / "\" / <">
|
||||||
|
// "/" / "[" / "]" / "?" / "="
|
||||||
|
// ; Must be in quoted-string,
|
||||||
|
// ; to use within parameter values
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// See also comments in test_from_raw_uncessary_percent_decode.
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap();
|
static ref RE: Regex = Regex::new("[\x00-\x08\x10-\x1F\x7F\"\\\\]").unwrap();
|
||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
DispositionParam::Name(ref value) => write!(f, "name={}", value),
|
DispositionParam::Name(ref value) => write!(f, "name={}", value),
|
||||||
@ -774,8 +835,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_raw_uncessary_percent_decode() {
|
fn test_from_raw_uncessary_percent_decode() {
|
||||||
|
// In fact, RFC7578 (multipart/form-data) Section 2 and 4.2 suggests that filename with
|
||||||
|
// non-ASCII characters MAY be percent-encoded.
|
||||||
|
// On the contrary, RFC6266 or other RFCs related to Content-Disposition response header
|
||||||
|
// do not mention such percent-encoding.
|
||||||
|
// So, it appears to be undecidable whether to percent-decode or not without
|
||||||
|
// knowing the usage scenario (multipart/form-data v.s. HTTP response header) and
|
||||||
|
// inevitable to unnecessarily percent-decode filename with %XX in the former scenario.
|
||||||
|
// Fortunately, it seems that almost all mainstream browsers just send UTF-8 encoded file
|
||||||
|
// names in quoted-string format (tested on Edge, IE11, Chrome and Firefox) without
|
||||||
|
// percent-encoding. So we do not bother to attempt to percent-decode.
|
||||||
let a = HeaderValue::from_static(
|
let a = HeaderValue::from_static(
|
||||||
"form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded!
|
"form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"",
|
||||||
);
|
);
|
||||||
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
|
||||||
let b = ContentDisposition {
|
let b = ContentDisposition {
|
||||||
@ -811,6 +882,9 @@ mod tests {
|
|||||||
|
|
||||||
let a = HeaderValue::from_static("inline; filename= ");
|
let a = HeaderValue::from_static("inline; filename= ");
|
||||||
assert!(ContentDisposition::from_raw(&a).is_err());
|
assert!(ContentDisposition::from_raw(&a).is_err());
|
||||||
|
|
||||||
|
let a = HeaderValue::from_static("inline; filename=\"\"");
|
||||||
|
assert!(ContentDisposition::from_raw(&a).expect("parse cd").get_filename().expect("filename").is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.2.8] - 2019-10-24
|
||||||
|
|
||||||
|
* Add support for setting query from Serialize type for client request.
|
||||||
|
|
||||||
## [0.2.7] - 2019-09-25
|
## [0.2.7] - 2019-09-25
|
||||||
|
|
||||||
|
@ -382,6 +382,27 @@ impl ClientRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the query part of the request
|
||||||
|
pub fn query<T: Serialize>(
|
||||||
|
mut self,
|
||||||
|
query: &T,
|
||||||
|
) -> Result<Self, serde_urlencoded::ser::Error> {
|
||||||
|
let mut parts = self.head.uri.clone().into_parts();
|
||||||
|
|
||||||
|
if let Some(path_and_query) = parts.path_and_query {
|
||||||
|
let query = serde_urlencoded::to_string(query)?;
|
||||||
|
let path = path_and_query.path();
|
||||||
|
parts.path_and_query = format!("{}?{}", path, query).parse().ok();
|
||||||
|
|
||||||
|
match Uri::from_parts(parts) {
|
||||||
|
Ok(uri) => self.head.uri = uri,
|
||||||
|
Err(e) => self.err = Some(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Freeze request builder and construct `FrozenClientRequest`,
|
/// Freeze request builder and construct `FrozenClientRequest`,
|
||||||
/// which could be used for sending same request multiple times.
|
/// which could be used for sending same request multiple times.
|
||||||
pub fn freeze(self) -> Result<FrozenClientRequest, FreezeRequestError> {
|
pub fn freeze(self) -> Result<FrozenClientRequest, FreezeRequestError> {
|
||||||
@ -690,4 +711,13 @@ mod tests {
|
|||||||
"Bearer someS3cr3tAutht0k3n"
|
"Bearer someS3cr3tAutht0k3n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_query() {
|
||||||
|
let req = Client::new()
|
||||||
|
.get("/")
|
||||||
|
.query(&[("key1", "val1"), ("key2", "val2")])
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(req.get_uri().query().unwrap(), "key1=val1&key2=val2");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
35
src/guard.rs
35
src/guard.rs
@ -276,10 +276,12 @@ pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
|
|||||||
|
|
||||||
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
let host_value = req.headers.get(header::HOST)?;
|
req.headers
|
||||||
let host = host_value.to_str().ok()?;
|
.get(header::HOST)
|
||||||
let uri = Uri::from_str(host).ok()?;
|
.and_then(|host_value| host_value.to_str().ok())
|
||||||
Some(uri)
|
.or_else(|| req.uri.host())
|
||||||
|
.map(|host: &str| Uri::from_str(host).ok())
|
||||||
|
.and_then(|host_success| host_success)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -400,6 +402,31 @@ mod tests {
|
|||||||
assert!(!pred.check(req.head()));
|
assert!(!pred.check(req.head()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_host_without_header() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.uri("www.rust-lang.org")
|
||||||
|
.to_http_request();
|
||||||
|
|
||||||
|
let pred = Host("www.rust-lang.org");
|
||||||
|
assert!(pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("www.rust-lang.org").scheme("https");
|
||||||
|
assert!(pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("blog.rust-lang.org");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("crates.io");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("localhost");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_methods() {
|
fn test_methods() {
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
* Update serde_urlencoded to "0.6.1"
|
* Update serde_urlencoded to "0.6.1"
|
||||||
|
* Increase TestServerRuntime timeouts from 500ms to 3000ms
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ impl TestServer {
|
|||||||
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
|
||||||
Connector::new()
|
Connector::new()
|
||||||
.conn_lifetime(time::Duration::from_secs(0))
|
.conn_lifetime(time::Duration::from_secs(0))
|
||||||
.timeout(time::Duration::from_millis(500))
|
.timeout(time::Duration::from_millis(3000))
|
||||||
.ssl(builder.build())
|
.ssl(builder.build())
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
@ -152,7 +152,7 @@ impl TestServer {
|
|||||||
{
|
{
|
||||||
Connector::new()
|
Connector::new()
|
||||||
.conn_lifetime(time::Duration::from_secs(0))
|
.conn_lifetime(time::Duration::from_secs(0))
|
||||||
.timeout(time::Duration::from_millis(500))
|
.timeout(time::Duration::from_millis(3000))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user