Fix regression in following redirection with query sections.

This commit is contained in:
Jean-Christophe Amiel 2025-07-29 17:19:28 +02:00
parent 9c3096eb6a
commit 607ca0dac1
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
4 changed files with 91 additions and 18 deletions

View File

@ -10,6 +10,9 @@
GET http://localhost:8000/follow-redirect
Accept: text/plain
[Query]
# We add a query param to test that query params strings are not forwarded
foo: bar
HTTP 200
[Asserts]
redirects count == 2

View File

@ -19,9 +19,16 @@ GET http://localhost:8000/follow-redirect
Accept: text/plain
[Options]
location: true
[Query]
# We add a query param to test that query params strings are not forwarded
foo: bar
HTTP 200
[Asserts]
header "Location" not exists
redirects count == 2
redirects nth 0 location == "http://localhost:8000/following-redirect"
redirects nth 1 location == "http://localhost:8000/followed-redirect"
url == "http://localhost:8000/followed-redirect"
`Followed redirect!`
@ -35,6 +42,9 @@ location: true
HTTP 200
[Asserts]
header "Location" not exists
redirects count == 1
redirects nth 0 location == "http://localhost:8000/followed-redirect-from-post"
url == "http://localhost:8000/followed-redirect-from-post"
`Followed redirect!`
@ -46,6 +56,9 @@ location: true
HTTP 200
[Asserts]
header "Location" not exists
redirects count == 1
redirects nth 0 location == "http://localhost:8000/followed-redirect-post"
url == "http://localhost:8000/followed-redirect-post"
`Followed redirect POST!`
@ -55,6 +68,8 @@ Accept: text/plain
location: true
HTTP 200
[Asserts]
redirects nth 0 location == "http://localhost:8000/follow-redirect/bar"
redirects count == 1
url == "http://localhost:8000/follow-redirect/bar"
`Followed relative redirect!`
@ -68,6 +83,9 @@ location: true
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://127.0.0.1:8000/followed-redirect-basic-auth"
url == "http://127.0.0.1:8000/followed-redirect-basic-auth"
header "Location" not exists
`Followed redirect without Authorization header!`
@ -83,6 +101,9 @@ location: true
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://127.0.0.1:8000/followed-redirect-basic-auth"
url == "http://127.0.0.1:8000/followed-redirect-basic-auth"
header "Location" not exists
`Followed redirect without Authorization header!`
@ -97,6 +118,9 @@ location: true
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://localhost:8000/followed-redirect-basic-auth"
url == "http://localhost:8000/followed-redirect-basic-auth"
header "Location" not exists
`Followed redirect with Authorization header!`
@ -110,6 +134,9 @@ user: bob@email.com:secret
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://127.0.0.1:8000/followed-redirect-basic-auth"
url == "http://127.0.0.1:8000/followed-redirect-basic-auth"
header "Location" not exists
`Followed redirect without Authorization header!`
@ -123,6 +150,9 @@ fruit: lemon
HTTP 200
[Asserts]
header "Location" not exists
redirects count == 1
redirects nth 0 location == "http://localhost:8000/followed-redirect-basic-auth"
url == "http://localhost:8000/followed-redirect-basic-auth"
`Followed redirect with Authorization header!`
@ -136,6 +166,9 @@ bob@email.com: secret
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://127.0.0.1:8000/followed-redirect-basic-auth"
url == "http://127.0.0.1:8000/followed-redirect-basic-auth"
header "Location" not exists
`Followed redirect without Authorization header!`
@ -149,6 +182,9 @@ bob@email.com: secret
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://localhost:8000/followed-redirect-basic-auth"
url == "http://localhost:8000/followed-redirect-basic-auth"
header "Location" not exists
`Followed redirect with Authorization header!`
@ -162,5 +198,8 @@ location-trusted: true
fruit: lemon
HTTP 200
[Asserts]
redirects count == 1
redirects nth 0 location == "http://127.0.0.1:8000/followed-redirect-basic-auth-trusted"
url == "http://127.0.0.1:8000/followed-redirect-basic-auth-trusted"
header "Location" not exists
`Followed redirect Basic Auth!`

View File

