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)