Curl: Fix following redirections when base changes

Update base URL when there is an absolute location, so that following
relative locations are considered relative to the new base.

Consider below cURL output for https://example_one.com:

    HTTP/1.1 302 Moved Temporarily
    Location: https://example_two.com

    HTTP/1.1 302 Moved Temporarily
    Location: /foo/

    HTTP/1.1 200 OK

The final URL should be https://example_two.com/foo/ rather than
https://example_one.com/foo/.
This commit is contained in:
Frederick Zhang 2022-11-25 12:09:59 +11:00
parent 747829334b
commit c27eed4606
No known key found for this signature in database
GPG Key ID: 980A192C361BE1AE
3 changed files with 79 additions and 1 deletions

View File

@ -471,7 +471,7 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
lines = output.to_s.lines.map(&:chomp) lines = output.to_s.lines.map(&:chomp)
final_url = curl_response_last_location(parsed_output[:responses], absolutize: true, base_url: url) final_url = curl_response_follow_redirections(parsed_output[:responses], url)
final_url ||= url final_url ||= url
content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new

View File

@ -556,4 +556,58 @@ describe "Utils::Curl" do
expect(curl_response_last_location([response_hash[:ok]])).to be_nil expect(curl_response_last_location([response_hash[:ok]])).to be_nil
end end
end end
describe "#curl_response_follow_redirections" do
it "returns the original URL when there are no location headers" do
expect(
curl_response_follow_redirections(
[response_hash[:ok]],
"https://brew.sh/test1/test2",
),
).to eq("https://brew.sh/test1/test2")
end
it "returns the URL relative to base when locations are relative" do
expect(
curl_response_follow_redirections(
[response_hash[:redirection_root_relative], response_hash[:ok]],
"https://brew.sh/test1/test2",
),
).to eq("https://brew.sh/example/")
expect(
curl_response_follow_redirections(
[response_hash[:redirection_parent_relative], response_hash[:ok]],
"https://brew.sh/test1/test2",
),
).to eq("https://brew.sh/test1/example/")
expect(
curl_response_follow_redirections(
[
response_hash[:redirection_parent_relative],
response_hash[:redirection_parent_relative],
response_hash[:ok],
],
"https://brew.sh/test1/test2",
),
).to eq("https://brew.sh/test1/example/example/")
end
it "returns new base when there are absolute location(s)" do
expect(
curl_response_follow_redirections(
[response_hash[:redirection], response_hash[:ok]],
"https://brew.sh/test1/test2",
),
).to eq(location_urls[0])
expect(
curl_response_follow_redirections(
[response_hash[:redirection], response_hash[:redirection_parent_relative], response_hash[:ok]],
"https://brew.sh/test1/test2",
),
).to eq("#{location_urls[0]}example/")
end
end
end end

View File

@ -506,6 +506,30 @@ module Utils
nil nil
end end
# Returns the final URL by following location headers in cURL responses.
# @param responses [Array<Hash>] An array of hashes containing response
# status information and headers from `#parse_curl_response`.
# @param base_url [String] The URL to use as a base.
# @return [String] The final absolute URL after redirections.
sig {
params(
responses: T::Array[T::Hash[Symbol, T.untyped]],
base_url: String,
).returns(String)
}
def curl_response_follow_redirections(responses, base_url)
responses.each do |response|
next if response[:headers].blank?
location = response[:headers]["location"]
next if location.blank?
base_url = URI.join(base_url, location).to_s
end
base_url
end
private private
# Parses HTTP response text from `curl` output into a hash containing the # Parses HTTP response text from `curl` output into a hash containing the