mirror of https://github.com/fafhrd91/actix-web
recover from malformed location header when redirecting
based on code from #2111 closes #2102
This commit is contained in:
parent
f983103e44
commit
9b1db1b950
|
@ -1,22 +1,25 @@
|
||||||
//! `awc` is an asynchronous HTTP and WebSocket client library.
|
//! `awc` is an asynchronous HTTP and WebSocket client library.
|
||||||
//!
|
//!
|
||||||
//! # Making a GET request
|
//! # `GET` Requests
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
|
//! // create client
|
||||||
//! let mut client = awc::Client::default();
|
//! let mut client = awc::Client::default();
|
||||||
//! let response = client.get("http://www.rust-lang.org") // <- Create request builder
|
|
||||||
//! .insert_header(("User-Agent", "Actix-web"))
|
|
||||||
//! .send() // <- Send http request
|
|
||||||
//! .await?;
|
|
||||||
//!
|
//!
|
||||||
//! println!("Response: {:?}", response);
|
//! // construct request
|
||||||
|
//! let req = client.get("http://www.rust-lang.org")
|
||||||
|
//! .insert_header(("User-Agent", "awc/3.0"));
|
||||||
|
//!
|
||||||
|
//! // send request and await response
|
||||||
|
//! let res = req.send().await?;
|
||||||
|
//! println!("Response: {:?}", res);
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Making POST requests
|
//! # `POST` Requests
|
||||||
//! ## Raw body contents
|
//! ## Raw Body
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
|
@ -28,20 +31,6 @@
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Forms
|
|
||||||
//! ```no_run
|
|
||||||
//! # #[actix_rt::main]
|
|
||||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
|
||||||
//! let params = [("foo", "bar"), ("baz", "quux")];
|
|
||||||
//!
|
|
||||||
//! let mut client = awc::Client::default();
|
|
||||||
//! let response = client.post("http://httpbin.org/post")
|
|
||||||
//! .send_form(¶ms)
|
|
||||||
//! .await?;
|
|
||||||
//! # Ok(())
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## JSON
|
//! ## JSON
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
|
@ -59,6 +48,20 @@
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! ## URL Encoded Form
|
||||||
|
//! ```no_run
|
||||||
|
//! # #[actix_rt::main]
|
||||||
|
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||||
|
//! let params = [("foo", "bar"), ("baz", "quux")];
|
||||||
|
//!
|
||||||
|
//! let mut client = awc::Client::default();
|
||||||
|
//! let response = client.post("http://httpbin.org/post")
|
||||||
|
//! .send_form(¶ms)
|
||||||
|
//! .await?;
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! # Response Compression
|
//! # Response Compression
|
||||||
//! All [official][iana-encodings] and common content encoding codecs are supported, optionally.
|
//! All [official][iana-encodings] and common content encoding codecs are supported, optionally.
|
||||||
//!
|
//!
|
||||||
|
@ -76,11 +79,12 @@
|
||||||
//!
|
//!
|
||||||
//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
|
//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
|
||||||
//!
|
//!
|
||||||
//! # WebSocket support
|
//! # WebSockets
|
||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # #[actix_rt::main]
|
//! # #[actix_rt::main]
|
||||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! use futures_util::{sink::SinkExt, stream::StreamExt};
|
//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _};
|
||||||
|
//!
|
||||||
//! let (_resp, mut connection) = awc::Client::new()
|
//! let (_resp, mut connection) = awc::Client::new()
|
||||||
//! .ws("ws://echo.websocket.org")
|
//! .ws("ws://echo.websocket.org")
|
||||||
//! .connect()
|
//! .connect()
|
||||||
|
@ -89,8 +93,9 @@
|
||||||
//! connection
|
//! connection
|
||||||
//! .send(awc::ws::Message::Text("Echo".into()))
|
//! .send(awc::ws::Message::Text("Echo".into()))
|
||||||
//! .await?;
|
//! .await?;
|
||||||
|
//!
|
||||||
//! let response = connection.next().await.unwrap()?;
|
//! let response = connection.next().await.unwrap()?;
|
||||||
//! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into()));
|
//! assert_eq!(response, awc::ws::Frame::Text("Echo".into()));
|
||||||
//! # Ok(())
|
//! # Ok(())
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
|
@ -246,26 +246,32 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
|
fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result<Uri, SendRequestError> {
|
||||||
let uri = res
|
// responses without this header are not processed
|
||||||
.headers()
|
let location = res.headers().get(header::LOCATION).unwrap();
|
||||||
.get(header::LOCATION)
|
|
||||||
.map(|value| {
|
// try to parse the location and resolve to a full URI but fall back to default if it fails
|
||||||
// try to parse the location to a full uri
|
let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default());
|
||||||
let uri = Uri::try_from(value.as_bytes())
|
|
||||||
.map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?;
|
let uri = if uri.scheme().is_none() || uri.authority().is_none() {
|
||||||
if uri.scheme().is_none() || uri.authority().is_none() {
|
let builder = Uri::builder()
|
||||||
let uri = Uri::builder()
|
|
||||||
.scheme(prev_uri.scheme().cloned().unwrap())
|
.scheme(prev_uri.scheme().cloned().unwrap())
|
||||||
.authority(prev_uri.authority().cloned().unwrap())
|
.authority(prev_uri.authority().cloned().unwrap());
|
||||||
.path_and_query(value.as_bytes())
|
|
||||||
.build()?;
|
// when scheme or authority is missing treat the location value as path and query
|
||||||
Ok::<_, SendRequestError>(uri)
|
// recover error where location does not have leading slash
|
||||||
|
let path = if location.as_bytes().starts_with(b"/") {
|
||||||
|
location.as_bytes().to_owned()
|
||||||
} else {
|
} else {
|
||||||
Ok(uri)
|
[b"/", location.as_bytes()].concat()
|
||||||
}
|
};
|
||||||
})
|
|
||||||
// TODO: this error type is wrong.
|
builder
|
||||||
.ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??;
|
.path_and_query(path)
|
||||||
|
.build()
|
||||||
|
.map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))?
|
||||||
|
} else {
|
||||||
|
uri
|
||||||
|
};
|
||||||
|
|
||||||
Ok(uri)
|
Ok(uri)
|
||||||
}
|
}
|
||||||
|
@ -294,7 +300,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_basic_redirect() {
|
async fn basic_redirect() {
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.disable_redirects()
|
.disable_redirects()
|
||||||
.wrap(Redirect::new().max_redirect_times(10))
|
.wrap(Redirect::new().max_redirect_times(10))
|
||||||
|
@ -319,6 +325,27 @@ mod tests {
|
||||||
assert_eq!(res.status().as_u16(), 400);
|
assert_eq!(res.status().as_u16(), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn redirect_relative_without_leading_slash() {
|
||||||
|
let client = ClientBuilder::new().finish();
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/").route(web::to(|| async {
|
||||||
|
HttpResponse::Found()
|
||||||
|
.insert_header(("location", "abc/"))
|
||||||
|
.finish()
|
||||||
|
})))
|
||||||
|
.service(
|
||||||
|
web::resource("/abc/")
|
||||||
|
.route(web::to(|| async { HttpResponse::Accepted().finish() })),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status(), StatusCode::ACCEPTED);
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn redirect_without_location() {
|
async fn redirect_without_location() {
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
|
|
Loading…
Reference in New Issue