down 5.6.0 → 5.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e1ee0b81e91856c59a6c43aaca2b0d310249ab49b96e0eb9b32f76491a35c08
4
- data.tar.gz: 182fe92d881b17ad97779d86715b8916c5c194f137b3e57bed6ab6b47b25d077
3
+ metadata.gz: dafd791660ca13ac117b8db2da6a31a38b5c6008735039dcbf4263b60438cf6a
4
+ data.tar.gz: 1ce7f027452f7c21421f169be6f967d3a180c3e34940d07c0d41be35fb1015cf
5
5
  SHA512:
6
- metadata.gz: 9d695a18d0f70d4bd6406e4bc21db7aa992a7ae10d9ccce87e42bccf7175a3ea73f438b4fc766d6c3512e72c3ff8635cc029fe1ee611d30fb2fd0e2c28ef1970
7
- data.tar.gz: df566a6a80c99016a8cbe382110865c8bbfc1034a5b5e5527051f43181833ed9782deb04c74704a5b001d995793e92f4bca68fddbdce8eee1924005618a38caa
6
+ metadata.gz: 32d796dfff756b332fbf688d2560a08820f3636b1e64b7e5545bb896f2955a5c5def44c9866207a7a9898eba0bbb45bee8d8d663e7c1060a43157556624cd176
7
+ data.tar.gz: 13fedb0b5618d6c7ff9dda385835e6fb15a9a3d6bdad30db201c4ee216ddb6b508cbad374e7dd48d08df5ff26b39b2ca8e837789f44a6e59df2beab164c705c0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 5.6.2 (2026-11-01)
2
+
3
+ * Return `nil` `Down::ChunkedIO#size` from `Down::NetHttp.open` when net/http decompresses the body (@wtn)
4
+
5
+ * Set `Down::ResponseError#response` to the response object on invalid redirect URIs in `Down::NetHttp` (@wtn)
6
+
7
+ ## 5.6.1 (2026-10-18)
8
+
9
+ * Avoid warnings on requiring `cgi` on Ruby 4.0+ (@janko)
10
+
1
11
  ## 5.6.0 (2026-04-25)
2
12
 
3
13
  * Require Ruby 3.2+ (@janko)
data/README.md CHANGED
@@ -109,7 +109,7 @@ internally downloads chunks of the remote file, but only how much is needed.
109
109
 
110
110
  ```rb
111
111
  remote_file = Down.open("http://example.com/image.jpg")
112
- remote_file.size # read from the "Content-Length" header
112
+ remote_file.size # read from the "Content-Length" header, nil if unknown
113
113
 
114
114
  remote_file.read(1024) # downloads and returns first 1 KB
115
115
  remote_file.read(1024) # downloads and returns next 1 KB
@@ -121,6 +121,17 @@ remote_file.eof? #=> true
121
121
  remote_file.close # closes the HTTP connection and deletes the internal Tempfile
122
122
  ```
123
123
 
124
+ The size is `nil` when the server doesn't send a `Content-Length`. With the
125
+ Net::HTTP backend it's also `nil` when the response body is compressed, because
126
+ Net::HTTP decompresses it on the fly and the header holds the compressed size. If
127
+ you need the size, you can set your own `Accept-Encoding`, which turns off
128
+ decompression; the body may then need to be decompressed by hand if the server
129
+ sends it compressed anyway:
130
+
131
+ ```rb
132
+ remote_file = Down.open("http://example.com/file.json", headers: { "Accept-Encoding" => "identity" })
133
+ ```
134
+
124
135
  The following IO methods are implemented:
125
136
 
126
137
  * `#read` & `#readpartial`
data/down.gemspec CHANGED
@@ -20,7 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "minitest", "~> 6.0"
22
22
  spec.add_development_dependency "mocha", "~> 1.5"
23
- spec.add_development_dependency "cgi"
24
23
  spec.add_development_dependency "rake"
