From a10339cb209ddb34348a9a3cc78f7319d4c8c6dc Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Fri, 4 Sep 2020 06:43:20 -0400 Subject: [PATCH] fix: Handle bad redirects more gracefully (#7342) --- cli/http_util.rs | 114 +++++++++++++++++++++++-------------------- test_util/src/lib.rs | 8 ++- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/cli/http_util.rs b/cli/http_util.rs index 72aaee6827..9fb4ab9141 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -2,7 +2,6 @@ use crate::version; use bytes::Bytes; use deno_core::ErrBox; -use futures::future::FutureExt; use reqwest::header::HeaderMap; use reqwest::header::HeaderValue; use reqwest::header::IF_NONE_MATCH; @@ -92,77 +91,71 @@ pub enum FetchOnceResult { /// yields Code(ResultPayload). /// If redirect occurs, does not follow and /// yields Redirect(url). -pub fn fetch_once( +pub async fn fetch_once( client: Client, url: &Url, cached_etag: Option, -) -> impl Future> { +) -> Result { let url = url.clone(); - let fut = async move { - let mut request = client.get(url.clone()); + let mut request = client.get(url.clone()); - if let Some(etag) = cached_etag { - let if_none_match_val = HeaderValue::from_str(&etag).unwrap(); - request = request.header(IF_NONE_MATCH, if_none_match_val); - } - let response = request.send().await?; + if let Some(etag) = cached_etag { + let if_none_match_val = HeaderValue::from_str(&etag).unwrap(); + request = request.header(IF_NONE_MATCH, if_none_match_val); + } + let response = request.send().await?; - if response.status() == StatusCode::NOT_MODIFIED { - return Ok(FetchOnceResult::NotModified); - } + if response.status() == StatusCode::NOT_MODIFIED { + return Ok(FetchOnceResult::NotModified); + } - let mut headers_: HashMap = HashMap::new(); - let headers = response.headers(); + let mut headers_: HashMap = HashMap::new(); + let headers = response.headers(); - if let Some(warning) = headers.get("X-Deno-Warning") { - eprintln!( - "{} {}", - crate::colors::yellow("Warning"), - warning.to_str().unwrap() - ); - } + if let Some(warning) = headers.get("X-Deno-Warning") { + eprintln!( + "{} {}", + crate::colors::yellow("Warning"), + warning.to_str().unwrap() + ); + } - for key in headers.keys() { - let key_str = key.to_string(); - let values = headers.get_all(key); - let values_str = values - .iter() - .map(|e| e.to_str().unwrap().to_string()) - .collect::>() - .join(","); - headers_.insert(key_str, values_str); - } - - if response.status().is_redirection() { - let location_string = response - .headers() - .get(LOCATION) - .expect("url redirection should provide 'location' header") - .to_str() - .unwrap(); + for key in headers.keys() { + let key_str = key.to_string(); + let values = headers.get_all(key); + let values_str = values + .iter() + .map(|e| e.to_str().unwrap().to_string()) + .collect::>() + .join(","); + headers_.insert(key_str, values_str); + } + if response.status().is_redirection() { + if let Some(location) = response.headers().get(LOCATION) { + let location_string = location.to_str().unwrap(); debug!("Redirecting to {:?}...", &location_string); let new_url = resolve_url_from_location(&url, location_string); return Ok(FetchOnceResult::Redirect(new_url, headers_)); + } else { + return Err(ErrBox::error(format!( + "Redirection from '{}' did not provide location header", + url + ))); } + } - if response.status().is_client_error() - || response.status().is_server_error() - { - let err = io::Error::new( - io::ErrorKind::Other, - format!("Import '{}' failed: {}", &url, response.status()), - ); - return Err(err.into()); - } + if response.status().is_client_error() || response.status().is_server_error() + { + let err = + ErrBox::error(format!("Import '{}' failed: {}", &url, response.status())); + return Err(err); + } - let body = response.bytes().await?.to_vec(); + let body = response.bytes().await?.to_vec(); - return Ok(FetchOnceResult::Code(body, headers_)); - }; - - fut.boxed() + Ok(FetchOnceResult::Code(body, headers_)) } /// Wraps reqwest `Response` so that it can be exposed as an `AsyncRead` and integrated @@ -500,4 +493,17 @@ mod tests { panic!(); } } + + #[tokio::test] + async fn bad_redirect() { + let _g = test_util::http_server(); + let url_str = "http://127.0.0.1:4545/bad_redirect"; + let url = Url::parse(url_str).unwrap(); + let client = create_http_client(None).unwrap(); + let result = fetch_once(client, &url, None).await; + assert!(result.is_err()); + let err = result.unwrap_err(); + // Check that the error message contains the original URL + assert!(err.to_string().contains(url_str)); + } } diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index 84e353a8b5..1f8548e343 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -264,6 +264,11 @@ pub async fn run_all_servers() { ); Box::new(res) }); + let bad_redirect = warp::path("bad_redirect").map(|| -> Box { + let mut res = Response::new(Body::from("")); + *res.status_mut() = StatusCode::FOUND; + Box::new(res) + }); let etag_script = warp::path!("etag_script.ts") .and(warp::header::optional::("if-none-match")) @@ -404,7 +409,8 @@ pub async fn run_all_servers() { .or(xtypescripttypes) .or(echo_server) .or(echo_multipart_file) - .or(multipart_form_data); + .or(multipart_form_data) + .or(bad_redirect); let http_fut = warp::serve(content_type_handler.clone()).bind(([127, 0, 0, 1], PORT));