class Fetchers::Url

def self.resolve(target, opts = {})

def self.resolve(target, opts = {})
  if target.is_a?(Hash) && target.key?(:url)
    resolve_from_string(target[:url], opts)
  elsif target.is_a?(String)
    resolve_from_string(target, opts)
  end
end

def self.resolve_from_string(target, opts)

def self.resolve_from_string(target, opts)
  uri = URI.parse(target)
  return nil if uri.nil? or uri.scheme.nil?
  return nil unless %{ http https }.include? uri.scheme
  target = transform(target)
  new(target, opts)
rescue URI::Error
  nil
end

def self.transform(target)

def self.transform(target)
  transformed_target = if m = GITHUB_URL_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
                         "https://github.com/#{m[:user]}/#{m[:repo]}/archive/master.tar.gz"
                       elsif m = GITHUB_URL_WITH_TREE_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
                         "https://github.com/#{m[:user]}/#{m[:repo]}/archive/#{m[:commit]}.tar.gz"
                       elsif m = BITBUCKET_URL_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
                         "https://bitbucket.org/#{m[:user]}/#{m[:repo]}/get/master.tar.gz"
                       elsif m = BITBUCKET_URL_BRANCH_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
                         "https://bitbucket.org/#{m[:user]}/#{m[:repo]}/get/#{m[:branch]}.tar.gz"
                       elsif m = BITBUCKET_URL_COMMIT_REGEX.match(target) # rubocop:disable Lint/AssignmentInCondition
                         "https://bitbucket.org/#{m[:user]}/#{m[:repo]}/get/#{m[:commit]}.tar.gz"
                       end
  if transformed_target
    Inspec::Log.warn("URL target #{target} transformed to #{transformed_target}. Consider using the git fetcher")
    transformed_target
  else
    target
  end
end

def cache_key

def cache_key
  @archive_shasum ||= sha256
end

def check_for_missing_values!(opts)

def check_for_missing_values!(opts)
  keys_missing_values = opts.keys.delete_if do |k|
    if opts[k].nil?
      false
    elsif opts[k].respond_to?(:empty?) && opts[k].empty?
      false
    else
      true
    end
  end
  raise 'Unable to fetch profile - the following HTTP headers have no value: ' \
    "#{keys_missing_values.join(', ')}" unless keys_missing_values.empty?
end

def download_archive(path)

def download_archive(path)
  temp_archive_path
  final_path = "#{path}#{@archive_type}"
  FileUtils.mkdir_p(File.dirname(final_path))
  FileUtils.mv(temp_archive_path, final_path)
  Inspec::Log.debug("Fetched archive moved to: #{final_path}")
  @temp_archive_path = nil
  final_path
end

def download_archive_to_temp

Downloads archive to temporary file with side effect :( of setting @archive_type
def download_archive_to_temp
  return @temp_archive_path if !@temp_archive_path.nil?
  Inspec::Log.debug("Fetching URL: #{@target}")
  remote = open(@target, http_opts)
  @archive_type = file_type_from_remote(remote) # side effect :(
  archive = Tempfile.new(['inspec-dl-', @archive_type])
  archive.binmode
  archive.write(remote.read)
  archive.rewind
  archive.close
  Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
  @temp_archive_path = archive.path
end

def download_automate2_archive_to_temp

def download_automate2_archive_to_temp
  return @temp_archive_path if !@temp_archive_path.nil?
  Inspec::Log.debug("Fetching URL: #{@target}")
  json = {
    owner: @config['profile'][0],
    name: @config['profile'][1],
    version: @config['profile'][2],
  }.to_json
  uri = URI.parse(@target)
  opts = http_opts
  opts[:use_ssl] = uri.scheme == 'https'
  if @insecure
    opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
  else
    opts[:verify_mode] = OpenSSL::SSL::VERIFY_PEER
  end
  req = Net::HTTP::Post.new(uri)
  opts.each do |key, value|
    req.add_field(key, value)
  end
  req.body = json
  res = Net::HTTP.start(uri.host, uri.port, opts) { |http|
    http.request(req)
  }
  @archive_type = '.tar.gz'
  archive = Tempfile.new(['inspec-dl-', @archive_type])
  archive.binmode
  archive.write(res.body)
  archive.rewind
  archive.close
  Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
  @temp_archive_path = archive.path
end

def fetch(path)

def fetch(path)
  @archive_path ||= download_archive(path)
end

def file_type_from_remote(remote)

def file_type_from_remote(remote)
  content_type = remote.meta['content-type']
  file_type = MIME_TYPES[content_type]
  if file_type.nil?
    Inspec::Log.warn("Unrecognized content type: #{content_type}. Assuming tar.gz")
    file_type = '.tar.gz'
  end
  file_type
end

def http_opts

def http_opts
  opts = {}
  opts[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if @insecure
  if @config['server_type'] =~ /automate/
    opts['chef-delivery-enterprise'] = @config['automate']['ent']
    if @config['automate']['token_type'] == 'dctoken'
      opts['x-data-collector-token'] = @config['token']
    else
      opts['chef-delivery-user'] = @config['user']
      opts['chef-delivery-token'] = @config['token']
    end
  elsif @token
    opts['Authorization'] = "Bearer #{@token}"
  end
  # Do not send any headers that have nil values.
  # Net::HTTP does not gracefully handle this situation.
  check_for_missing_values!(opts)
  opts
end

def initialize(url, opts)

def initialize(url, opts)
  @target = url
  @insecure = opts['insecure']
  @token = opts['token']
  @config = opts
  @archive_path = nil
  @temp_archive_path = nil
end

def resolved_source

def resolved_source
  @resolved_source ||= { url: @target, sha256: sha256 }
end

def sha256

def sha256
  file = @archive_path || temp_archive_path
  OpenSSL::Digest::SHA256.digest(File.read(file)).unpack('H*')[0]
end

def temp_archive_path

def temp_archive_path
  @temp_archive_path ||= if @config['server_type'] == 'automate2'
                           download_automate2_archive_to_temp
                         else
                           download_archive_to_temp
                         end
end

def to_s

def to_s
  @target
end