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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +12 -1
- data/down.gemspec +0 -1
- data/lib/down/http.rb +15 -15
- data/lib/down/httpx.rb +16 -25
- data/lib/down/net_http.rb +68 -50
- data/lib/down/utils.rb +5 -1
- data/lib/down/version.rb +1 -1
- data/lib/down.rb +4 -4
- metadata +2 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dafd791660ca13ac117b8db2da6a31a38b5c6008735039dcbf4263b60438cf6a
|
|
4
|
+
data.tar.gz: 1ce7f027452f7c21421f169be6f967d3a180c3e34940d07c0d41be35fb1015cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
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,
|
|
33
|
-
response = request(url,
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
71
|
-
response = request(url,
|
|
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,
|
|
89
|
-
response = send_request(method, url,
|
|
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,
|
|
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, **
|
|
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, &
|
|
108
|
-
response.body.each(&
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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,
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
88
|
-
response = request(
|
|
89
|
-
|
|
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:
|
|
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(
|
|
122
|
-
response = send_request(
|
|
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(
|
|
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, **
|
|
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(
|
|
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,
|
|
34
|
-
options = merge_options(@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
|
|
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
|
|
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
|
|
70
|
-
proxy = URI(
|
|
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:
|
|
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
|
|
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,
|
|
110
|
-
options = merge_options(@options,
|
|
113
|
+
def open(url, ...)
|
|
114
|
+
options = merge_options(@options, ...)
|
|
111
115
|
|
|
112
|
-
|
|
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:
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
263
|
-
proxy = URI(
|
|
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 =
|
|
274
|
+
http.verify_mode = ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
|
|
273
275
|
store = OpenSSL::X509::Store.new
|
|
274
|
-
if
|
|
275
|
-
Array(
|
|
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 =
|
|
285
|
-
http.open_timeout =
|
|
286
|
+
http.read_timeout = read_timeout
|
|
287
|
+
http.open_timeout = open_timeout
|
|
286
288
|
|
|
287
|
-
get = Net::HTTP::Get.new(uri,
|
|
289
|
+
get = Net::HTTP::Get.new(uri, headers.to_h)
|
|
288
290
|
|
|
289
|
-
user, 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, &
|
|
297
|
-
response.read_body(&
|
|
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
data/lib/down/version.rb
CHANGED
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(
|
|
10
|
-
backend.download(
|
|
9
|
+
def download(...)
|
|
10
|
+
backend.download(...)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def open(
|
|
14
|
-
backend.open(
|
|
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.
|
|
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.
|
|
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: []
|