diff options
Diffstat (limited to 'Library/Homebrew/utils/curl.rb')
| -rw-r--r-- | Library/Homebrew/utils/curl.rb | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/Library/Homebrew/utils/curl.rb b/Library/Homebrew/utils/curl.rb index eaa81352c..84853047c 100644 --- a/Library/Homebrew/utils/curl.rb +++ b/Library/Homebrew/utils/curl.rb @@ -59,3 +59,95 @@ end def curl_output(*args, **options) Open3.capture3(*curl_args(*args, show_output: true, **options)) end + +def curl_check_http_content(url, user_agents: [:default], check_content: false, strict: false, require_http: false) + return unless url.start_with? "http" + + details = nil + user_agent = nil + hash_needed = url.start_with?("http:") && !require_http + user_agents.each do |ua| + details = curl_http_content_headers_and_checksum(url, hash_needed: hash_needed, user_agent: ua) + user_agent = ua + break if details[:status].to_s.start_with?("2") + end + + unless details[:status] + # Hack around https://github.com/Homebrew/brew/issues/3199 + return if MacOS.version == :el_capitan + return "The URL #{url} is not reachable" + end + + unless details[:status].start_with? "2" + return "The URL #{url} is not reachable (HTTP status code #{details[:status]})" + end + + return unless hash_needed + + secure_url = url.sub "http", "https" + secure_details = + curl_http_content_headers_and_checksum(secure_url, hash_needed: true, user_agent: user_agent) + + if !details[:status].to_s.start_with?("2") || + !secure_details[:status].to_s.start_with?("2") + return + end + + etag_match = details[:etag] && + details[:etag] == secure_details[:etag] + content_length_match = + details[:content_length] && + details[:content_length] == secure_details[:content_length] + file_match = details[:file_hash] == secure_details[:file_hash] + + if etag_match || content_length_match || file_match + return "The URL #{url} should use HTTPS rather than HTTP" + end + + return unless check_content + + no_protocol_file_contents = %r{https?:\\?/\\?/} + details[:file] = details[:file].gsub(no_protocol_file_contents, "/") + secure_details[:file] = secure_details[:file].gsub(no_protocol_file_contents, "/") + + # Check for the same content after removing all protocols + if details[:file] == secure_details[:file] + return "The URL #{url} should use HTTPS rather than HTTP" + end + + return unless strict + + # Same size, different content after normalization + # (typical causes: Generated ID, Timestamp, Unix time) + if details[:file].length == secure_details[:file].length + return "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser." + end + + lenratio = (100 * secure_details[:file].length / details[:file].length).to_i + return unless (90..110).cover?(lenratio) + "The URL #{url} may be able to use HTTPS rather than HTTP. Please verify it in a browser." +end + +def curl_http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default) + max_time = hash_needed ? "600" : "25" + output, = curl_output( + "--connect-timeout", "15", "--include", "--max-time", max_time, "--location", url, + user_agent: user_agent + ) + + status_code = :unknown + while status_code == :unknown || status_code.to_s.start_with?("3") + headers, _, output = output.partition("\r\n\r\n") + status_code = headers[%r{HTTP\/.* (\d+)}, 1] + end + + output_hash = Digest::SHA256.digest(output) if hash_needed + + { + status: status_code, + etag: headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2], + content_length: headers[/Content-Length: (\d+)/, 1], + file_hash: output_hash, + file: output, + } +end |
