| 
									
										
										
										
											2020-10-10 14:16:11 +02:00
										 |  |  | # typed: false | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Homebrew | 
					
						
							|  |  |  |   module Livecheck | 
					
						
							|  |  |  |     # The `Livecheck::Strategy` module contains the various strategies as well | 
					
						
							|  |  |  |     # as some general-purpose methods for working with them. Within the context | 
					
						
							|  |  |  |     # of the `brew livecheck` command, strategies are established procedures | 
					
						
							|  |  |  |     # for finding new software versions at a given source. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # @api private | 
					
						
							|  |  |  |     module Strategy | 
					
						
							| 
									
										
										
										
											2020-12-24 03:33:14 +01:00
										 |  |  |       extend T::Sig | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       module_function | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Strategy priorities informally range from 1 to 10, where 10 is the | 
					
						
							|  |  |  |       # highest priority. 5 is the default priority because it's roughly in | 
					
						
							|  |  |  |       # the middle of this range. Strategies with a priority of 0 (or lower) | 
					
						
							|  |  |  |       # are ignored. | 
					
						
							|  |  |  |       DEFAULT_PRIORITY = 5
 | 
					
						
							|  |  |  |       private_constant :DEFAULT_PRIORITY | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Creates and/or returns a `@strategies` `Hash`, which maps a snake | 
					
						
							|  |  |  |       # case strategy name symbol (e.g. `:page_match`) to the associated | 
					
						
							|  |  |  |       # {Strategy}. | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       # | 
					
						
							|  |  |  |       # At present, this should only be called after tap strategies have been | 
					
						
							|  |  |  |       # loaded, otherwise livecheck won't be able to use them. | 
					
						
							|  |  |  |       # @return [Hash] | 
					
						
							|  |  |  |       def strategies | 
					
						
							|  |  |  |         return @strategies if defined? @strategies | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         @strategies = {} | 
					
						
							|  |  |  |         constants.sort.each do |strategy_symbol| | 
					
						
							|  |  |  |           key = strategy_symbol.to_s.underscore.to_sym | 
					
						
							|  |  |  |           strategy = const_get(strategy_symbol) | 
					
						
							|  |  |  |           @strategies[key] = strategy | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |         @strategies | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |       private_class_method :strategies | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # Returns the {Strategy} that corresponds to the provided `Symbol` (or | 
					
						
							|  |  |  |       # `nil` if there is no matching {Strategy}). | 
					
						
							|  |  |  |       # | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       # @param symbol [Symbol] the strategy name in snake case as a `Symbol` | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       #   (e.g. `:page_match`) | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       # @return [Strategy, nil] | 
					
						
							|  |  |  |       def from_symbol(symbol) | 
					
						
							|  |  |  |         strategies[symbol] | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Returns an array of strategies that apply to the provided URL. | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |       # | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       # @param url [String] the URL to check for matching strategies | 
					
						
							| 
									
										
										
										
											2020-12-05 11:49:47 -05:00
										 |  |  |       # @param livecheck_strategy [Symbol] a {Strategy} symbol from the | 
					
						
							|  |  |  |       #   `livecheck` block | 
					
						
							|  |  |  |       # @param regex_provided [Boolean] whether a regex is provided in the | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |       #   `livecheck` block | 
					
						
							|  |  |  |       # @return [Array] | 
					
						
							| 
									
										
										
										
											2020-12-14 02:09:23 +01:00
										 |  |  |       def from_url(url, livecheck_strategy: nil, url_provided: nil, regex_provided: nil, block_provided: nil) | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |         usable_strategies = strategies.values.select do |strategy| | 
					
						
							| 
									
										
										
										
											2020-12-05 11:49:47 -05:00
										 |  |  |           if strategy == PageMatch | 
					
						
							|  |  |  |             # Only treat the `PageMatch` strategy as usable if a regex is | 
					
						
							|  |  |  |             # present in the `livecheck` block | 
					
						
							| 
									
										
										
										
											2021-01-07 13:49:05 -08:00
										 |  |  |             next if !regex_provided && !block_provided | 
					
						
							| 
									
										
										
										
											2020-12-05 11:49:47 -05:00
										 |  |  |           elsif strategy.const_defined?(:PRIORITY) && | 
					
						
							|  |  |  |                 !strategy::PRIORITY.positive? && | 
					
						
							|  |  |  |                 from_symbol(livecheck_strategy) != strategy | 
					
						
							|  |  |  |             # Ignore strategies with a priority of 0 or lower, unless the | 
					
						
							|  |  |  |             # strategy is specified in the `livecheck` block | 
					
						
							|  |  |  |             next | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | 
 | 
					
						
							|  |  |  |           strategy.respond_to?(:match?) && strategy.match?(url) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Sort usable strategies in descending order by priority, using the | 
					
						
							|  |  |  |         # DEFAULT_PRIORITY when a strategy doesn't contain a PRIORITY constant | 
					
						
							|  |  |  |         usable_strategies.sort_by do |strategy| | 
					
						
							|  |  |  |           (strategy.const_defined?(:PRIORITY) ? -strategy::PRIORITY : -DEFAULT_PRIORITY) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-12-12 21:56:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |       def self.page_headers(url) | 
					
						
							| 
									
										
										
										
											2020-12-19 00:46:18 -05:00
										 |  |  |         headers = [] | 
					
						
							| 
									
										
										
										
											2020-12-13 12:21:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 02:10:38 +01:00
										 |  |  |         [:default, :browser].each do |user_agent| | 
					
						
							| 
									
										
										
										
											2020-12-19 18:17:18 -05:00
										 |  |  |           args = [ | 
					
						
							|  |  |  |             "--head",                 # Only work with the response headers | 
					
						
							|  |  |  |             "--request", "GET",       # Use a GET request (instead of HEAD) | 
					
						
							|  |  |  |             "--silent",               # Silent mode | 
					
						
							|  |  |  |             "--location",             # Follow redirects | 
					
						
							|  |  |  |             "--connect-timeout", "5", # Max time allowed for connection (secs) | 
					
						
							|  |  |  |             "--max-time", "10"        # Max time allowed for transfer (secs) | 
					
						
							|  |  |  |           ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-20 02:23:30 +01:00
										 |  |  |           stdout, _, status = curl_with_workarounds( | 
					
						
							| 
									
										
										
										
											2020-12-19 18:17:18 -05:00
										 |  |  |             *args, url, | 
					
						
							|  |  |  |             print_stdout: false, print_stderr: false, | 
					
						
							|  |  |  |             debug: false, verbose: false, | 
					
						
							|  |  |  |             user_agent: user_agent, retry: false | 
					
						
							| 
									
										
										
										
											2020-12-14 02:10:38 +01:00
										 |  |  |           ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           while stdout.match?(/\AHTTP.*\r$/) | 
					
						
							|  |  |  |             h, stdout = stdout.split("\r\n\r\n", 2) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-19 00:46:18 -05:00
										 |  |  |             headers << h.split("\r\n").drop(1) | 
					
						
							|  |  |  |                         .map { |header| header.split(/:\s*/, 2) } | 
					
						
							|  |  |  |                         .to_h.transform_keys(&:downcase) | 
					
						
							| 
									
										
										
										
											2020-12-14 02:10:38 +01:00
										 |  |  |           end | 
					
						
							| 
									
										
										
										
											2020-12-13 12:21:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-20 12:35:04 -05:00
										 |  |  |           return headers if status.success? | 
					
						
							| 
									
										
										
										
											2020-12-13 12:21:59 +01:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-14 02:10:38 +01:00
										 |  |  |         headers | 
					
						
							| 
									
										
										
										
											2020-12-12 21:56:07 +01:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-22 22:46:52 -05:00
										 |  |  |       # Fetches the content at the URL and returns a hash containing the | 
					
						
							|  |  |  |       # content and, if there are any redirections, the final URL. | 
					
						
							|  |  |  |       # | 
					
						
							|  |  |  |       # @param url [String] the URL of the content to check | 
					
						
							|  |  |  |       # @return [Hash] | 
					
						
							| 
									
										
										
										
											2020-12-24 03:33:14 +01:00
										 |  |  |       sig { params(url: String).returns(T::Hash[Symbol, T.untyped]) } | 
					
						
							| 
									
										
										
										
											2020-12-19 00:21:29 -05:00
										 |  |  |       def self.page_content(url) | 
					
						
							| 
									
										
										
										
											2020-12-22 22:46:52 -05:00
										 |  |  |         original_url = url | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Manually handling `URI#open` redirections allows us to detect the | 
					
						
							|  |  |  |         # resolved URL while also supporting HTTPS to HTTP redirections (which | 
					
						
							|  |  |  |         # are normally forbidden by `OpenURI`). | 
					
						
							|  |  |  |         begin | 
					
						
							|  |  |  |           content = URI.parse(url).open(redirect: false, &:read) | 
					
						
							|  |  |  |         rescue OpenURI::HTTPRedirect => e | 
					
						
							|  |  |  |           url = e.uri.to_s | 
					
						
							|  |  |  |           retry | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         data = { content: content } | 
					
						
							|  |  |  |         data[:final_url] = url unless url == original_url | 
					
						
							|  |  |  |         data | 
					
						
							| 
									
										
										
										
											2020-12-12 21:56:07 +01:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require_relative "strategy/apache" | 
					
						
							|  |  |  | require_relative "strategy/bitbucket" | 
					
						
							| 
									
										
										
										
											2020-12-11 04:31:14 +01:00
										 |  |  | require_relative "strategy/cpan" | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | require_relative "strategy/git" | 
					
						
							| 
									
										
										
										
											2020-12-02 18:04:22 +05:30
										 |  |  | require_relative "strategy/github_latest" | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | require_relative "strategy/gnome" | 
					
						
							|  |  |  | require_relative "strategy/gnu" | 
					
						
							|  |  |  | require_relative "strategy/hackage" | 
					
						
							| 
									
										
										
										
											2020-12-14 02:49:32 +01:00
										 |  |  | require_relative "strategy/header_match" | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | require_relative "strategy/launchpad" | 
					
						
							|  |  |  | require_relative "strategy/npm" | 
					
						
							|  |  |  | require_relative "strategy/page_match" | 
					
						
							|  |  |  | require_relative "strategy/pypi" | 
					
						
							|  |  |  | require_relative "strategy/sourceforge" | 
					
						
							| 
									
										
										
										
											2020-12-12 21:59:04 +01:00
										 |  |  | require_relative "strategy/sparkle" | 
					
						
							| 
									
										
										
										
											2020-08-08 07:16:06 +05:30
										 |  |  | require_relative "strategy/xorg" |