require 'digest/sha2' require 'io/console' require 'uri' require_relative 'const' require_relative 'color' require_relative 'progress_bar' require_relative 'require_gem' require_gem('activesupport', 'active_support/core_ext/object/blank') require_gem('ptools') begin require 'securerandom' # resolv-replace is no longer needed with ruby 3.4 require 'resolv-replace' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.4.0') require 'net/http' rescue RuntimeError => e # hide the error message and fallback to curl if securerandom raise an error if e.message == 'failed to get urandom' Object.send(:remove_const, :CREW_USE_CURL) CREW_USE_CURL = true else raise end end def downloader(url, sha256sum, filename = File.basename(url), verbose: false) # downloader: wrapper for all Chromebrew downloaders (`net/http`,`curl`...) # Usage: downloader , , , # # : URL that points to the target file # : SHA256 checksum, verify downloaded file with given checksum # : (Optional) Output path/filename # : (Optional) Verbose output # puts "downloader(#{url}, #{sha256sum}, #{filename}, #{verbose})" if verbose uri = URI(url) if CREW_USE_CURL || !ENV['CREW_DOWNLOADER'].to_s.empty? # force using external downloader if either CREW_USE_CURL or ENV['CREW_DOWNLOADER'] is set puts "external_downloader(#{uri}, #{filename}, #{verbose})" if verbose external_downloader(uri, filename, verbose: verbose) else case uri.scheme when 'http', 'https' # use net/http if the url protocol is http(s):// puts "http_downloader(#{uri}, #{filename}, #{verbose})" if verbose http_downloader(uri, filename, verbose: verbose) when 'file' # use FileUtils to copy if it is a local file (the url protocol is file://) if File.exist?(uri.path) return FileUtils.cp(uri.path, filename) else abort "#{uri.path}: File not found :/".lightred end else # use external downloader (curl by default) if the url protocol is not http(s):// or file:// puts "external_downloader(#{uri}, #{filename}, #{verbose})" if verbose external_downloader(uri, filename, verbose: verbose) end end # verify with given checksum calc_sha256sum = Digest::SHA256.hexdigest(File.read(filename)) unless (sha256sum =~ /^SKIP$/i) || (calc_sha256sum == sha256sum) if CREW_FORCE pkg_name = @pkg_name.blank? ? name : @pkg_name puts "Updating checksum for #{filename}".lightblue puts "from #{sha256sum} to #{calc_sha256sum}".lightblue puts "in #{CREW_LOCAL_REPO_ROOT}/packages/#{pkg_name}.rb .".lightblue system "sed -i 's/#{sha256sum}/#{calc_sha256sum}/' #{CREW_LOCAL_REPO_ROOT}/packages/#{pkg_name}.rb" else FileUtils.rm_f filename warn 'Checksum mismatch :/ Try again?'.lightred, <<~EOT #{''} Filename: #{filename.lightblue} Expected checksum (SHA256): #{sha256sum.green} Calculated checksum (SHA256): #{calc_sha256sum.red} EOT exit 2 end end rescue StandardError => e warn e.full_message # fallback to curl if error occurred puts "external_downloader(#{uri}, #{filename}, #{verbose})" if verbose external_downloader(uri, filename, verbose: verbose) end def get_http_response(uri) uri = URI(uri) if uri.is_a?(String) ssl_error_retry = 0 # open http connection Net::HTTP.start(uri.host, uri.port, { max_retries: CREW_DOWNLOADER_RETRY, use_ssl: uri.scheme.eql?('https'), min_version: :TLS1_2, ca_file: SSL_CERT_FILE, ca_path: SSL_CERT_DIR }) do |http| return http.get(uri) end rescue OpenSSL::SSL::SSLError # handle SSL errors ssl_error_retry += 1 ssl_error_retry <= 3 ? retry : raise end def http_downloader(uri, filename = File.basename(url), verbose: false) # http_downloader: Downloader based on net/http library ssl_error_retry = 0 # open http connection Net::HTTP.start(uri.host, uri.port, { max_retries: CREW_DOWNLOADER_RETRY, use_ssl: uri.scheme.eql?('https'), min_version: :TLS1_2, ca_file: SSL_CERT_FILE, ca_path: SSL_CERT_DIR }) do |http| http.request(Net::HTTP::Get.new(uri)) do |response| case when response.is_a?(Net::HTTPSuccess) # Response is successful, don't abort when response.is_a?(Net::HTTPRedirection) # follow HTTP redirection puts <<~EOT if verbose * Follow HTTP redirection: #{response['Location']} * EOT redirect_uri = URI(response['Location']) # add url scheme/host for redirected url based on original url if missing redirect_uri.scheme ||= uri.scheme redirect_uri.host ||= uri.host return send(__method__, redirect_uri, filename, verbose: verbose) else abort "Download of #{uri} failed with error #{response.code}: #{response.msg}".lightred end # get target file size (should be returned by the server) file_size = response['Content-Length'].to_f downloaded_size = 0.0 # initialize progress bar progress_bar = ChromebrewProgressBar.new(file_size) if verbose warn <<~EOT * Connected to #{uri.host} port #{uri.port} * HTTPS: #{uri.scheme.eql?('https')} * EOT # parse response's header to readable format response.to_hash.each_pair { |k, v| warn "> #{k}: #{v}" } warn "\n" end progress_bar_thread = progress_bar.show # print progress bar # read file chunks from server, write it to filesystem File.open(filename, 'wb') do |io| response.read_body do |chunk| downloaded_size += chunk.size # record downloaded size, used for showing progress bar progress_bar.set_downloaded_size(downloaded_size, invalid_size_error: false) if file_size.positive? io.write(chunk) # write to file end ensure # stop progress bar, wait for it to terminate progress_bar.progress_bar_showing = false progress_bar_thread.join end end end rescue OpenSSL::SSL::SSLError # handle SSL errors ssl_error_retry += 1 ssl_error_retry <= 3 ? retry : raise end def external_downloader(uri, filename = File.basename(url), verbose: false) # external_downloader: wrapper for external downloaders in CREW_DOWNLOADER (curl by default) # default curl cmdline, CREW_DOWNLOADER should be in this format also # %s: Will be substitute to "--verbose" if #{verbose} set to true, otherwise will be substitute to "" # %: Will be substitute to #{CREW_DOWNLOADER_RETRY} # %s: Will be substitute to #{url} # %s: Will be substitute to #{filename} # i686 curl throws a "SSL certificate problem: self signed certificate in certificate chain" error. # Only bypass this when we are using the system curl early in install. @default_curl = File.which('curl') curl_cmdline = ARCH == 'i686' && @default_curl == '/usr/bin/curl' ? 'curl %s -kL -# --retry %s %s -o %s' : 'curl %s -L -# --retry %s %s -o %s' # use CREW_DOWNLOADER if specified, use curl by default downloader_cmdline = CREW_DOWNLOADER || curl_cmdline return system( format(downloader_cmdline, { verbose: verbose ? '--verbose' : '', retry: CREW_DOWNLOADER_RETRY, url: uri.to_s, output: filename }), exception: true ) end