@ -2,6 +2,7 @@ GET http://localhost:8000/redirected
HTTP 200
[Asserts]
url == "http://localhost:8000/redirected"
redirects count == 0
`Redirected`
@ -12,6 +13,9 @@ HTTP 302
Location: http://localhost:8000/redirected
[Asserts]
url == "http://localhost:8000/redirect-absolute"
# `redirects` query extracts data only if Hurl has run redirection (using --location or --location-trusted)
# If this case we have a 302, but we've nots asked Hurl to follow redirection so `redirects` query is empty.
redirects count == 0
GET http://localhost:8000/redirect-absolute
@ -20,6 +24,23 @@ location: true
HTTP 200
[Asserts]
url == "http://localhost:8000/redirected"
redirects count == 1
redirects nth 0 location == "http://localhost:8000/redirected"
`Redirected`
# Redirection can redirect body from requests to requests (provided the method doesn't
# change) but query strings params are NEVER forwarded
GET http://localhost:8000/redirect-absolute
[Options]
location: true
[Query]
foo: bar
HTTP 200
[Asserts]
url == "http://localhost:8000/redirected"
redirects count == 1
redirects nth 0 location == "http://localhost:8000/redirected"
`Redirected`
@ -30,6 +51,7 @@ HTTP 302
Location: /redirected
[Asserts]
url == "http://localhost:8000/redirect-relative"
redirects count == 0
GET http://localhost:8000/redirect-relative
@ -38,4 +60,6 @@ location: true
HTTP 200
[Asserts]
url == "http://localhost:8000/redirected"
redirects count == 1
redirects nth 0 location == "http://localhost:8000/redirected"
`Redirected`

View File

@ -33,14 +33,15 @@ use crate::http::certificate::Certificate;
use crate::http::curl_cmd::CurlCmd;
use crate::http::debug::log_body;
use crate::http::header::{
HeaderVec, ACCEPT_ENCODING, AUTHORIZATION, CONTENT_TYPE, EXPECT, LOCATION, USER_AGENT,
HeaderVec, ACCEPT_ENCODING, AUTHORIZATION, CONTENT_TYPE, EXPECT, LOCATION, SET_COOKIE,
USER_AGENT,
};
use crate::http::ip::IpAddr;
use crate::http::options::ClientOptions;
use crate::http::timings::Timings;
use crate::http::url::Url;
use crate::http::{
easy_ext, Call, Cookie, FileParam, Header, HttpError, HttpVersion, IpResolve, Method,
easy_ext, Body, Call, Cookie, FileParam, Header, HttpError, HttpVersion, IpResolve, Method,
MultipartParam, Param, Request, RequestCookie, RequestSpec, RequestedHttpVersion, Response,
Verbosity,
};
@ -120,11 +121,17 @@ impl Client {
let redirect_method = redirect_method(status, &request_spec.method);
let mut headers = request_spec.headers;
// When following redirection, we filter `AUTHORIZATION` header unless explicitly told
// to trust the redirected host with `--location-trusted`.
// When following redirection, we filter `Authorization` and `Set-Cookie` headers if the
// hostname changes unless the user explicitly trusts the redirected host with `--location-trusted`.
// <https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html>:
//
// > By default, libcurl only sends Authentication: or explicitly set Cookie: headers
// > to the initial host given in the original URL, to avoid leaking username + password
// > to other sites.
let host_changed = request_url.host() != redirect_url.host();
if host_changed && !options.follow_location_trusted {
headers.retain(|h| !h.name_eq(AUTHORIZATION));
headers.retain(|h| !h.name_eq(SET_COOKIE));
options.user = None;
}
@ -134,21 +141,21 @@ impl Client {
// > When libcurl switches method to GET, it then uses that method without sending any
// > request body. If it does not change the method, it sends the subsequent request the
// > same way as the previous one; including the request body if one was provided.
if redirect_method != request_spec.method {
request_spec = RequestSpec {
method: redirect_method,
url: redirect_url,
headers,
..Default::default()
};
let (form, multipart, body) = if redirect_method != request_spec.method {
(vec![], vec![], Body::Binary(vec![]))
} else {
request_spec = RequestSpec {
method: redirect_method,
url: redirect_url,
headers,
..request_spec
};
}
(request_spec.form, request_spec.multipart, request_spec.body)
};
request_spec = RequestSpec {
method: redirect_method,
url: redirect_url,
headers,
form,
multipart,
body,
cookies: request_spec.cookies,
..Default::default()
};
}
Ok(calls)
}