1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-03 17:41:30 +02:00

Compare commits

...

9 Commits

Author SHA1 Message Date
0669ed0f06 soft-deprecate NormalizePath::default in v3 (#2529) 2021-12-18 22:57:23 +00:00
c9c36679e4 bump actix http version 2021-08-11 19:21:40 +01:00
655d7b4f05 sec fixes 2021-08-08 21:21:48 +01:00
24d525d978 prepare web 3.3.2 release 2020-12-01 22:22:46 +00:00
1f70ef155d Fix match_pattern() returning None for scope with resource of empty path (#1798)
* fix match_pattern function not returning pattern where scope has resource of path ""

* remove print in test

* make comparison on existing else if block

* add fix to changelog
2020-12-01 13:39:41 +00:00
7981e0068a Remove a panic in normalize middleware (#1762)
Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
2020-12-01 10:22:15 +09:00
32d59ca904 Upgrade socket2 dependency (#1803)
Upgrades to a version not making invalid assumptions about
the memory layout of std::net::SocketAddr
2020-12-01 04:18:02 +09:00
ea8bf36104 update web and awc changelogs 2020-11-29 16:35:35 +00:00
0b5b463cfa prepare web and awc releases
closes #1799
2020-11-29 16:33:45 +00:00
17 changed files with 159 additions and 51 deletions

View File

@ -3,6 +3,28 @@
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
## 3.3.3 - 2021-12-18
### Changed
* Soft-deprecate `NormalizePath::default()`, noting upcoming behavior change in v4. [#2529]
[#2529]: https://github.com/actix/actix-web/pull/2529
## 3.3.2 - 2020-12-01
### Fixed
* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762]
* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798]
* Increase minimum `socket2` version. [#1803]
[#1762]: https://github.com/actix/actix-web/pull/1762
[#1798]: https://github.com/actix/actix-web/pull/1798
[#1803]: https://github.com/actix/actix-web/pull/1803
## 3.3.1 - 2020-11-29
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
## 3.3.0 - 2020-11-25 ## 3.3.0 - 2020-11-25
### Added ### Added
* Add `Either<A, B>` extractor helper. [#1788] * Add `Either<A, B>` extractor helper. [#1788]

View File

@ -1,8 +1,8 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "3.3.0" version = "3.3.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
readme = "README.md" readme = "README.md"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
homepage = "https://actix.rs" homepage = "https://actix.rs"
@ -85,11 +85,11 @@ actix-threadpool = "0.3.1"
actix-tls = "2.0.0" actix-tls = "2.0.0"
actix-web-codegen = "0.4.0" actix-web-codegen = "0.4.0"
actix-http = "2.1.0" actix-http = "2.2.0"
awc = { version = "2.0.0", default-features = false } awc = { version = "2.0.3", default-features = false }
bytes = "0.5.3" bytes = "0.5.3"
derive_more = "0.99.2" derive_more = "0.99.5"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-channel = { version = "0.3.5", default-features = false } futures-channel = { version = "0.3.5", default-features = false }
futures-core = { version = "0.3.5", default-features = false } futures-core = { version = "0.3.5", default-features = false }
@ -97,7 +97,7 @@ futures-util = { version = "0.3.5", default-features = false }
fxhash = "0.2.1" fxhash = "0.2.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
socket2 = "0.3" socket2 = "0.3.16"
pin-project = "1.0.0" pin-project = "1.0.0"
regex = "1.4" regex = "1.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@ -1,15 +1,15 @@
<div align="center"> <div align="center">
<h1>Actix web</h1> <h1>Actix web</h1>
<p> <p>
<strong>Actix web is a powerful, pragmatic, and extremely fast web framework for Rust</strong> <strong>Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust</strong>
</p> </p>
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.0)](https://docs.rs/actix-web/3.3.0) [![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.3)](https://docs.rs/actix-web/3.3.3)
[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html)
![License](https://img.shields.io/crates/l/actix-web.svg) ![License](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/3.3.0/status.svg)](https://deps.rs/crate/actix-web/3.3.0) [![Dependency Status](https://deps.rs/crate/actix-web/3.3.3/status.svg)](https://deps.rs/crate/actix-web/3.3.3)
<br /> <br />
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "2.2.0" version = "2.2.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem" description = "HTTP primitives for the Actix ecosystem"
readme = "README.md" readme = "README.md"

View File

@ -3,9 +3,9 @@
> HTTP primitives for the Actix ecosystem. > HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.0)](https://docs.rs/actix-http/2.2.0) [![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.1)](https://docs.rs/actix-http/2.2.1)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http)
[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0) [![Dependency Status](https://deps.rs/crate/actix-http/2.2.1/status.svg)](https://deps.rs/crate/actix-http/2.2.1)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources ## Documentation & Resources

View File

@ -55,7 +55,7 @@ impl Error {
/// Similar to `as_response_error` but downcasts. /// Similar to `as_response_error` but downcasts.
pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> { pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> {
ResponseError::downcast_ref(self.cause.as_ref()) <dyn ResponseError>::downcast_ref(self.cause.as_ref())
} }
} }

View File

@ -67,6 +67,7 @@ pub(crate) trait MessageType: Sized {
let mut has_upgrade_websocket = false; let mut has_upgrade_websocket = false;
let mut expect = false; let mut expect = false;
let mut chunked = false; let mut chunked = false;
let mut seen_te = false;
let mut content_length = None; let mut content_length = None;
{ {
@ -85,8 +86,17 @@ pub(crate) trait MessageType: Sized {
}; };
match name { match name {
header::CONTENT_LENGTH => { header::CONTENT_LENGTH if content_length.is_some() => {
if let Ok(s) = value.to_str() { debug!("multiple Content-Length");
return Err(ParseError::Header);
}
header::CONTENT_LENGTH => match value.to_str() {
Ok(s) if s.trim().starts_with("+") => {
debug!("illegal Content-Length: {:?}", s);
return Err(ParseError::Header);
}
Ok(s) => {
if let Ok(len) = s.parse::<u64>() { if let Ok(len) = s.parse::<u64>() {
if len != 0 { if len != 0 {
content_length = Some(len); content_length = Some(len);
@ -95,15 +105,31 @@ pub(crate) trait MessageType: Sized {
debug!("illegal Content-Length: {:?}", s); debug!("illegal Content-Length: {:?}", s);
return Err(ParseError::Header); return Err(ParseError::Header);
} }
} else { }
Err(_) => {
debug!("illegal Content-Length: {:?}", value); debug!("illegal Content-Length: {:?}", value);
return Err(ParseError::Header); return Err(ParseError::Header);
} }
} },
// transfer-encoding // transfer-encoding
header::TRANSFER_ENCODING if seen_te => {
debug!("multiple Transfer-Encoding not allowed");
return Err(ParseError::Header);
}
header::TRANSFER_ENCODING => { header::TRANSFER_ENCODING => {
seen_te = true;
if let Ok(s) = value.to_str().map(|s| s.trim()) { if let Ok(s) = value.to_str().map(|s| s.trim()) {
chunked = s.eq_ignore_ascii_case("chunked"); if s.eq_ignore_ascii_case("chunked") {
chunked = true;
} else if s.eq_ignore_ascii_case("identity") {
// allow silently since multiple TE headers are already checked
} else {
debug!("illegal Transfer-Encoding: {:?}", s);
return Err(ParseError::Header);
}
} else { } else {
return Err(ParseError::Header); return Err(ParseError::Header);
} }
@ -510,19 +536,11 @@ impl ChunkedState {
size: &mut u64, size: &mut u64,
) -> Poll<Result<ChunkedState, io::Error>> { ) -> Poll<Result<ChunkedState, io::Error>> {
let radix = 16; let radix = 16;
match byte!(rdr) {
b @ b'0'..=b'9' => { let rem = match byte!(rdr) {
*size *= radix; b @ b'0'..=b'9' => b - b'0',
*size += u64::from(b - b'0'); b @ b'a'..=b'f' => b + 10 - b'a',
} b @ b'A'..=b'F' => b + 10 - b'A',
b @ b'a'..=b'f' => {
*size *= radix;
*size += u64::from(b + 10 - b'a');
}
b @ b'A'..=b'F' => {
*size *= radix;
*size += u64::from(b + 10 - b'A');
}
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)), b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
b';' => return Poll::Ready(Ok(ChunkedState::Extension)), b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)), b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
@ -532,8 +550,23 @@ impl ChunkedState {
"Invalid chunk size line: Invalid Size", "Invalid chunk size line: Invalid Size",
))); )));
} }
};
match size.checked_mul(radix) {
Some(n) => {
*size = n as u64;
*size += rem as u64;
Poll::Ready(Ok(ChunkedState::Size))
}
None => {
debug!("chunk size would overflow");
Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid chunk size line: Invalid Size",
)))
}
} }
Poll::Ready(Ok(ChunkedState::Size))
} }
fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> { fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
@ -552,6 +585,11 @@ impl ChunkedState {
fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> { fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) { match byte!(rdr) {
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)), b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
// strictly 0x20 (space) should be disallowed but we don't parse quoted strings here
0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid character in chunk extension",
))),
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
} }
} }
@ -977,13 +1015,7 @@ mod tests {
"GET /test HTTP/1.1\r\n\ "GET /test HTTP/1.1\r\n\
transfer-encoding: chnked\r\n\r\n", transfer-encoding: chnked\r\n\r\n",
); );
let req = parse_ready!(&mut buf); expect_parse_err!(&mut buf);
if let Ok(val) = req.chunked() {
assert!(!val);
} else {
unreachable!("Error");
}
} }
#[test] #[test]

