lib/down.rb in down-2.5.1 vs lib/down.rb in down-3.0.0
- old
+ new
@@ -1,216 +1,4 @@
-require "down/version"
-require "down/chunked_io"
+# frozen-string-literal: true
-require "open-uri"
-require "net/http"
-require "tempfile"
-require "fileutils"
-require "cgi"
-
-module Down
- class Error < StandardError; end
- class TooLarge < Error; end
- class NotFound < Error; end
-
- module_function
-
- 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(uri)
-
- open_uri_options = {
- "User-Agent" => "Down/#{VERSION}",
- content_length_proc: proc { |size|
- if size && max_size && size > max_size
- raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
- end
- content_length_proc.call(size) if content_length_proc
- },
- progress_proc: proc { |current_size|
- if max_size && current_size > max_size
- raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
- end
- progress_proc.call(current_size) if progress_proc
- },
- read_timeout: timeout,
- redirect: false,
- }
-
- if uri.user || uri.password
- open_uri_options[:http_basic_authentication] = [uri.user, uri.password]
- uri.user = nil
- uri.password = nil
- end
-
- open_uri_options.update(options)
-
- downloaded_file = uri.open(open_uri_options)
- rescue OpenURI::HTTPRedirect => redirect
- uri = redirect.uri
- retry if (tries -= 1) > 0
- raise Down::NotFound, "too many redirects"
- rescue => error
- raise if error.is_a?(Down::Error)
- raise Down::NotFound, "file not found"
- 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
- # file extension, so we want to run it against #copy_to_tempfile which
- # does.
- open_uri_file = downloaded_file
- downloaded_file = copy_to_tempfile(uri.path, open_uri_file)
- OpenURI::Meta.init downloaded_file, open_uri_file
-
- downloaded_file.extend DownloadedFile
- downloaded_file
- end
-
- def stream(url, options = {})
- warn "Down.stream is deprecated and will be removed in Down 3. Use Down.open instead."
- io = open(url, options)
- io.each_chunk { |chunk| yield chunk, io.size }
- io.close
- end
-
- 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
- store = OpenSSL::X509::Store.new
- if options[:ssl_ca_cert]
- Array(options[:ssl_ca_cert]).each do |cert|
- File.directory?(cert) ? store.add_path(cert) : store.add_file(cert)
- end
- else
- store.set_default_paths
- end
- http.cert_store = store
- end
-
- request_headers = options.select { |key, value| key.is_a?(String) }
- get = Net::HTTP::Get.new(uri.request_uri, request_headers)
- get.basic_auth(uri.user, uri.password) if uri.user || uri.password
-
- request = Fiber.new do
- http.start do
- http.request(get) 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 #{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
-
- chunked_io = ChunkedIO.new(
- chunks: Enumerator.new { |y| y << tempfile.read(16*1024) until tempfile.eof? },
- size: tempfile.size,
- on_close: -> { tempfile.close! },
- )
- else
- 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)
- io.close
- tempfile.close
- FileUtils.mv io.path, tempfile.path
- else
- IO.copy_stream(io, tempfile)
- io.rewind
- end
- tempfile.open
- tempfile
- end
-
- module DownloadedFile
- def original_filename
- filename_from_content_disposition || filename_from_uri
- end
-
- private
-
- def filename_from_content_disposition
- content_disposition = meta["content-disposition"].to_s
- filename = content_disposition[/filename="([^"]*)"/, 1] || content_disposition[/filename=(.+)/, 1]
- filename = CGI.unescape(filename.to_s.strip)
- filename unless filename.empty?
- end
-
- def filename_from_uri
- path = base_uri.path
- filename = path.split("/").last
- CGI.unescape(filename) if filename
- end
- end
-end
+require "down/version"
+require "down/net_http" unless Down.respond_to?(:download)