diff --git a/Library/Homebrew/livecheck/strategy.rb b/Library/Homebrew/livecheck/strategy.rb index 3f98a9faf4..baa2eea270 100644 --- a/Library/Homebrew/livecheck/strategy.rb +++ b/Library/Homebrew/livecheck/strategy.rb @@ -35,13 +35,24 @@ module Homebrew # `Utils::Curl` method calls in {Strategy}. CURL_PROCESS_TIMEOUT = CURL_MAX_TIME + 5 + # The maximum number of redirections that `curl` should allow. + MAX_REDIRECTIONS = 5 + + # This value is passed to `#parse_curl_output` to ensure that the limit + # for the number of responses it will parse corresponds to the maximum + # number of responses in this context. The `+ 1` here accounts for the + # situation where there are exactly `MAX_REDIRECTIONS` number of + # redirections, followed by a final `200 OK` response. + MAX_PARSE_ITERATIONS = MAX_REDIRECTIONS + 1 + # Baseline `curl` arguments used in {Strategy} methods. DEFAULT_CURL_ARGS = [ # Follow redirections to handle mirrors, relocations, etc. "--location", + "--max-redirs", MAX_REDIRECTIONS.to_s, # Avoid progress bar text, so we can reliably identify `curl` error # messages in output - "--silent", + "--silent" ].freeze # `curl` arguments used in `Strategy#page_headers` method. @@ -183,7 +194,7 @@ module Homebrew ) next unless status.success? - parsed_output = parse_curl_output(output) + parsed_output = parse_curl_output(output, max_iterations: MAX_PARSE_ITERATIONS) parsed_output[:responses].each { |response| headers << response[:headers] } break if headers.present? end @@ -217,7 +228,7 @@ module Homebrew # Separate the head(s)/body and identify the final URL (after any # redirections) - parsed_output = parse_curl_output(output) + parsed_output = parse_curl_output(output, max_iterations: MAX_PARSE_ITERATIONS) final_url = curl_response_last_location(parsed_output[:responses], absolutize: true, base_url: url) data = { content: parsed_output[:body] } diff --git a/Library/Homebrew/utils/curl.rb b/Library/Homebrew/utils/curl.rb index f1dd65e450..d9687d670a 100644 --- a/Library/Homebrew/utils/curl.rb +++ b/Library/Homebrew/utils/curl.rb @@ -393,13 +393,17 @@ module Utils # `:status_code`, `:status_text`, and `:headers`. # @param output [String] The output text from `curl` containing HTTP # responses, body content, or both. + # @param max_iterations [Integer] The maximum number of iterations for the + # `while` loop that parses HTTP response text. This should correspond to + # the maximum number of requests in the output. If `curl`'s `--max-redirs` + # option is used, `max_iterations` should be `max-redirs + 1`, to + # account for any final response after the redirections. # @return [Hash] A hash containing an array of response hashes and the body # content, if found. - sig { params(output: String).returns(T::Hash[Symbol, T.untyped]) } - def parse_curl_output(output) + sig { params(output: String, max_iterations: Integer).returns(T::Hash[Symbol, T.untyped]) } + def parse_curl_output(output, max_iterations: 5) responses = [] - max_iterations = 5 iterations = 0 output = output.lstrip while output.match?(%r{\AHTTP/[\d.]+ \d+}) && output.include?(HTTP_RESPONSE_BODY_SEPARATOR)