25
24
  spec.add_development_dependency "httpx", "~> 1.0", "< 1.4.4"
26
25
  spec.add_development_dependency "http", "~> 6.0"
data/lib/down/http.rb CHANGED
@@ -13,8 +13,8 @@ module Down
13
13
  # Provides streaming downloads implemented with HTTP.rb.
14
14
  class Http < Backend
15
15
  # Initializes the backend with common defaults.
16
- def initialize(**options, &block)
17
- @method = options.delete(:method) || :get
16
+ def initialize(method: :get, **options, &block)
17
+ @method = method
18
18
  @client = HTTP
19
19
  .headers("User-Agent" => "Down/#{Down::VERSION}")
20
20
  .follow(max_hops: 2)
@@ -29,10 +29,10 @@ module Down
29
29
 
30
30
  # Downlods the remote file to disk. Accepts HTTP.rb options via a hash or a
31
31
  # block, and some additional options as well.
32
- def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil, tempfile_name: nil, **options, &block)
33
- response = request(url, **options, &block)
32
+ def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil, tempfile_name: nil, **, &)
33
+ response = request(url, **, &)
34
34
 
35
- content_length_proc.call(response.content_length) if content_length_proc && response.content_length
35
+ content_length_proc&.call(response.content_length) if response.content_length
36
36
 
37
37
  if max_size && response.content_length && response.content_length > max_size
38
38
  raise Down::TooLarge, "file is too large (#{response.content_length/1024/1024}MB, max is #{max_size/1024/1024}MB)"
@@ -45,7 +45,7 @@ module Down
45
45
  tempfile.write(chunk)
46
46
  chunk.clear # deallocate string
47
47
 
48
- progress_proc.call(tempfile.size) if progress_proc
48
+ progress_proc&.call(tempfile.size)
49
49
 
50
50
  if max_size && tempfile.size > max_size
51
51
  raise Down::TooLarge, "file is too large (#{tempfile.size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
@@ -60,15 +60,15 @@ module Down
60
60
 
61
61
  download_result(tempfile, destination)
62
62
  rescue
63
- tempfile.close! if tempfile
63
+ tempfile&.close!
64
64
  raise
65
65
  end
66
66
 
67
67
  # Starts retrieving the remote file and returns an IO-like object which
68
68
  # downloads the response body on-demand. Accepts HTTP.rb options via a hash
69
69
  # or a block.
70
- def open(url, rewindable: true, **options, &block)
71
- response = request(url, **options, &block)
70
+ def open(url, rewindable: true, **, &)
71
+ response = request(url, **, &)
72
72
 
