diff --git a/integration/tests_ok/follow_redirect.curl b/integration/tests_ok/follow_redirect.curl index 49e9321d3..e4bc887bb 100644 --- a/integration/tests_ok/follow_redirect.curl +++ b/integration/tests_ok/follow_redirect.curl @@ -1,2 +1,3 @@ curl --location 'http://localhost:8000/follow-redirect' -curl --location --request POST 'http://localhost:8000/follow-redirect-308' +curl --request POST --location 'http://localhost:8000/follow-redirect' +curl --request POST --location 'http://localhost:8000/follow-redirect-308' diff --git a/integration/tests_ok/follow_redirect.html b/integration/tests_ok/follow_redirect.html index 00e990f8a..918aa57cc 100644 --- a/integration/tests_ok/follow_redirect.html +++ b/integration/tests_ok/follow_redirect.html @@ -2,6 +2,12 @@ HTTP 200 `Followed redirect!` +# On 301, 302, 303, redirected request switch to GET. +# Otherwise, method are untouched. +POST http://localhost:8000/follow-redirect +HTTP 200 +`Followed redirect!` + POST http://localhost:8000/follow-redirect-308 HTTP 200 `Followed redirect POST!` diff --git a/integration/tests_ok/follow_redirect.hurl b/integration/tests_ok/follow_redirect.hurl index 708b06168..862212eef 100644 --- a/integration/tests_ok/follow_redirect.hurl +++ b/integration/tests_ok/follow_redirect.hurl @@ -2,6 +2,12 @@ GET http://localhost:8000/follow-redirect HTTP 200 `Followed redirect!` +# On 301, 302, 303, redirected request switch to GET. +# Otherwise, method are untouched. +POST http://localhost:8000/follow-redirect +HTTP 200 +`Followed redirect!` + POST http://localhost:8000/follow-redirect-308 HTTP 200 `Followed redirect POST!` diff --git a/integration/tests_ok/follow_redirect.json b/integration/tests_ok/follow_redirect.json index 468c4adfb..ff1a037cb 100644 --- a/integration/tests_ok/follow_redirect.json +++ b/integration/tests_ok/follow_redirect.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect"},"response":{"status":200,"body":{"type":"text","value":"Followed redirect!"}}},{"request":{"method":"POST","url":"http://localhost:8000/follow-redirect-308"},"response":{"status":200,"body":{"type":"text","value":"Followed redirect POST!"}}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect"},"response":{"status":200,"body":{"type":"text","value":"Followed redirect!"}}},{"request":{"method":"POST","url":"http://localhost:8000/follow-redirect"},"response":{"status":200,"body":{"type":"text","value":"Followed redirect!"}}},{"request":{"method":"POST","url":"http://localhost:8000/follow-redirect-308"},"response":{"status":200,"body":{"type":"text","value":"Followed redirect POST!"}}}]} diff --git a/integration/tests_ok/follow_redirect.py b/integration/tests_ok/follow_redirect.py index e40ed7f2f..06a121e3f 100644 --- a/integration/tests_ok/follow_redirect.py +++ b/integration/tests_ok/follow_redirect.py @@ -2,7 +2,7 @@ from app import app from flask import redirect, Response -@app.route("/follow-redirect") +@app.route("/follow-redirect", methods=["GET", "POST"]) def follow_redirect(): return redirect("http://localhost:8000/following-redirect") diff --git a/integration/tests_ok/follow_redirect_option.curl b/integration/tests_ok/follow_redirect_option.curl index 528bb6e7f..c0c9ccc79 100644 --- a/integration/tests_ok/follow_redirect_option.curl +++ b/integration/tests_ok/follow_redirect_option.curl @@ -1,3 +1,5 @@ curl 'http://localhost:8000/follow-redirect' curl 'http://localhost:8000/follow-redirect' curl --location 'http://localhost:8000/follow-redirect' +curl --request POST --location 'http://localhost:8000/follow-redirect' +curl --request POST --location 'http://localhost:8000/follow-redirect-308' diff --git a/integration/tests_ok/follow_redirect_option.html b/integration/tests_ok/follow_redirect_option.html index 015d55ad1..fbee320f2 100644 --- a/integration/tests_ok/follow_redirect_option.html +++ b/integration/tests_ok/follow_redirect_option.html @@ -23,5 +23,26 @@ [Asserts] header "Location" not exists `Followed redirect!` - - + + +# On 301, 302, 303, redirected request switch to GET. +# Otherwise, method are untouched. +POST http://localhost:8000/follow-redirect +[Options] +location: true + +HTTP 200 +[Asserts] +header "Location" not exists +`Followed redirect!` + + +POST http://localhost:8000/follow-redirect-308 +[Options] +location: true + +HTTP 200 +[Asserts] +header "Location" not exists +`Followed redirect POST!` + diff --git a/integration/tests_ok/follow_redirect_option.hurl b/integration/tests_ok/follow_redirect_option.hurl index a69be16c5..06db133bb 100644 --- a/integration/tests_ok/follow_redirect_option.hurl +++ b/integration/tests_ok/follow_redirect_option.hurl @@ -24,3 +24,24 @@ HTTP 200 header "Location" not exists `Followed redirect!` + +# On 301, 302, 303, redirected request switch to GET. +# Otherwise, method are untouched. +POST http://localhost:8000/follow-redirect +[Options] +location: true + +HTTP 200 +[Asserts] +header "Location" not exists +`Followed redirect!` + + +POST http://localhost:8000/follow-redirect-308 +[Options] +location: true + +HTTP 200 +[Asserts] +header "Location" not exists +`Followed redirect POST!` diff --git a/integration/tests_ok/follow_redirect_option.json b/integration/tests_ok/follow_redirect_option.json index 40a455d7d..36b839b46 100644 --- a/integration/tests_ok/follow_redirect_option.json +++ b/integration/tests_ok/follow_redirect_option.json @@ -1 +1 @@ -{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect"},"response":{"status":302,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"type":"equal","value":"http://localhost:8000/following-redirect"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect","options":[{"name":"location","value":false}]},"response":{"status":302,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"type":"equal","value":"http://localhost:8000/following-redirect"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect","options":[{"name":"location","value":true}]},"response":{"status":200,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"not":true,"type":"exist"}}],"body":{"type":"text","value":"Followed redirect!"}}}]} +{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect"},"response":{"status":302,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"type":"equal","value":"http://localhost:8000/following-redirect"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect","options":[{"name":"location","value":false}]},"response":{"status":302,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"type":"equal","value":"http://localhost:8000/following-redirect"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/follow-redirect","options":[{"name":"location","value":true}]},"response":{"status":200,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"not":true,"type":"exist"}}],"body":{"type":"text","value":"Followed redirect!"}}},{"request":{"method":"POST","url":"http://localhost:8000/follow-redirect","options":[{"name":"location","value":true}]},"response":{"status":200,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"not":true,"type":"exist"}}],"body":{"type":"text","value":"Followed redirect!"}}},{"request":{"method":"POST","url":"http://localhost:8000/follow-redirect-308","options":[{"name":"location","value":true}]},"response":{"status":200,"asserts":[{"query":{"type":"header","name":"Location"},"predicate":{"not":true,"type":"exist"}}],"body":{"type":"text","value":"Followed redirect POST!"}}}]} diff --git a/integration/tests_ok/follow_redirect_option.out b/integration/tests_ok/follow_redirect_option.out index 1ce8d0b1d..8cf3b25c1 100644 --- a/integration/tests_ok/follow_redirect_option.out +++ b/integration/tests_ok/follow_redirect_option.out @@ -1 +1 @@ -Followed redirect! \ No newline at end of file +Followed redirect POST! \ No newline at end of file diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index 2124f7c19..2b07c5fa9 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -86,7 +86,7 @@ impl Client { let call = self.execute(&request_spec, options, logger)?; let base_url = call.request.base_url()?; let redirect_url = self.get_follow_location(&call.response, &base_url); - let call_response_status = call.response.status; + let status = call.response.status; calls.push(call); if !options.follow_location || redirect_url.is_none() { break; @@ -94,21 +94,16 @@ impl Client { let redirect_url = redirect_url.unwrap(); logger.debug(""); logger.debug(format!("=> Redirect to {redirect_url}").as_str()); + logger.debug(""); redirect_count += 1; if let Some(max_redirect) = options.max_redirect { if redirect_count > max_redirect { return Err(HttpError::TooManyRedirect); } } - let new_request_method = self.get_redirected_request_method( - options, - logger, - call_response_status, - request_spec.method, - ); - logger.debug(""); + let redirect_method = get_redirect_method(status, request_spec.method); request_spec = RequestSpec { - method: new_request_method, + method: redirect_method, url: redirect_url, ..Default::default() }; @@ -116,32 +111,6 @@ impl Client { Ok(calls) } - pub fn get_redirected_request_method( - &self, - options: &ClientOptions, - logger: &Logger, - response_status: u32, - original_method: Method, - ) -> Method { - // this replicates curls behavior - return match response_status { - 301 | 302 | 303 => { - // spaces at the start to align with the arrow used in a previous - // debug line - logger.debug( - format!( - " Resending with method GET because response code was {response_status}" - ) - .as_str(), - ); - Method("GET".to_string()) - } - // Could be only 307 and 308, but curl does this for all 3xx - // codes not converted to GET above. - _ => original_method, - }; - } - /// Executes an HTTP request `request_spec`, without following redirection and returns a /// pair of [`Request`], [`Response`]. pub fn execute( @@ -685,6 +654,17 @@ fn get_redirect_url(location: &str, base_url: &str) -> String { } } +/// Returns the method used for redirecting a request/response with `response_status`. +fn get_redirect_method(response_status: u32, original_method: Method) -> Method { + // This replicates curl's behavior + match response_status { + 301 | 302 | 303 => Method("GET".to_string()), + // Could be only 307 and 308, but curl does this for all 3xx + // codes not converted to GET above. + _ => original_method, + } +} + /// Returns cookies from both cookies from the cookie storage and the request. pub fn all_cookies(cookie_storage: &[Cookie], request_spec: &RequestSpec) -> Vec { let mut cookies = request_spec.cookies.clone(); @@ -844,4 +824,32 @@ mod tests { "http://localhost:8000/redirected".to_string() ); } + + #[test] + fn test_redirect_method() { + // Status of the response to be redirected | method of the original request | method of the new request + let datas = [ + (301, "GET", "GET"), + (301, "POST", "GET"), + (301, "DELETE", "GET"), + (302, "GET", "GET"), + (302, "POST", "GET"), + (302, "DELETE", "GET"), + (303, "GET", "GET"), + (303, "POST", "GET"), + (303, "DELETE", "GET"), + (304, "GET", "GET"), + (304, "POST", "POST"), + (304, "DELETE", "DELETE"), + (308, "GET", "GET"), + (308, "POST", "POST"), + (308, "DELETE", "DELETE"), + ]; + for (status, original, redirected) in datas { + assert_eq!( + get_redirect_method(status, Method(original.to_string())), + Method(redirected.to_string()) + ); + } + } }