From 9096a111d7e48c6c3dd98f1bf769a6d33d01c630 Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:28:03 -0500 Subject: [PATCH 1/2] curl_headers: Handle POST requests `Livecheck::Strategy.page_headers` uses `Utils::Curl.curl_headers` but the method only handles `HEAD` and `GET` requests. I recently added `POST` support to livecheck but forgot to update `curl_headers` in the process, so `livecheck` blocks using the `HeaderMatch` strategy along with `post_form` or `post_json` will fail because curl doesn't allow both `--head` and `--data`/`--json` arguments. This addresses the issue by updating `curl_headers` to handle `POST` requests and skip the `GET` retry logic. --- Library/Homebrew/utils/curl.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Library/Homebrew/utils/curl.rb b/Library/Homebrew/utils/curl.rb index fb51876e0e..0fda86f324 100644 --- a/Library/Homebrew/utils/curl.rb +++ b/Library/Homebrew/utils/curl.rb @@ -277,15 +277,20 @@ module Utils ).returns(T::Hash[Symbol, T.untyped]) } def curl_headers(*args, wanted_headers: [], **options) - get_retry_args = ["--request", "GET"] + base_args = ["--fail", "--location", "--silent"] + get_retry_args = [] + if (is_post_request = args.include?("POST")) + base_args << "--dump-header" << "-" + else + base_args << "--head" + get_retry_args << "--request" << "GET" + end + # This is a workaround for https://github.com/Homebrew/brew/issues/18213 get_retry_args << "--http1.1" if curl_version >= Version.new("8.7") && curl_version < Version.new("8.10") [[], get_retry_args].each do |request_args| - result = curl_output( - "--fail", "--location", "--silent", "--head", *request_args, *args, - **options - ) + result = curl_output(*base_args, *request_args, *args, **options) # We still receive usable headers with certain non-successful exit # statuses, so we special case them below. @@ -295,6 +300,7 @@ module Utils CURL_RECV_ERROR_EXIT_CODE, ].include?(result.exit_status) parsed_output = parse_curl_output(result.stdout) + return parsed_output if is_post_request if request_args.empty? # If we didn't get any wanted header yet, retry using `GET`. From 8ba1b4400a935036686918358a0da45a676133c0 Mon Sep 17 00:00:00 2001 From: Sam Ford <1584702+samford@users.noreply.github.com> Date: Fri, 7 Mar 2025 20:31:00 -0500 Subject: [PATCH 2/2] livecheck: Include Content-Length header for POST Some servers will return an error response if a `Content-Length` header isn't included in a `POST` request, so this adds it to the `post_args` array when `post_form` or `post_json` are used. --- Library/Homebrew/livecheck/strategy.rb | 8 +++++++- Library/Homebrew/test/livecheck/strategy_spec.rb | 12 +++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Library/Homebrew/livecheck/strategy.rb b/Library/Homebrew/livecheck/strategy.rb index 56d0ecb168..92b75552fb 100644 --- a/Library/Homebrew/livecheck/strategy.rb +++ b/Library/Homebrew/livecheck/strategy.rb @@ -178,7 +178,7 @@ module Homebrew ).returns(T::Array[String]) } def self.post_args(post_form: nil, post_json: nil) - if post_form.present? + args = if post_form.present? require "uri" ["--data", URI.encode_www_form(post_form)] elsif post_json.present? @@ -187,6 +187,12 @@ module Homebrew else [] end + + if (content_length = args[1]&.length) + args << "--header" << "Content-Length: #{content_length}" + end + + args end # Collects HTTP response headers, starting with the provided URL. diff --git a/Library/Homebrew/test/livecheck/strategy_spec.rb b/Library/Homebrew/test/livecheck/strategy_spec.rb index 4ef416c331..396fb5ccb0 100644 --- a/Library/Homebrew/test/livecheck/strategy_spec.rb +++ b/Library/Homebrew/test/livecheck/strategy_spec.rb @@ -144,16 +144,22 @@ RSpec.describe Homebrew::Livecheck::Strategy do end describe "::post_args" do + let(:form_string_content_length) { "Content-Length: #{form_string.length}" } + let(:json_string_content_length) { "Content-Length: #{json_string.length}" } + it "returns an array including `--data` and an encoded form data string" do - expect(strategy.post_args(post_form: post_hash)).to eq(["--data", form_string]) + expect(strategy.post_args(post_form: post_hash)) + .to eq(["--data", form_string, "--header", form_string_content_length]) # If both `post_form` and `post_json` are present, only `post_form` will # be used. - expect(strategy.post_args(post_form: post_hash, post_json: post_hash)).to eq(["--data", form_string]) + expect(strategy.post_args(post_form: post_hash, post_json: post_hash)) + .to eq(["--data", form_string, "--header", form_string_content_length]) end it "returns an array including `--json` and a JSON string" do - expect(strategy.post_args(post_json: post_hash)).to eq(["--json", json_string]) + expect(strategy.post_args(post_json: post_hash)) + .to eq(["--json", json_string, "--header", json_string_content_length]) end it "returns an empty array if `post_form` value is blank" do