View File

@ -80,6 +80,7 @@ pub(crate) trait MessageType: Sized {
match length { match length {
BodySize::Stream => { BodySize::Stream => {
if chunked { if chunked {
skip_len = true;
if camel_case { if camel_case {
dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n") dst.put_slice(b"\r\nTransfer-Encoding: chunked\r\n")
} else { } else {

View File

@ -3,6 +3,11 @@
## Unreleased - 2020-xx-xx ## Unreleased - 2020-xx-xx
## 2.0.3 - 2020-11-29
### Fixed
* Ensure `actix-http` dependency uses same `serde_urlencoded`.
## 2.0.2 - 2020-11-25 ## 2.0.2 - 2020-11-25
### Changed ### Changed
* Upgrade `serde_urlencoded` to `0.7`. [#1773] * Upgrade `serde_urlencoded` to `0.7`. [#1773]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "2.0.2" version = "2.0.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Async HTTP and WebSocket client library built on the Actix ecosystem" description = "Async HTTP and WebSocket client library built on the Actix ecosystem"
readme = "README.md" readme = "README.md"
@ -39,7 +39,7 @@ compress = ["actix-http/compress"]
[dependencies] [dependencies]
actix-codec = "0.3.0" actix-codec = "0.3.0"
actix-service = "1.0.6" actix-service = "1.0.6"
actix-http = "2.0.0" actix-http = "2.2.0"
actix-rt = "1.0.0" actix-rt = "1.0.0"
base64 = "0.13" base64 = "0.13"

View File

@ -3,9 +3,9 @@
> Async HTTP and WebSocket client library. > Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.2)](https://docs.rs/awc/2.0.2) [![Documentation](https://docs.rs/awc/badge.svg?version=2.0.3)](https://docs.rs/awc/2.0.3)
![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/2.0.2/status.svg)](https://deps.rs/crate/awc/2.0.2) [![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3)
[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & Resources ## Documentation & Resources

View File

@ -17,7 +17,7 @@ digraph {
"multipart" -> { "web" "service" "utils" } "multipart" -> { "web" "service" "utils" }
"http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" }
"http" -> { "actix" "tls" }[color=blue] // optional "http" -> { "actix" "tls" }[color=blue] // optional
"files" -> { "web" "http" } "files" -> { "web" }
"http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" }
// net // net

View File

@ -11,11 +11,11 @@ digraph {
"actix-http-test" "actix-http-test"
} }
"actix-web" -> { "actix-web-codegen" "actix-http" "awc" } "actix-web" -> { "actix-web-codegen" "actix-http" "awc" }
"awc" -> { "actix-http" } "awc" -> { "actix-http" }
"actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" }
"actix-multipart" -> { "actix-web" } "actix-multipart" -> { "actix-web" }
"actix-http" -> { "actix" }[color=blue] // optional "actix-http" -> { "actix" }[color=blue] // optional
"actix-files" -> { "actix-web" "actix-http" } "actix-files" -> { "actix-web" }
"actix-http-test" -> { "awc" } "actix-http-test" -> { "awc" }
} }

View File

@ -1,4 +1,4 @@
//! Actix web is a powerful, pragmatic, and extremely fast web framework for Rust. //! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
//! //!
//! ## Example //! ## Example
//! //!

View File

@ -31,7 +31,7 @@ impl Default for TrailingSlash {
} }
} }
#[derive(Default, Clone, Copy)] #[derive(Clone, Copy)]
/// `Middleware` to normalize request's URI in place /// `Middleware` to normalize request's URI in place
/// ///
/// Performs following: /// Performs following:
@ -56,6 +56,18 @@ impl Default for TrailingSlash {
pub struct NormalizePath(TrailingSlash); pub struct NormalizePath(TrailingSlash);
impl Default for NormalizePath {
fn default() -> Self {
log::warn!(
"`NormalizePath::default()` is deprecated. The default trailing slash behavior will \
change in v4 from `Always` to `Trim`. Update your call to `NormalizePath::new(...)` to \
avoid inaccessible routes when upgrading."
);
Self(TrailingSlash::default())
}
}
impl NormalizePath { impl NormalizePath {
/// Create new `NormalizePath` middleware with the specified trailing slash style. /// Create new `NormalizePath` middleware with the specified trailing slash style.
pub fn new(trailing_slash_style: TrailingSlash) -> Self { pub fn new(trailing_slash_style: TrailingSlash) -> Self {
@ -137,9 +149,9 @@ where
// so the change can not be deduced from the length comparison // so the change can not be deduced from the length comparison
if path != original_path { if path != original_path {
let mut parts = head.uri.clone().into_parts(); let mut parts = head.uri.clone().into_parts();
let pq = parts.path_and_query.as_ref().unwrap(); let query = parts.path_and_query.as_ref().and_then(|pq| pq.query());
let path = if let Some(q) = pq.query() { let path = if let Some(q) = query {
Bytes::from(format!("{}?{}", path, q)) Bytes::from(format!("{}?{}", path, q))
} else { } else {
Bytes::copy_from_slice(path.as_bytes()) Bytes::copy_from_slice(path.as_bytes())

View File

@ -675,4 +675,40 @@ mod tests {
let res = call_service(&mut srv, req).await; let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
} }
#[actix_rt::test]
async fn extract_path_pattern_complex() {
let mut srv = init_service(
App::new()
.service(web::scope("/user").service(web::scope("/{id}").service(
web::resource("").to(move |req: HttpRequest| {
assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned()));
HttpResponse::Ok().finish()
}),
)))
.service(web::resource("/").to(move |req: HttpRequest| {
assert_eq!(req.match_pattern(), Some("/".to_owned()));
HttpResponse::Ok().finish()
}))
.default_service(web::to(move |req: HttpRequest| {
assert!(req.match_pattern().is_none());
HttpResponse::Ok().finish()
})),
)
.await;
let req = TestRequest::get().uri("/user/test").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let req = TestRequest::get().uri("/").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
let req = TestRequest::get().uri("/not-exist").to_request();
let res = call_service(&mut srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
}
} }

View File

@ -86,7 +86,7 @@ impl ResourceMap {
if let Some(plen) = pattern.is_prefix_match(path) { if let Some(plen) = pattern.is_prefix_match(path) {
return rmap.has_resource(&path[plen..]); return rmap.has_resource(&path[plen..]);
} }
} else if pattern.is_match(path) { } else if pattern.is_match(path) || pattern.pattern() == "" && path == "/" {
return true; return true;
} }
} }