73
73
  Down::ChunkedIO.new(
74
74
  chunks: enum_for(:stream_body, response),
@@ -85,27 +85,27 @@ module Down
85
85
 
86
86
  private
87
87
 
88
- def request(url, method: @method, **options, &block)
89
- response = send_request(method, url, **options, &block)
88
+ def request(url, method: @method, **, &)
89
+ response = send_request(method, url, **, &)
90
90
  response_error!(response) unless response.status.success?
91
91
  response
92
92
  end
93
93
 
94
- def send_request(method, url, **options, &block)
94
+ def send_request(method, url, **, &block)
95
95
  uri = HTTP::URI.parse(url)
96
96
 
97
97
  client = @client
98
98
  client = client.basic_auth(user: uri.user, pass: uri.password) if uri.user || uri.password
99
99
  client = block.call(client) if block
100
100
 
101
- client.request(method, url, **options)
101
+ client.request(method, url, **)
102
102
  rescue => exception
103
103
  request_error!(exception)
104
104
  end
105
105
 
106
106
  # Yields chunks of the response body to the block.
107
- def stream_body(response, &block)
108
- response.body.each(&block)
107
+ def stream_body(response, &)
108
+ response.body.each(&)
109
109
  rescue => exception
110
110
  request_error!(exception)
111
111
  ensure
data/lib/down/httpx.rb CHANGED
@@ -6,19 +6,16 @@ require "httpx"
6
6
 
7
7
  require "down/backend"
8
8
 
9
-
10
9
  module Down
11
10
  # Provides streaming downloads implemented with HTTPX.
12
11
  class Httpx < Backend
13
- # Initializes the backend
14
-
15
12
  USER_AGENT = "Down/#{Down::VERSION}"
16
13
 
17
14
  BASIC_AUTH = HTTPX::VERSION >= "1.0.0" ? :basic_auth : :basic_authentication
18
15
 
19
- def initialize(**options, &block)
20
- @method = options.delete(:method) || :get
21
- headers = options.delete(:headers) || {}
16
+ # Initializes the backend with common defaults.
17
+ def initialize(method: :get, headers: {}, **options, &block)
18
+ @method = method
22
19
  @client = HTTPX
23
20
  .plugin(:follow_redirects, max_redirects: 2)
24
21
  .plugin(BASIC_AUTH)
@@ -32,20 +29,15 @@ module Down
32
29
  @client = block.call(@client) if block
33
30
  end
34
31
 
35
-
36
32
  # Downloads the remote file to disk. Accepts HTTPX options via a hash or a
37
33
  # block, and some additional options as well.
38
- def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil, tempfile_name: nil, **options, &block)
39
- client = @client
40
-
41
- response = request(client, url, **options, &block)
42
-
43
- content_length = nil
34
+ def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil, tempfile_name: nil, **, &)
35
+ response = request(url, **, &)
44
36
 
45
37
  if response.headers.key?("content-length")
46
38
  content_length = response.headers["content-length"].to_i
47
39
 
48
- content_length_proc.call(content_length) if content_length_proc
40
+ content_length_proc&.call(content_length)
49
41
 
50
42
  if max_size && content_length > max_size
51
43
  response.close
@@ -60,7 +52,7 @@ module Down
60
52
  tempfile.write(chunk)
61
53
  chunk.clear # deallocate string
62
54
 
63
- progress_proc.call(tempfile.size) if progress_proc
55
+ progress_proc&.call(tempfile.size)
64
56
 
65
57
  if max_size && tempfile.size > max_size
66
58
  raise Down::TooLarge, "file is too large (#{tempfile.size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
@@ -77,20 +69,19 @@ module Down
77
69
 
78
70
  download_result(tempfile, destination)
79
71
  rescue
80
- tempfile.close! if tempfile
72
+ tempfile&.close!
81
73
  raise
82
74
  end
83
75
 
84
76
  # Starts retrieving the remote file and returns an IO-like object which
85
77
  # downloads the response body on-demand. Accepts HTTPX options via a hash
86
78
  # or a block.
