1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-18 15:35:43 +02:00

Compare commits

..

13 Commits

Author SHA1 Message Date
Nikolay Kim
5cb2d500d1 update actix-web-actors 2019-11-14 08:58:24 +06:00
Nikolay Kim
0212c618c6 prepare actix-web release 2019-11-14 08:55:37 +06:00
Feiko Nanninga
88110ed268 Add security note to ConnectionInfo::remote() (#1158) 2019-11-14 08:32:47 +06:00
Nikolay Kim
fba02fdd8c prep awc release 2019-11-06 11:33:25 -08:00
Nikolay Kim
b2934ad8d2 prep actix-file release 2019-11-06 11:25:26 -08:00
Nikolay Kim
f7f410d033 fix test order dep 2019-11-06 11:20:47 -08:00
Nikolay Kim
885ff7396e prepare actox-http release 2019-11-06 10:35:13 -08:00
Erlend Langseth
61b38e8d0d Increase timeouts in test-server (#1153) 2019-11-06 06:09:22 -08:00
Hung-I Wang
edcde67076 Fix escaping/encoding problems in Content-Disposition header (#1151)
* Fix filename encoding in Content-Disposition of acitx_files::NamedFile

* Add more comments on how to use Content-Disposition header properly & Fix some trivial problems

* Improve Content-Disposition filename(*) parameters of actix_files::NamedFile

* Tweak Content-Disposition parse to accept empty param value in quoted-string

* Fix typos in comments in .../content_disposition.rs (pointed out by @JohnTitor)

* Update CHANGES.md

* Update CHANGES.md again
2019-11-06 06:08:37 -08:00
Jonathas Conceição
f0612f7570 awc: Add support for setting query from Serialize type for client request (#1130)
Signed-off-by: Jonathas-Conceicao <jadoliveira@inf.ufpel.edu.br>
2019-10-26 08:27:14 +03:00
Anton Lazarev
ace98e3a1e support Host guards when Host header is unset (#1129) 2019-10-15 05:05:54 +06:00
Nikolay Kim
1ca9d87f0a prep actix-web-codegen release 2019-10-14 21:35:53 +06:00
DanSnow
967f965405 Update syn & quote to 1.0 (#1133)
* chore(actix-web-codegen): Upgrade syn and quote to 1.0

* feat(actix-web-codegen): Generate better error message

* doc(actix-web-codegen): Update CHANGES.md

* fix: Build with stable rust
2019-10-14 21:34:17 +06:00
23 changed files with 428 additions and 176 deletions

View File

@@ -1,11 +1,16 @@
# Changes # Changes
## [1.0.9] - 2019-xx-xx ## [1.0.9] - 2019-11-14
### Added ### Added
* 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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.8" version = "1.0.9"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@@ -78,7 +78,7 @@ actix-utils = "0.4.4"
actix-router = "0.1.5" actix-router = "0.1.5"
actix-rt = "0.2.4" actix-rt = "0.2.4"
actix-web-codegen = "0.1.2" actix-web-codegen = "0.1.2"
actix-http = "0.2.9" actix-http = "0.2.11"
actix-server = "0.6.1" actix-server = "0.6.1"
actix-server-config = "0.1.2" actix-server-config = "0.1.2"
actix-testing = "0.1.0" actix-testing = "0.1.0"
@@ -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"

View File

@@ -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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.1.6" version = "0.1.7"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@@ -19,7 +19,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "1.0.8", default-features = false } actix-web = { version = "1.0.8", default-features = false }
actix-http = "0.2.9" actix-http = "0.2.11"
actix-service = "0.4.1" actix-service = "0.4.1"
bitflags = "1" bitflags = "1"
bytes = "0.4" bytes = "0.4"

View File

@@ -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")

View File

@@ -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)
}; };

View File

@@ -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

View File

@@ -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"

View File

@@ -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"));
} }
} }

View File

