Class: Vagrant::Util::Downloader

Inherits:
Object
  • Object
show all
Defined in:
lib/vagrant/util/downloader.rb

Overview

This class downloads files using various protocols by subprocessing to cURL. cURL is a much more capable and complete download tool than a hand-rolled Ruby library, so we defer to its expertise.

Constant Summary collapse

USER_AGENT =

Custom user agent provided to cURL so that requests to URL shorteners are properly tracked.

Vagrant/1.7.4 (+https://www.vagrantup.com; ruby2.1.0)
"Vagrant/#{VERSION} (+https://www.vagrantup.com; #{RUBY_ENGINE}#{RUBY_VERSION}) #{ENV['VAGRANT_USER_AGENT_PROVISIONAL_STRING']}".strip.freeze
SILENCED_HOSTS =

Hosts that do not require notification on redirect

[
  "vagrantcloud.com".freeze,
  "vagrantup.com".freeze
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, destination, options = nil) ⇒ Downloader

Returns a new instance of Downloader.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/vagrant/util/downloader.rb', line 39

def initialize(source, destination, options=nil)
  options     ||= {}

  @logger      = Log4r::Logger.new("vagrant::util::downloader")
  @source      = source.to_s
  @destination = destination.to_s

  begin
    url = URI.parse(@source)
    if url.scheme && url.scheme.start_with?("http") && url.user
      auth = "#{CGI.unescape(url.user)}"
      auth += ":#{CGI.unescape(url.password)}" if url.password
      url.user = nil
      url.password = nil
      options[:auth] ||= auth
      @source = url.to_s
    end
  rescue URI::InvalidURIError
    # Ignore, since its clearly not HTTP
  end

  # Get the various optional values
  @auth        = options[:auth]
  @ca_cert     = options[:ca_cert]
  @ca_path     = options[:ca_path]
  @continue    = options[:continue]
  @headers     = Array(options[:headers])
  @insecure    = options[:insecure]
  @ui          = options[:ui]
  @client_cert = options[:client_cert]
  @location_trusted = options[:location_trusted]
  @checksums   = {
    :md5 => options[:md5],
    :sha1 => options[:sha1],
    :sha256 => options[:sha256],
    :sha384 => options[:sha384],
    :sha512 => options[:sha512]
  }.compact
  @extra_download_options = options[:box_extra_download_options] || []
  # If on Windows SSL revocation checks should be best effort. More context
  # for this usage can be found in the following links:
  #
  # https://github.com/curl/curl/issues/3727
  # https://github.com/curl/curl/pull/4981
  if Platform.windows?
    @ssl_revoke_best_effort = !options[:disable_ssl_revoke_best_effort]
  end
end

Instance Attribute Details

#destinationObject (readonly)

Returns the value of attribute destination.



36
37
38
# File 'lib/vagrant/util/downloader.rb', line 36

def destination
  @destination
end

#headersObject

Returns the value of attribute headers.



37
38
39
# File 'lib/vagrant/util/downloader.rb', line 37

def headers
  @headers
end

#sourceObject

Returns the value of attribute source.



35
36
37
# File 'lib/vagrant/util/downloader.rb', line 35

def source
  @source
end

Instance Method Details

#download!Object

This executes the actual download, downloading the source file to the destination with the given options used to initialize this class.

If this method returns without an exception, the download succeeded. An exception will be raised if the download failed.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/vagrant/util/downloader.rb', line 94

def download!
  # This variable can contain the proc that'll be sent to
  # the subprocess execute.
  data_proc = nil

  extra_subprocess_opts = {}
  if @ui
    # If we're outputting progress, then setup the subprocess to
    # tell us output so we can parse it out.
    extra_subprocess_opts[:notify] = :stderr

    data_proc = Vagrant::Util::CurlHelper.capture_output_proc(@logger, @ui, @source)
  end

  @logger.info("Downloader starting download: ")
  @logger.info("  -- Source: #{@source}")
  @logger.info("  -- Destination: #{@destination}")

  retried = false
  begin
    # Get the command line args and the subprocess opts based
    # on our downloader settings.
    options, subprocess_options = self.options
    options += ["--output", @destination]
    options << @source

    # Merge in any extra options we set
    subprocess_options.merge!(extra_subprocess_opts)

    # Go!
    execute_curl(options, subprocess_options, &data_proc)
  rescue Errors::DownloaderError => e
    # If we already retried, raise it.
    raise if retried

    @logger.error("Exit code: #{e.extra_data[:code]}")

    # If its any error other than 33, it is an error.
    raise if e.extra_data[:code].to_i != 33

    # Exit code 33 means that the server doesn't support ranges.
    # In this case, try again without resume.
    @logger.error("Error is server doesn't support byte ranges. Retrying from scratch.")
    @continue = false
    retried = true
    retry
  ensure
    # If we're outputting to the UI, clear the output to
    # avoid lingering progress meters.
    if @ui
      @ui.clear_line

      # Windows doesn't clear properly for some reason, so we just
      # output one more newline.
      @ui.detail("") if Platform.windows?
    end
  end

  validate_download!(@source, @destination, @checksums)

  # Everything succeeded
  true
end

#headObject

Does a HEAD request of the URL and returns the output.



159
160
161
162
163
164
165
166
167
# File 'lib/vagrant/util/downloader.rb', line 159

def head
  options, subprocess_options = self.options
  options.unshift("-I")
  options << @source

  @logger.info("HEAD: #{@source}")
  result = execute_curl(options, subprocess_options)
  result.stdout
end