87
- def open(url, rewindable: true, **options, &block)
88
- response = request(@client, url, stream: true, **options, &block)
89
- size = response.headers["content-length"]
90
- size = size.to_i if size
79
+ def open(url, rewindable: true, **, &)
80
+ response = request(url, stream: true, **, &)
81
+
91
82
  Down::ChunkedIO.new(
92
83
  chunks: enum_for(:stream_body, response),
93
- size: size,
84
+ size: response.headers["content-length"]&.to_i,
94
85
  encoding: response.body.encoding,
95
86
  rewindable: rewindable,
96
87
  data: {
@@ -118,8 +109,8 @@ module Down
118
109
  request_error!(exception)
119
110
  end
120
111
 
121
- def request(client, url, method: @method, **options, &block)
122
- response = send_request(client, method, url, **options, &block)
112
+ def request(url, method: @method, **, &)
113
+ response = send_request(method, url, **, &)
123
114
  response.raise_for_status
124
115
  response_error!(response) unless (200..399).include?(response.status)
125
116
  response
@@ -129,7 +120,7 @@ module Down
129
120
  request_error!(error)
130
121
  end
131
122
 
132
- def send_request(client, method, url, **options, &block)
123
+ def send_request(method, url, **, &block)
133
124
  uri = URI(url)
134
125
  client = @client
135
126
  if uri.user || uri.password
@@ -138,7 +129,7 @@ module Down
138
129
  end
139
130
  client = block.call(client) if block
140
131
 
141
- client.request(method.to_s.upcase, uri, stream: true, **options)
132
+ client.request(method.to_s.upcase, uri, stream: true, **)
142
133
  rescue => exception
143
134
  request_error!(exception)
144
135
  end
data/lib/down/net_http.rb CHANGED
@@ -17,31 +17,36 @@ module Down
17
17
  addressable_uri.normalize.to_s
18
18
  end
19
19
 
20
+ # Content encodings that Net::HTTP transparently decodes.
21
+ DECODED_ENCODINGS = %w[gzip x-gzip deflate].freeze
22
+ private_constant :DECODED_ENCODINGS
23
+
24
+ DEFAULT_OPTIONS = {
25
+ headers: { "User-Agent" => "Down/#{Down::VERSION}" }.freeze,
26
+ max_redirects: 2,
27
+ open_timeout: 30,
28
+ read_timeout: 30,
29
+ uri_normalizer: URI_NORMALIZER,
30
+ }.freeze
31
+
20
32
  # Initializes the backend with common defaults.
21
- def initialize(*args, **options)
22
- @options = merge_options({
23
- headers: { "User-Agent" => "Down/#{Down::VERSION}" },
24
- max_redirects: 2,
25
- open_timeout: 30,
26
- read_timeout: 30,
27
- uri_normalizer: URI_NORMALIZER,
28
- }, *args, **options)
33
+ def initialize(...)
34
+ @options = merge_options(DEFAULT_OPTIONS, ...)
29
35
  end
30
36
 
31
37
  # Downloads a remote file to disk using open-uri. Accepts any open-uri
32
38
  # options, and a few more.
33
- def download(url, *args, **options)
34
- options = merge_options(@options, *args, **options)
39
+ def download(url, ...)
40
+ options = merge_options(@options, ...)
35
41
 
36
42
  max_size = options.delete(:max_size)
37
- max_redirects = options.delete(:max_redirects)
38
43
  progress_proc = options.delete(:progress_proc)
39
44
  content_length_proc = options.delete(:content_length_proc)
45
+ proxy = options.delete(:proxy)
40
46
  destination = options.delete(:destination)
41
47
  headers = options.delete(:headers)
42
48
  uri_normalizer = options.delete(:uri_normalizer)
43
49
  extension = options.delete(:extension)
44
- auth_on_redirect = options.delete(:auth_on_redirect)
45
50
  tempfile_name = options.delete(:tempfile_name)
46
51
 
47
52
  # Use open-uri's :content_lenth_proc or :progress_proc to raise an
@@ -54,20 +59,20 @@ module Down
54
59
  if size && max_size && size > max_size
55
60
  raise Down::TooLarge, "file is too large (#{size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
56
61
  end
57
- content_length_proc.call(size) if content_length_proc
62
+ content_length_proc&.call(size)
58
63
  },
59
64
  progress_proc: proc { |current_size|
60
65
  if max_size && current_size > max_size
61
66
  raise Down::TooLarge, "file is too large (#{current_size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
62
67
  end
63
- progress_proc.call(current_size) if progress_proc
68
+ progress_proc&.call(current_size)
64
69
  },
65
70
  redirect: false,
66
71
  }
67
72
 
68
73
  # Handle basic authentication in the :proxy option.
69
- if options[:proxy]
70
- proxy = URI(options.delete(:proxy))
74
+ if proxy
75
+ proxy = URI(proxy)
71
76
  user = proxy.user
72
77
  password = proxy.password
73
78
 
@@ -81,10 +86,9 @@ module Down
81
86
  end
