lib/down.rb in down-2.4.3 vs lib/down.rb in down-2.5.0
- old
+ new
@@ -3,33 +3,47 @@
require "open-uri"
require "net/http"
require "tempfile"
require "fileutils"
-require "cgi/util"
+require "cgi"
module Down
class Error < StandardError; end
class TooLarge < Error; end
class NotFound < Error; end
module_function
- def download(url, options = {})
+ def download(uri, options = {})
warn "Passing :timeout option to `Down.download` is deprecated and will be removed in Down 3. You should use open-uri's :open_timeout and/or :read_timeout." if options.key?(:timeout)
warn "Passing :progress option to `Down.download` is deprecated and will be removed in Down 3. You should use open-uri's :progress_proc." if options.key?(:progress)
max_size = options.delete(:max_size)
max_redirects = options.delete(:max_redirects) || 2
progress_proc = options.delete(:progress_proc) || options.delete(:progress)
content_length_proc = options.delete(:content_length_proc)
timeout = options.delete(:timeout)
+ if options[:proxy]
+ proxy = URI(options[:proxy])
+ user = proxy.user
+ password = proxy.password
+
+ if user || password
+ proxy.user = nil
+ proxy.password = nil
+
+ options[:proxy_http_basic_authentication] = [proxy.to_s, user, password]
+ options.delete(:proxy)
+ end
+ end
+
tries = max_redirects + 1
begin
- uri = URI.parse(url)
+ uri = URI(uri)
open_uri_options = {
"User-Agent" => "Down/#{VERSION}",
content_length_proc: proc { |size|
if size && max_size && size > max_size
@@ -55,16 +69,16 @@
open_uri_options.update(options)
downloaded_file = uri.open(open_uri_options)
rescue OpenURI::HTTPRedirect => redirect
- url = redirect.uri.to_s
+ uri = redirect.uri
retry if (tries -= 1) > 0
- raise Down::NotFound, "too many redirects: #{url}"
+ raise Down::NotFound, "too many redirects: #{uri.to_s}"
rescue => error
raise if error.is_a?(Down::Error)
- raise Down::NotFound, "file not found: #{url}"
+ raise Down::NotFound, "file not found: #{uri.to_s}"
end
# open-uri will return a StringIO instead of a Tempfile if the filesize is
# less than 10 KB, so if it happens we convert it back to Tempfile. We want
# to do this with a Tempfile as well, because open-uri doesn't preserve the
@@ -83,14 +97,21 @@
io = open(url, options)
io.each_chunk { |chunk| yield chunk, io.size }
io.close
end
- def open(url, options = {})
- uri = URI.parse(url)
- http = Net::HTTP.new(uri.host, uri.port)
+ def open(uri, options = {})
+ uri = URI(uri)
+ http_class = Net::HTTP
+ if options[:proxy]
+ proxy = URI.parse(options[:proxy])
+ http_class = Net::HTTP::Proxy(proxy.hostname, proxy.port, proxy.user, proxy.password)
+ end
+
+ http = http_class.new(uri.host, uri.port)
+
# taken from open-uri implementation
if uri.is_a?(URI::HTTPS)
require "net/https"
http.use_ssl = true
http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
@@ -107,42 +128,56 @@
request_headers = options.select { |key, value| key.is_a?(String) }
request = Fiber.new do
http.start do
- http.request_get(uri.request_uri, request_headers) do |response|
+ req = Net::HTTP::Get.new(uri.request_uri, request_headers)
+ req.basic_auth(uri.user, uri.password) if uri.user || uri.password
+ http.request(req) do |response|
Fiber.yield response
response.instance_variable_set("@read", true)
end
end
end
response = request.resume
+ raise Down::NotFound, "request to #{uri.to_s} returned status #{response.code} and body:\n#{response.body}" if response.code.to_i.between?(400, 599)
+
if response.chunked?
# Net::HTTP's implementation of reading "Transfer-Encoding: chunked"
# raises a Fiber error, so we work around it by downloading the whole
# response body without Enumerators (which internally use Fibers).
- warn "Response from #{url} returned as \"Transfer-Encoding: chunked\", which Down cannot partially download, so the whole response body will be downloaded instead."
+ warn "Response from #{uri.to_s} returned as \"Transfer-Encoding: chunked\", which Down cannot partially download, so the whole response body will be downloaded instead."
tempfile = Tempfile.new("down", binmode: true)
response.read_body { |chunk| tempfile << chunk }
tempfile.rewind
request.resume # close HTTP connection
- ChunkedIO.new(
+ chunked_io = ChunkedIO.new(
chunks: Enumerator.new { |y| y << tempfile.read(16*1024) until tempfile.eof? },
size: tempfile.size,
on_close: -> { tempfile.close! },
)
else
- ChunkedIO.new(
+ chunked_io = ChunkedIO.new(
chunks: response.enum_for(:read_body),
size: response["Content-Length"] && response["Content-Length"].to_i,
on_close: -> { request.resume }, # close HTTP connnection
)
end
+
+ chunked_io.data[:status] = response.code.to_i
+ chunked_io.data[:headers] = {}
+
+ response.each_header do |downcased_name, value|
+ name = downcased_name.split("-").map(&:capitalize).join("-")
+ chunked_io.data[:headers].merge!(name => value)
+ end
+
+ chunked_io
end
def copy_to_tempfile(basename, io)
tempfile = Tempfile.new(["down", File.extname(basename)], binmode: true)
if io.is_a?(OpenURI::Meta) && io.is_a?(Tempfile)