@@ -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]

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [1.0.3] - 2019-11-14
* Update actix-web and actix-http dependencies
## [1.0.2] - 2019-07-20 ## [1.0.2] - 2019-07-20
* Add `ws::start_with_addr()`, returning the address of the created actor, along * Add `ws::start_with_addr()`, returning the address of the created actor, along

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "1.0.2" version = "1.0.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework." description = "Actix actors support for actix web framework."
readme = "README.md" readme = "README.md"
@@ -19,8 +19,8 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix = "0.8.3" actix = "0.8.3"
actix-web = "1.0.3" actix-web = "1.0.9"
actix-http = "0.2.5" actix-http = "0.2.11"
actix-codec = "0.1.2" actix-codec = "0.1.2"
bytes = "0.4" bytes = "0.4"
futures = "0.1.25" futures = "0.1.25"

View File

@@ -1,5 +1,11 @@
# Changes # Changes
## [0.1.3] - 2019-10-14
* Bump up `syn` & `quote` to 1.0
* Provide better error message
## [0.1.2] - 2019-06-04 ## [0.1.2] - 2019-06-04
* Add macros for head, options, trace, connect and patch http methods * Add macros for head, options, trace, connect and patch http methods

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.1.2" version = "0.1.3"
description = "Actix web proc macros" description = "Actix web proc macros"
readme = "README.md" readme = "README.md"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
@@ -12,8 +12,9 @@ workspace = ".."
proc-macro = true proc-macro = true
[dependencies] [dependencies]
quote = "0.6.12" quote = "1"
syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] } syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0" } actix-web = { version = "1.0.0" }

View File

@@ -58,7 +58,10 @@ use syn::parse_macro_input;
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Get); let gen = match route::Route::new(args, input, route::GuardType::Get) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -70,7 +73,10 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Post); let gen = match route::Route::new(args, input, route::GuardType::Post) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -82,7 +88,10 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Put); let gen = match route::Route::new(args, input, route::GuardType::Put) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -94,7 +103,10 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Delete); let gen = match route::Route::new(args, input, route::GuardType::Delete) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -106,7 +118,10 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Head); let gen = match route::Route::new(args, input, route::GuardType::Head) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -118,7 +133,10 @@ pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Connect); let gen = match route::Route::new(args, input, route::GuardType::Connect) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -130,7 +148,10 @@ pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Options); let gen = match route::Route::new(args, input, route::GuardType::Options) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -142,7 +163,10 @@ pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Trace); let gen = match route::Route::new(args, input, route::GuardType::Trace) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@@ -154,6 +178,9 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Patch); let gen = match route::Route::new(args, input, route::GuardType::Patch) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }

View File