82
87
  end
83
88
 
84
- open_uri_options.merge!(options)
85
- open_uri_options.merge!(headers)
89
+ open_uri_options.merge!(options, headers)
86
90
 
87
- uri = ensure_uri(normalize_uri(url, uri_normalizer: uri_normalizer))
91
+ uri = ensure_uri(normalize_uri(url, uri_normalizer:))
88
92
 
89
93
  # Handle basic authentication in the remote URL.
90
94
  if uri.user || uri.password
@@ -93,7 +97,7 @@ module Down
93
97
  uri.password = nil
94
98
  end
95
99
 
96
- open_uri_file = open_uri(uri, open_uri_options, follows_remaining: max_redirects, auth_on_redirect: auth_on_redirect)
100
+ open_uri_file = open_uri(uri, **open_uri_options)
97
101
 
98
102
  # Handle the fact that open-uri returns StringIOs for small files.
99
103
  extname = extension ? ".#{extension}" : File.extname(open_uri_file.base_uri.path)
@@ -106,18 +110,16 @@ module Down
106
110
 
107
111
  # Starts retrieving the remote file using Net::HTTP and returns an IO-like
108
112
  # object which downloads the response body on-demand.
109
- def open(url, *args, **options)
110
- options = merge_options(@options, *args, **options)
113
+ def open(url, ...)
114
+ options = merge_options(@options, ...)
111
115
 
112
- max_redirects = options.delete(:max_redirects)
113
- uri_normalizer = options.delete(:uri_normalizer)
114
- auth_on_redirect = options.delete(:auth_on_redirect)
116
+ uri_normalizer = options.delete(:uri_normalizer)
115
117
 
116
- uri = ensure_uri(normalize_uri(url, uri_normalizer: uri_normalizer))
118
+ uri = ensure_uri(normalize_uri(url, uri_normalizer:))
117
119
 
118
120
  # Create a Fiber that halts when response headers are received.
119
121
  request = Fiber.new do
120
- net_http_request(uri, options, follows_remaining: max_redirects, auth_on_redirect: auth_on_redirect) do |response|
122
+ net_http_request(uri, **options) do |response|
121
123
  Fiber.yield response
122
124
  end
123
125
  end
@@ -129,7 +131,7 @@ module Down
129
131
  # Build an IO-like object that will retrieve response body on-demand.
130
132
  Down::ChunkedIO.new(
131
133
  chunks: enum_for(:stream_body, response),
132
- size: response["Content-Length"] && response["Content-Length"].to_i,
134
+ size: content_length(response),
133
135
  encoding: response.type_params["charset"],
134
136
  rewindable: options.fetch(:rewindable, true),
135
137
  on_close: -> { request.resume }, # close HTTP connnection
@@ -144,10 +146,10 @@ module Down
144
146
  private
145
147
 
146
148
  # Calls open-uri's URI::HTTP#open method. Additionally handles redirects.
147
- def open_uri(uri, options, follows_remaining:, auth_on_redirect:)
149
+ def open_uri(uri, max_redirects:, auth_on_redirect: false, **options)
148
150
  uri.open(options)
149
151
  rescue OpenURI::HTTPRedirect => exception
150
- raise Down::TooManyRedirects, "too many redirects" if follows_remaining == 0
152
+ raise Down::TooManyRedirects, "too many redirects" if max_redirects == 0
151
153
 
152
154
  # fail if redirect URI scheme is not http or https
153
155
  begin
@@ -155,7 +157,7 @@ module Down
155
157
  rescue Down::InvalidUrl
156
158
  response = rebuild_response_from_open_uri_exception(exception)
157
159
 
158
- raise ResponseError.new("Invalid Redirect URI: #{exception.uri}", response: response)
160
+ raise ResponseError.new("Invalid Redirect URI: #{exception.uri}", response)
159
161
  end
160
162
 
161
163
  # do not leak credentials on redirect
@@ -171,14 +173,14 @@ module Down
171
173
  options["Cookie"] = (old_cookies | new_cookies).join(';')
172
174
  end
173
175
 
174
- follows_remaining -= 1
176
+ max_redirects -= 1
175
177
  retry
176
178
  rescue OpenURI::HTTPError => exception
177
179
  response = rebuild_response_from_open_uri_exception(exception)
178
180
 
179
181
  # open-uri attempts to parse the redirect URI, so we re-raise that exception
180
182
  if exception.message.include?("(Invalid Location URI)")
181
- raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response: response)
183
+ raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response)
182
184
  end
183
185
 
184
186
  response_error!(response)
@@ -207,8 +209,8 @@ module Down
207
209
  end
208
210
 
209
211
  # Makes a Net::HTTP request and follows redirects.
210
- def net_http_request(uri, options, follows_remaining:, auth_on_redirect:, &block)
211
- http, request = create_net_http(uri, options)
212
+ def net_http_request(uri, max_redirects:, auth_on_redirect: false, **options, &)
213
+ http, request = create_net_http(uri, **options)
212
214
 
213
215
  begin
214
216
  response = http.start do
@@ -229,13 +231,13 @@ module Down
229
231
  if response.is_a?(Net::HTTPNotModified)
230
232
  raise Down::NotModified
231
233
  elsif response.is_a?(Net::HTTPRedirection)
232
- raise Down::TooManyRedirects if follows_remaining == 0
234
+ raise Down::TooManyRedirects if max_redirects == 0
233
235
 
234
236
  # fail if redirect URI is not a valid http or https URL
235
237
  begin
236
238
  location = ensure_uri(response["Location"], allow_relative: true)
237
239
  rescue Down::InvalidUrl
238
- raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response: response)
240
+ raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response)
239
241
  end
240
242
 
241
243
  # do not leak credentials on redirect
@@ -251,16 +253,16 @@ module Down
251
253
  options[:http_basic_authentication] ||= [uri.user, uri.password]
252
254
  end
253
255
 
254
- net_http_request(location, options, follows_remaining: follows_remaining - 1, auth_on_redirect: auth_on_redirect, &block)
256
+ net_http_request(location, **options, max_redirects: max_redirects - 1, auth_on_redirect:, &)
255
257
  end
256
258
  end
257
259
 
258
260
  # Build a Net::HTTP object for making a request.
259
- def create_net_http(uri, options)
261
+ def create_net_http(uri, proxy: nil, ssl_verify_mode: nil, ssl_ca_cert: nil, read_timeout: nil, open_timeout: nil, headers: {}, http_basic_authentication: nil, **)
260
262
  http_class = Net::HTTP
261
263
 
262
- if options[:proxy]
263
- proxy = URI(options[:proxy])
264
+ if proxy
265
+ proxy = URI(proxy)
264
266
  http_class = Net::HTTP::Proxy(proxy.hostname, proxy.port, proxy.user, proxy.password)
265
267
  end
266
268
 
@@ -269,10 +271,10 @@ module Down
269
271
  # Handle SSL parameters (taken from the open-uri implementation).
270
272
  if uri.is_a?(URI::HTTPS)
271
273
  http.use_ssl = true
272
- http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
274
+ http.verify_mode = ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
273
275
  store = OpenSSL::X509::Store.new
274
- if options[:ssl_ca_cert]
275
- Array(options[:ssl_ca_cert]).each do |cert|
276
+ if ssl_ca_cert
277
+ Array(ssl_ca_cert).each do |cert|
276
278
  File.directory?(cert) ? store.add_path(cert) : store.add_file(cert)
277
279
  end
278
280
  else
@@ -281,24 +283,38 @@ module Down
281
283
  http.cert_store = store
282
284
  end
283
285
 