@@ -1,21 +1,23 @@
extern crate proc_macro; extern crate proc_macro;
use std::fmt;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{AttributeArgs, Ident, NestedMeta};
enum ResourceType { enum ResourceType {
Async, Async,
Sync, Sync,
} }
impl fmt::Display for ResourceType { impl ToTokens for ResourceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn to_tokens(&self, stream: &mut TokenStream2) {
match *self { let ident = match self {
ResourceType::Async => write!(f, "to_async"), ResourceType::Async => "to_async",
ResourceType::Sync => write!(f, "to"), ResourceType::Sync => "to",
} };
let ident = Ident::new(ident, Span::call_site());
stream.append(ident);
} }
} }
@@ -32,63 +34,89 @@ pub enum GuardType {
Patch, Patch,
} }
impl fmt::Display for GuardType { impl GuardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn as_str(&self) -> &'static str {
match *self { match self {
GuardType::Get => write!(f, "Get"), GuardType::Get => "Get",
GuardType::Post => write!(f, "Post"), GuardType::Post => "Post",
GuardType::Put => write!(f, "Put"), GuardType::Put => "Put",
GuardType::Delete => write!(f, "Delete"), GuardType::Delete => "Delete",
GuardType::Head => write!(f, "Head"), GuardType::Head => "Head",
GuardType::Connect => write!(f, "Connect"), GuardType::Connect => "Connect",
GuardType::Options => write!(f, "Options"), GuardType::Options => "Options",
GuardType::Trace => write!(f, "Trace"), GuardType::Trace => "Trace",
GuardType::Patch => write!(f, "Patch"), GuardType::Patch => "Patch",
} }
} }
} }
pub struct Args { impl ToTokens for GuardType {
name: syn::Ident, fn to_tokens(&self, stream: &mut TokenStream2) {
path: String, let ident = self.as_str();
ast: syn::ItemFn, let ident = Ident::new(ident, Span::call_site());
resource_type: ResourceType, stream.append(ident);
pub guard: GuardType, }
pub extra_guards: Vec<String>,
} }
impl fmt::Display for Args { struct Args {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { path: syn::LitStr,
let ast = &self.ast; guards: Vec<Ident>,
let guards = format!(".guard(actix_web::guard::{}())", self.guard); }
let guards = self.extra_guards.iter().fold(guards, |acc, val| {
format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)
});
write!( impl Args {
f, fn new(args: AttributeArgs) -> syn::Result<Self> {
" let mut path = None;
#[allow(non_camel_case_types)] let mut guards = Vec::new();
pub struct {name}; for arg in args {
match arg {
impl actix_web::dev::HttpServiceFactory for {name} {{ NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
fn register(self, config: &mut actix_web::dev::AppService) {{ None => {
{ast} path = Some(lit);
}
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name}); _ => {
return Err(syn::Error::new_spanned(
actix_web::dev::HttpServiceFactory::register(resource, config) lit,
}} "Multiple paths specified! Should be only one!",
}}", ));
name = self.name, }
ast = quote!(#ast), },
path = self.path, NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
guards = guards, if nv.path.is_ident("guard") {
to = self.resource_type if let syn::Lit::Str(lit) = nv.lit {
) guards.push(Ident::new(&lit.value(), Span::call_site()));
} else {
return Err(syn::Error::new_spanned(
nv.lit,
"Attribute guard expects literal string!",
));
}
} else {
return Err(syn::Error::new_spanned(
nv.path,
"Unknown attribute key is specified. Allowed: guard",
));
}
}
arg => {
return Err(syn::Error::new_spanned(arg, "Unknown attribute"));
}
}
}
Ok(Args {
path: path.unwrap(),
guards,
})
} }
} }
pub struct Route {
name: syn::Ident,
args: Args,
ast: syn::ItemFn,
resource_type: ResourceType,
guard: GuardType,
}
fn guess_resource_type(typ: &syn::Type) -> ResourceType { fn guess_resource_type(typ: &syn::Type) -> ResourceType {
let mut guess = ResourceType::Sync; let mut guess = ResourceType::Sync;
@@ -111,75 +139,73 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType {
guess guess
} }
impl Args { impl Route {
pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self { pub fn new(
args: AttributeArgs,
input: TokenStream,
guard: GuardType,
) -> syn::Result<Self> {
if args.is_empty() { if args.is_empty() {
panic!( return Err(syn::Error::new(
"invalid server definition, expected: #[{}(\"some path\")]", Span::call_site(),
guard format!(
); r#"invalid server definition, expected #[{}("<some path>")]"#,
guard.as_str().to_ascii_lowercase()
),
));
} }
let ast: syn::ItemFn = syn::parse(input)?;
let name = ast.sig.ident.clone();
let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function"); let args = Args::new(args)?;
let name = ast.ident.clone();
let mut extra_guards = Vec::new(); let resource_type = if ast.sig.asyncness.is_some() {
let mut path = None;
for arg in args {
match arg {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
if path.is_some() {
panic!("Multiple paths specified! Should be only one!")
}
let fname = quote!(#fname).to_string();
path = Some(fname.as_str()[1..fname.len() - 1].to_owned())
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => {
match ident.ident.to_string().to_lowercase().as_str() {
"guard" => match ident.lit {
syn::Lit::Str(ref text) => extra_guards.push(text.value()),
_ => panic!("Attribute guard expects literal string!"),
},
attr => panic!(
"Unknown attribute key is specified: {}. Allowed: guard",
attr
),
}
}
attr => panic!("Unknown attribute{:?}", attr),
}
}
let resource_type = if ast.asyncness.is_some() {
ResourceType::Async ResourceType::Async
} else { } else {
match ast.decl.output { match ast.sig.output {
syn::ReturnType::Default => panic!( syn::ReturnType::Default => {
"Function {} has no return type. Cannot be used as handler", return Err(syn::Error::new_spanned(
name ast,
), "Function has no return type. Cannot be used as handler",
));
}
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
} }
}; };
let path = path.unwrap(); Ok(Self {
Self {
name, name,
path, args,
ast, ast,
resource_type, resource_type,
guard, guard,
extra_guards, })
}
} }
pub fn generate(&self) -> TokenStream { pub fn generate(&self) -> TokenStream {
let text = self.to_string(); let name = &self.name;
let guard = &self.guard;
let ast = &self.ast;
let path = &self.args.path;
let extra_guards = &self.args.guards;
let resource_type = &self.resource_type;
let stream = quote! {
#[allow(non_camel_case_types)]
pub struct #name;
match text.parse() { impl actix_web::dev::HttpServiceFactory for #name {
Ok(res) => res, fn register(self, config: &mut actix_web::dev::AppService) {
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text), #ast
}
let resource = actix_web::Resource::new(#path)
.guard(actix_web::guard::#guard())
#(.guard(actix_web::guard::fn_guard(#extra_guards)))*
.#resource_type(#name);
actix_web::dev::HttpServiceFactory::register(resource, config)
}
}
};
stream.into()
} }
} }

View File

@@ -1,5 +1,9 @@
# Changes # Changes
## [0.2.8] - 2019-11-06
* Add support for setting query from Serialize type for client request.
## [0.2.7] - 2019-09-25 ## [0.2.7] - 2019-09-25

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "0.2.7" version = "0.2.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client." description = "Actix http client."
readme = "README.md" readme = "README.md"
@@ -44,7 +44,7 @@ flate2-rust = ["actix-http/flate2-rust"]
[dependencies] [dependencies]
actix-codec = "0.1.2" actix-codec = "0.1.2"
actix-service = "0.4.1" actix-service = "0.4.1"
actix-http = "0.2.10" actix-http = "0.2.11"
base64 = "0.10.1" base64 = "0.10.1"
bytes = "0.4" bytes = "0.4"
derive_more = "0.15.0" derive_more = "0.15.0"
@@ -62,8 +62,8 @@ rustls = { version = "0.15.2", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "0.2.2" actix-rt = "0.2.2"
actix-web = { version = "1.0.0", features=["ssl"] } actix-web = { version = "1.0.8", features=["ssl"] }
actix-http = { version = "0.2.10", features=["ssl"] } actix-http = { version = "0.2.11", features=["ssl"] }
actix-http-test = { version = "0.2.0", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] }
actix-utils = "0.4.1" actix-utils = "0.4.1"
actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] }

View File

@@ -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");
}
} }

View File

@@ -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();

View File

@@ -162,6 +162,12 @@ impl ConnectionInfo {
/// - Forwarded /// - Forwarded
/// - X-Forwarded-For /// - X-Forwarded-For
/// - peer name of opened socket /// - peer name of opened socket
///
/// # Security
/// Do not use this function for security purposes, unless you can ensure the Forwarded and
/// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket
/// address explicitly, use
/// [`HttpRequest::peer_addr()`](../web/struct.HttpRequest.html#method.peer_addr) instead.
#[inline] #[inline]
pub fn remote(&self) -> Option<&str> { pub fn remote(&self) -> Option<&str> {
if let Some(ref r) = self.remote { if let Some(ref r) = self.remote {

View File

@@ -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

View File

@@ -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()
} }
}; };