284
- http.read_timeout = options[:read_timeout] if options.key?(:read_timeout)
285
- http.open_timeout = options[:open_timeout] if options.key?(:open_timeout)
286
+ http.read_timeout = read_timeout
287
+ http.open_timeout = open_timeout
286
288
 
287
- get = Net::HTTP::Get.new(uri, options[:headers].to_h)
289
+ get = Net::HTTP::Get.new(uri, headers.to_h)
288
290
 
289
- user, password = options[:http_basic_authentication] || [uri.user, uri.password]
291
+ user, password = http_basic_authentication || [uri.user, uri.password]
290
292
  get.basic_auth(user, password) if user || password
291
293
 
292
294
  [http, get]
293
295
  end
294
296
 
295
297
  # Yields chunks of the response body to the block.
296
- def stream_body(response, &block)
297
- response.read_body(&block)
298
+ def stream_body(response, &)
299
+ response.read_body(&)
298
300
  rescue => exception
299
301
  request_error!(exception)
300
302
  end
301
303
 
304
+ # Returns the Content-Length, unless a Transfer-Encoding is present or
305
+ # Net::HTTP will decode the body, in which case it doesn't reflect the
306
+ # number of bytes read. Decoding conditions mirror Net::HTTPResponse#inflater.
307
+ def content_length(response)
308
+ return if response["Transfer-Encoding"]
309
+
310
+ length = response.content_length
311
+ return length if length.nil? || length.zero?
312
+ return if response.decode_content && !response["Content-Range"] &&
313
+ DECODED_ENCODINGS.include?(response["Content-Encoding"]&.downcase)
314
+
315
+ length
316
+ end
317
+
302
318
  # Checks that the url is a valid URI and that its scheme is http or https.
303
319
  def ensure_uri(url, allow_relative: false)
304
320
  begin
@@ -384,6 +400,8 @@ module Down
384
400
  warn %([Down::NetHttp] Passing headers as top-level options has been deprecated, use the :headers option instead, e.g: `Down::NetHttp.download(headers: { "Key" => "Value", ... }, ...)`)
385
401
  new_options[:headers] = new_options.select { |key, value| key.is_a?(String) }
386
402
  new_options.reject! { |key, value| key.is_a?(String) }
403
+ else
404
+ new_options[:headers] ||= {}
387
405
  end
388
406
 
389
407
  options.merge(new_options) do |key, value1, value2|
data/lib/down/utils.rb CHANGED
@@ -1,4 +1,8 @@
1
- require "cgi"
1
+ if RUBY_VERSION >= "4.0"
2
+ require "cgi/escape"
3
+ else
4
+ require "cgi/util"
5
+ end
2
6
 
3
7
  module Down
4
8
  module Utils
data/lib/down/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  module Down
4
- VERSION = "5.6.0"
4
+ VERSION = "5.6.2"
5
5
  end
data/lib/down.rb CHANGED
@@ -6,12 +6,12 @@ require "down/net_http"
6
6
  module Down
7
7
  module_function
8
8
 
9
- def download(*args, **options, &block)
10
- backend.download(*args, **options, &block)
9
+ def download(...)
10
+ backend.download(...)
11
11
  end
12
12
 
13
- def open(*args, **options, &block)
14
- backend.open(*args, **options, &block)
13
+ def open(...)
14
+ backend.open(...)
15
15
  end
16
16
 
17
17
  # Allows setting a backend via a symbol or a downloader object.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: down
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0
4
+ version: 5.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '1.5'
68
- - !ruby/object:Gem::Dependency
69
- name: cgi
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0'
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: rake
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -193,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
179
  - !ruby/object:Gem::Version
194
180
  version: '0'
195
181
  requirements: []
196
- rubygems_version: 4.0.3
182
+ rubygems_version: 4.0.20
197
183
  specification_version: 4
198
184
  summary: Robust streaming downloads using Net::HTTP, http.rb or HTTPX.
199
185
  test_files: []