down 5.4.2 → 5.6.1
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 +34 -0
- data/README.md +10 -58
- data/down.gemspec +8 -13
- data/lib/down/chunked_io.rb +9 -1
- data/lib/down/http.rb +27 -19
- data/lib/down/httpx.rb +29 -31
- data/lib/down/net_http.rb +68 -54
- data/lib/down/utils.rb +5 -1
- data/lib/down/version.rb +1 -1
- data/lib/down/wget.rb +2 -0
- data/lib/down.rb +4 -4
- metadata +29 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 04d70935026de1e61cbda54578c360164e4ab252658c2b7a5531701e8195f55d
|
|
4
|
+
data.tar.gz: 047bbd998f10b26ad7865f4dc445a5ddc4ad811c4639ba51b8691eb1436cb6af
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0924393fcdee5795751393b9965e3cb2221da29baa5797d30bdd19115edb63972e8c6119310e1f65f77feeae7aae61ef5f03452394c77b57a57aaa1d8663a0da'
|
|
7
|
+
data.tar.gz: 808f8ac45f19f7a54e708b9b51ca4e659474eebe2bd14d894157a9454d4a2726d1260d323c61445218d8cc00f4b25497fb1c680c527e0fbc51a9cf1f374d605c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
## 5.6.1 (2026-10-18)
|
|
2
|
+
|
|
3
|
+
* Avoid warnings on requiring `cgi` on Ruby 4.0+ (@janko)
|
|
4
|
+
|
|
5
|
+
## 5.6.0 (2026-04-25)
|
|
6
|
+
|
|
7
|
+
* Require Ruby 3.2+ (@janko)
|
|
8
|
+
|
|
9
|
+
* Return `Down::Chunked#gets(nil, n)` in specified encoding for `IO` compatibility (@janko)
|
|
10
|
+
|
|
11
|
+
* Allow net/http to transparently decompress response bodies again (@janko)
|
|
12
|
+
|
|
13
|
+
* Handle `:auth_on_redirect` for absolute redirects in net/http backend (@janko)
|
|
14
|
+
|
|
15
|
+
* Fix `#pos` not always being updated after `Down::ChunkedIO#gets` (@janko)
|
|
16
|
+
|
|
17
|
+
* Add `#eof` alias to `Down::ChunkedIO#eof?` for `IO` compatibility
|
|
18
|
+
|
|
19
|
+
* Add `#length` alias for `Down::ChunkedIO#size` for `multipart-post` gem compatibility (@janko)
|
|
20
|
+
|
|
21
|
+
## 5.5.0 (2026-03-18)
|
|
22
|
+
|
|
23
|
+
* Add support for http.rb 6.0 (@sferik)
|
|
24
|
+
|
|
25
|
+
* Fix `Down::TooManyRedirects` error with `Down::Httpx` happening on first redirect (@artrybalko)
|
|
26
|
+
|
|
27
|
+
* Add `:auth_on_redirect` option to `Down::NetHttp` backend for skipping authentication on redirects (@makrsmark)
|
|
28
|
+
|
|
29
|
+
* Add `:tempfile_name` keyword argument to `.download` for overriding tempfile prefix (@softwaregravy)
|
|
30
|
+
|
|
31
|
+
* Drop support for Ruby 2.6 and older (@janko)
|
|
32
|
+
|
|
33
|
+
* Deprecate wget backend (@janko)
|
|
34
|
+
|
|
1
35
|
## 5.4.2 (2024-04-19)
|
|
2
36
|
|
|
3
37
|
* Add support for HTTPX 1.x (@HoneyryderChuck)
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Down
|
|
2
2
|
|
|
3
3
|
Down is a utility tool for streaming, flexible and safe downloading of remote
|
|
4
|
-
files. It can use [open-uri] + `Net::HTTP`, [http.rb]
|
|
5
|
-
|
|
4
|
+
files. It can use [open-uri] + `Net::HTTP`, [http.rb] or [HTTPX] as the backend
|
|
5
|
+
HTTP library.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -70,6 +70,13 @@ tempfile = Down.download("http://example.com/some/file", extension: "txt")
|
|
|
70
70
|
File.extname(tempfile.path) #=> ".txt"
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
You can also override the default tempfile prefix:
|
|
74
|
+
|
|
75
|
+
```rb
|
|
76
|
+
tempfile = Down.download("http://example.com/image.jpg", tempfile_name: "custom-prefix")
|
|
77
|
+
File.basename(tempfile.path) #=> "custom-prefix20150925-55456-z7vxqz.jpg"
|
|
78
|
+
```
|
|
79
|
+
|
|
73
80
|
### Basic authentication
|
|
74
81
|
|
|
75
82
|
`Down.download` and `Down.open` will automatically detect and apply HTTP basic
|
|
@@ -235,7 +242,6 @@ The following backends are available:
|
|
|
235
242
|
* [Down::NetHttp](#downnethttp) (default)
|
|
236
243
|
* [Down::Http](#downhttp)
|
|
237
244
|
* [Down::Httpx](#downhttpx)
|
|
238
|
-
* [Down::Wget](#downwget)
|
|
239
245
|
|
|
240
246
|
You can use the backend directly:
|
|
241
247
|
|
|
@@ -450,7 +456,7 @@ supports the HTTP/2 protocol, in addition to many other features.
|
|
|
450
456
|
|
|
451
457
|
```rb
|
|
452
458
|
gem "down", "~> 5.0"
|
|
453
|
-
gem "httpx", "~> 0
|
|
459
|
+
gem "httpx", "~> 1.0"
|
|
454
460
|
```
|
|
455
461
|
```rb
|
|
456
462
|
require "down/httpx"
|
|
@@ -465,61 +471,8 @@ io #=> #<Down::ChunkedIO ...>
|
|
|
465
471
|
It's implemented in much of the same way as `Down::Http`, so be sure to check
|
|
466
472
|
its docs for ways to pass additional options.
|
|
467
473
|
|
|
468
|
-
### Down::Wget (experimental)
|
|
469
|
-
|
|
470
|
-
The `Down::Wget` backend implements downloads using the `wget` command line
|
|
471
|
-
utility.
|
|
472
|
-
|
|
473
|
-
```rb
|
|
474
|
-
gem "down", "~> 5.0"
|
|
475
|
-
gem "posix-spawn" # omit if on JRuby
|
|
476
|
-
gem "http_parser.rb"
|
|
477
|
-
```
|
|
478
|
-
```rb
|
|
479
|
-
require "down/wget"
|
|
480
|
-
|
|
481
|
-
tempfile = Down::Wget.download("http://nature.com/forest.jpg")
|
|
482
|
-
tempfile #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg>
|
|
483
|
-
|
|
484
|
-
io = Down::Wget.open("http://nature.com/forest.jpg")
|
|
485
|
-
io #=> #<Down::ChunkedIO ...>
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
One major advantage of `wget` is that it automatically resumes downloads that
|
|
489
|
-
were interrupted due to network failures, which is very useful when you're
|
|
490
|
-
downloading large files.
|
|
491
|
-
|
|
492
|
-
However, the Wget backend should still be considered experimental, as it wasn't
|
|
493
|
-
easy to implement a CLI wrapper that streams output, so it's possible that I've
|
|
494
|
-
made mistakes. Let me know how it's working out for you 😉.
|
|
495
|
-
|
|
496
|
-
#### Additional arguments
|
|
497
|
-
|
|
498
|
-
You can pass additional arguments to the underlying `wget` commmand via symbols:
|
|
499
|
-
|
|
500
|
-
```rb
|
|
501
|
-
Down::Wget.download("http://nature.com/forest.jpg", :no_proxy, connect_timeout: 3)
|
|
502
|
-
Down::Wget.open("http://nature.com/forest.jpg", user: "janko", password: "secret")
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
You can also initialize the backend with default arguments:
|
|
506
|
-
|
|
507
|
-
```rb
|
|
508
|
-
wget = Down::Wget.new(:no_proxy, connect_timeout: 3)
|
|
509
|
-
|
|
510
|
-
wget.download("http://nature.com/forest.jpg")
|
|
511
|
-
wget.open("http://nature.com/forest.jpg")
|
|
512
|
-
```
|
|
513
|
-
|
|
514
474
|
## Development
|
|
515
475
|
|
|
516
|
-
Tests require that a [httpbin] server is running locally, which you can do via Docker:
|
|
517
|
-
|
|
518
|
-
```sh
|
|
519
|
-
$ docker pull kennethreitz/httpbin
|
|
520
|
-
$ docker run -p 80:80 kennethreitz/httpbin
|
|
521
|
-
```
|
|
522
|
-
|
|
523
476
|
Then you can run tests:
|
|
524
477
|
|
|
525
478
|
```
|
|
@@ -535,4 +488,3 @@ $ bundle exec rake test
|
|
|
535
488
|
[http.rb]: https://github.com/httprb/http
|
|
536
489
|
[HTTPX]: https://github.com/HoneyryderChuck/httpx
|
|
537
490
|
[Addressable::URI]: https://github.com/sporkmonger/addressable
|
|
538
|
-
[httpbin]: https://github.com/postmanlabs/httpbin
|
data/down.gemspec
CHANGED
|
@@ -4,9 +4,9 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
spec.name = "down"
|
|
5
5
|
spec.version = Down::VERSION
|
|
6
6
|
|
|
7
|
-
spec.required_ruby_version = ">= 2
|
|
7
|
+
spec.required_ruby_version = ">= 3.2"
|
|
8
8
|
|
|
9
|
-
spec.summary = "Robust streaming downloads using Net::HTTP,
|
|
9
|
+
spec.summary = "Robust streaming downloads using Net::HTTP, http.rb or HTTPX."
|
|
10
10
|
spec.homepage = "https://github.com/janko/down"
|
|
11
11
|
spec.authors = ["Janko Marohnić"]
|
|
12
12
|
spec.email = ["janko.marohnic@gmail.com"]
|
|
@@ -16,18 +16,13 @@ Gem::Specification.new do |spec|
|
|
|
16
16
|
spec.require_path = "lib"
|
|
17
17
|
|
|
18
18
|
spec.add_dependency "addressable", "~> 2.8"
|
|
19
|
+
spec.add_dependency "base64", "~> 0.3"
|
|
19
20
|
|
|
20
|
-
spec.add_development_dependency "minitest", "~>
|
|
21
|
+
spec.add_development_dependency "minitest", "~> 6.0"
|
|
21
22
|
spec.add_development_dependency "mocha", "~> 1.5"
|
|
22
23
|
spec.add_development_dependency "rake"
|
|
23
|
-
spec.add_development_dependency "httpx", "~> 0", "<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
else
|
|
28
|
-
spec.add_development_dependency "http", "~> 4.3"
|
|
29
|
-
end
|
|
30
|
-
spec.add_development_dependency "posix-spawn" unless RUBY_ENGINE == "jruby"
|
|
31
|
-
spec.add_development_dependency "http_parser.rb" unless RUBY_ENGINE == "jruby"
|
|
32
|
-
spec.add_development_dependency "warning" if RUBY_VERSION >= "2.4"
|
|
24
|
+
spec.add_development_dependency "httpx", "~> 1.0", "< 1.4.4"
|
|
25
|
+
spec.add_development_dependency "http", "~> 6.0"
|
|
26
|
+
spec.add_development_dependency "warning"
|
|
27
|
+
spec.add_development_dependency "csv"
|
|
33
28
|
end
|
data/lib/down/chunked_io.rb
CHANGED
|
@@ -42,6 +42,11 @@ module Down
|
|
|
42
42
|
retrieve_chunk # fetch first chunk so that we know whether the file is empty
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# For compatibility with multipart-post gem.
|
|
46
|
+
def length
|
|
47
|
+
@size
|
|
48
|
+
end
|
|
49
|
+
|
|
45
50
|
# Yields elements of the underlying enumerator.
|
|
46
51
|
def each_chunk
|
|
47
52
|
fail IOError, "closed stream" if closed?
|
|
@@ -105,7 +110,7 @@ module Down
|
|
|
105
110
|
separator = separator_or_limit
|
|
106
111
|
end
|
|
107
112
|
|
|
108
|
-
return read(limit) if separator.nil?
|
|
113
|
+
return read(limit)&.force_encoding(@encoding) if separator.nil?
|
|
109
114
|
|
|
110
115
|
separator = "\n\n" if separator.empty?
|
|
111
116
|
|
|
@@ -124,6 +129,8 @@ module Down
|
|
|
124
129
|
data.clear # deallocate data
|
|
125
130
|
|
|
126
131
|
if extra
|
|
132
|
+
@position -= extra.bytesize
|
|
133
|
+
|
|
127
134
|
if cache
|
|
128
135
|
cache.pos -= extra.bytesize
|
|
129
136
|
else
|
|
@@ -238,6 +245,7 @@ module Down
|
|
|
238
245
|
return false if cache && !cache.eof?
|
|
239
246
|
@buffer.nil? && chunks_depleted?
|
|
240
247
|
end
|
|
248
|
+
alias eof eof?
|
|
241
249
|
|
|
242
250
|
# Implements IO#rewind semantics. Rewinds the Down::ChunkedIO by rewinding
|
|
243
251
|
# the cache and setting the position to the beginning of the file. Raises
|
data/lib/down/http.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen-string-literal: true
|
|
2
2
|
|
|
3
|
-
gem "http", ">= 2.1.0", "<
|
|
3
|
+
gem "http", ">= 2.1.0", "< 7"
|
|
4
4
|
|
|
5
5
|
require "http"
|
|
6
|
+
require "addressable/uri"
|
|
6
7
|
|
|
7
8
|
require "down/backend"
|
|
8
9
|
|
|
@@ -12,36 +13,39 @@ module Down
|
|
|
12
13
|
# Provides streaming downloads implemented with HTTP.rb.
|
|
13
14
|
class Http < Backend
|
|
14
15
|
# Initializes the backend with common defaults.
|
|
15
|
-
def initialize(**options, &block)
|
|
16
|
-
@method =
|
|
16
|
+
def initialize(method: :get, **options, &block)
|
|
17
|
+
@method = method
|
|
17
18
|
@client = HTTP
|
|
18
19
|
.headers("User-Agent" => "Down/#{Down::VERSION}")
|
|
19
20
|
.follow(max_hops: 2)
|
|
20
21
|
.timeout(connect: 30, write: 30, read: 30)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
if options.any?
|
|
24
|
+
client_class = defined?(HTTP::Session) ? HTTP::Session : HTTP::Client
|
|
25
|
+
@client = client_class.new(@client.default_options.merge(options))
|
|
26
|
+
end
|
|
23
27
|
@client = block.call(@client) if block
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
# Downlods the remote file to disk. Accepts HTTP.rb options via a hash or a
|
|
27
31
|
# block, and some additional options as well.
|
|
28
|
-
def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil,
|
|
29
|
-
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, **, &)
|
|
30
34
|
|
|
31
|
-
content_length_proc
|
|
35
|
+
content_length_proc&.call(response.content_length) if response.content_length
|
|
32
36
|
|
|
33
37
|
if max_size && response.content_length && response.content_length > max_size
|
|
34
38
|
raise Down::TooLarge, "file is too large (#{response.content_length/1024/1024}MB, max is #{max_size/1024/1024}MB)"
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
extname = extension ? ".#{extension}" : File.extname(response.uri.path)
|
|
38
|
-
tempfile = Tempfile.new(["down-http", extname], binmode: true)
|
|
42
|
+
tempfile = Tempfile.new([tempfile_name || "down-http", extname], binmode: true)
|
|
39
43
|
|
|
40
44
|
stream_body(response) do |chunk|
|
|
41
45
|
tempfile.write(chunk)
|
|
42
46
|
chunk.clear # deallocate string
|
|
43
47
|
|
|
44
|
-
progress_proc
|
|
48
|
+
progress_proc&.call(tempfile.size)
|
|
45
49
|
|
|
46
50
|
if max_size && tempfile.size > max_size
|
|
47
51
|
raise Down::TooLarge, "file is too large (#{tempfile.size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
|
|
@@ -56,15 +60,15 @@ module Down
|
|
|
56
60
|
|
|
57
61
|
download_result(tempfile, destination)
|
|
58
62
|
rescue
|
|
59
|
-
tempfile
|
|
63
|
+
tempfile&.close!
|
|
60
64
|
raise
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
# Starts retrieving the remote file and returns an IO-like object which
|
|
64
68
|
# downloads the response body on-demand. Accepts HTTP.rb options via a hash
|
|
65
69
|
# or a block.
|
|
66
|
-
def open(url, rewindable: true,
|
|
67
|
-
response = request(url,
|
|
70
|
+
def open(url, rewindable: true, **, &)
|
|
71
|
+
response = request(url, **, &)
|
|
68
72
|
|
|
69
73
|
Down::ChunkedIO.new(
|
|
70
74
|
chunks: enum_for(:stream_body, response),
|
|
@@ -81,27 +85,27 @@ module Down
|
|
|
81
85
|
|
|
82
86
|
private
|
|
83
87
|
|
|
84
|
-
def request(url, method: @method,
|
|
85
|
-
response = send_request(method, url,
|
|
88
|
+
def request(url, method: @method, **, &)
|
|
89
|
+
response = send_request(method, url, **, &)
|
|
86
90
|
response_error!(response) unless response.status.success?
|
|
87
91
|
response
|
|
88
92
|
end
|
|
89
93
|
|
|
90
|
-
def send_request(method, url,
|
|
94
|
+
def send_request(method, url, **, &block)
|
|
91
95
|
uri = HTTP::URI.parse(url)
|
|
92
96
|
|
|
93
97
|
client = @client
|
|
94
98
|
client = client.basic_auth(user: uri.user, pass: uri.password) if uri.user || uri.password
|
|
95
99
|
client = block.call(client) if block
|
|
96
100
|
|
|
97
|
-
client.request(method, url,
|
|
101
|
+
client.request(method, url, **)
|
|
98
102
|
rescue => exception
|
|
99
103
|
request_error!(exception)
|
|
100
104
|
end
|
|
101
105
|
|
|
102
106
|
# Yields chunks of the response body to the block.
|
|
103
|
-
def stream_body(response, &
|
|
104
|
-
response.body.each(&
|
|
107
|
+
def stream_body(response, &)
|
|
108
|
+
response.body.each(&)
|
|
105
109
|
rescue => exception
|
|
106
110
|
request_error!(exception)
|
|
107
111
|
ensure
|
|
@@ -123,7 +127,7 @@ module Down
|
|
|
123
127
|
# Re-raise HTTP.rb exceptions as Down::Error exceptions.
|
|
124
128
|
def request_error!(exception)
|
|
125
129
|
case exception
|
|
126
|
-
when HTTP::Request::UnsupportedSchemeError, Addressable::URI::InvalidURIError
|
|
130
|
+
when HTTP::Request::UnsupportedSchemeError, Addressable::URI::InvalidURIError, *invalid_url_errors
|
|
127
131
|
raise Down::InvalidUrl, exception.message
|
|
128
132
|
when HTTP::ConnectionError
|
|
129
133
|
raise Down::ConnectionError, exception.message
|
|
@@ -138,6 +142,10 @@ module Down
|
|
|
138
142
|
end
|
|
139
143
|
end
|
|
140
144
|
|
|
145
|
+
def invalid_url_errors
|
|
146
|
+
defined?(HTTP::URI::InvalidError) ? [HTTP::URI::InvalidError] : []
|
|
147
|
+
end
|
|
148
|
+
|
|
141
149
|
# Defines some additional attributes for the returned Tempfile.
|
|
142
150
|
module DownloadedFile
|
|
143
151
|
attr_accessor :url, :headers
|
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
|
-
# Downlods the remote file to disk. Accepts HTTPX options via a hash or a
|
|
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,
|
|
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
|
|
@@ -54,13 +46,13 @@ module Down
|
|
|
54
46
|
end
|
|
55
47
|
|
|
56
48
|
extname = extension ? ".#{extension}" : File.extname(response.uri.path)
|
|
57
|
-
tempfile = Tempfile.new(["down-http", extname], binmode: true)
|
|
49
|
+
tempfile = Tempfile.new([tempfile_name || "down-http", extname], binmode: true)
|
|
58
50
|
|
|
59
51
|
stream_body(response) do |chunk|
|
|
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
|
-
# downloads the response body on-demand. Accepts
|
|
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: {
|
|
@@ -105,15 +96,23 @@ module Down
|
|
|
105
96
|
|
|
106
97
|
# Yields chunks of the response body to the block.
|
|
107
98
|
def stream_body(response, &block)
|
|
108
|
-
response.each
|
|
99
|
+
response.each do |chunk|
|
|
100
|
+
next if (300..399).include?(response.status)
|
|
101
|
+
|
|
102
|
+
block.call(chunk)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
if (300..399).include?(response.status)
|
|
106
|
+
raise Down::TooManyRedirects, "too many redirects"
|
|
107
|
+
end
|
|
109
108
|
rescue => exception
|
|
110
109
|
request_error!(exception)
|
|
111
110
|
end
|
|
112
111
|
|
|
113
|
-
def request(
|
|
114
|
-
response = send_request(
|
|
112
|
+
def request(url, method: @method, **, &)
|
|
113
|
+
response = send_request(method, url, **, &)
|
|
115
114
|
response.raise_for_status
|
|
116
|
-
response_error!(response) unless (200..
|
|
115
|
+
response_error!(response) unless (200..399).include?(response.status)
|
|
117
116
|
response
|
|
118
117
|
rescue HTTPX::HTTPError
|
|
119
118
|
response_error!(response)
|
|
@@ -121,7 +120,7 @@ module Down
|
|
|
121
120
|
request_error!(error)
|
|
122
121
|
end
|
|
123
122
|
|
|
124
|
-
def send_request(
|
|
123
|
+
def send_request(method, url, **, &block)
|
|
125
124
|
uri = URI(url)
|
|
126
125
|
client = @client
|
|
127
126
|
if uri.user || uri.password
|
|
@@ -130,7 +129,7 @@ module Down
|
|
|
130
129
|
end
|
|
131
130
|
client = block.call(client) if block
|
|
132
131
|
|
|
133
|
-
client.request(method.to_s.upcase, uri, stream: true, **
|
|
132
|
+
client.request(method.to_s.upcase, uri, stream: true, **)
|
|
134
133
|
rescue => exception
|
|
135
134
|
request_error!(exception)
|
|
136
135
|
end
|
|
@@ -140,7 +139,6 @@ module Down
|
|
|
140
139
|
args = [response.status.to_s, response]
|
|
141
140
|
|
|
142
141
|
case response.status
|
|
143
|
-
when 300..399 then raise Down::TooManyRedirects, "too many redirects"
|
|
144
142
|
when 404 then raise Down::NotFound.new(*args)
|
|
145
143
|
when 400..499 then raise Down::ClientError.new(*args)
|
|
146
144
|
when 500..599 then raise Down::ServerError.new(*args)
|
data/lib/down/net_http.rb
CHANGED
|
@@ -17,30 +17,33 @@ module Down
|
|
|
17
17
|
addressable_uri.normalize.to_s
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
DEFAULT_OPTIONS = {
|
|
21
|
+
headers: { "User-Agent" => "Down/#{Down::VERSION}" }.freeze,
|
|
22
|
+
max_redirects: 2,
|
|
23
|
+
open_timeout: 30,
|
|
24
|
+
read_timeout: 30,
|
|
25
|
+
uri_normalizer: URI_NORMALIZER,
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
20
28
|
# 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)
|
|
29
|
+
def initialize(...)
|
|
30
|
+
@options = merge_options(DEFAULT_OPTIONS, ...)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Downloads a remote file to disk using open-uri. Accepts any open-uri
|
|
32
34
|
# options, and a few more.
|
|
33
|
-
def download(url,
|
|
34
|
-
options = merge_options(@options,
|
|
35
|
+
def download(url, ...)
|
|
36
|
+
options = merge_options(@options, ...)
|
|
35
37
|
|
|
36
38
|
max_size = options.delete(:max_size)
|
|
37
|
-
max_redirects = options.delete(:max_redirects)
|
|
38
39
|
progress_proc = options.delete(:progress_proc)
|
|
39
40
|
content_length_proc = options.delete(:content_length_proc)
|
|
41
|
+
proxy = options.delete(:proxy)
|
|
40
42
|
destination = options.delete(:destination)
|
|
41
43
|
headers = options.delete(:headers)
|
|
42
44
|
uri_normalizer = options.delete(:uri_normalizer)
|
|
43
45
|
extension = options.delete(:extension)
|
|
46
|
+
tempfile_name = options.delete(:tempfile_name)
|
|
44
47
|
|
|
45
48
|
# Use open-uri's :content_lenth_proc or :progress_proc to raise an
|
|
46
49
|
# exception early if the file is too large.
|
|
@@ -52,20 +55,20 @@ module Down
|
|
|
52
55
|
if size && max_size && size > max_size
|
|
53
56
|
raise Down::TooLarge, "file is too large (#{size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
|
|
54
57
|
end
|
|
55
|
-
content_length_proc
|
|
58
|
+
content_length_proc&.call(size)
|
|
56
59
|
},
|
|
57
60
|
progress_proc: proc { |current_size|
|
|
58
61
|
if max_size && current_size > max_size
|
|
59
62
|
raise Down::TooLarge, "file is too large (#{current_size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
|
|
60
63
|
end
|
|
61
|
-
progress_proc
|
|
64
|
+
progress_proc&.call(current_size)
|
|
62
65
|
},
|
|
63
66
|
redirect: false,
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
# Handle basic authentication in the :proxy option.
|
|
67
|
-
if
|
|
68
|
-
proxy = URI(
|
|
70
|
+
if proxy
|
|
71
|
+
proxy = URI(proxy)
|
|
69
72
|
user = proxy.user
|
|
70
73
|
password = proxy.password
|
|
71
74
|
|
|
@@ -79,10 +82,9 @@ module Down
|
|
|
79
82
|
end
|
|
80
83
|
end
|
|
81
84
|
|
|
82
|
-
open_uri_options.merge!(options)
|
|
83
|
-
open_uri_options.merge!(headers)
|
|
85
|
+
open_uri_options.merge!(options, headers)
|
|
84
86
|
|
|
85
|
-
uri = ensure_uri(normalize_uri(url, uri_normalizer:
|
|
87
|
+
uri = ensure_uri(normalize_uri(url, uri_normalizer:))
|
|
86
88
|
|
|
87
89
|
# Handle basic authentication in the remote URL.
|
|
88
90
|
if uri.user || uri.password
|
|
@@ -91,11 +93,11 @@ module Down
|
|
|
91
93
|
uri.password = nil
|
|
92
94
|
end
|
|
93
95
|
|
|
94
|
-
open_uri_file = open_uri(uri, open_uri_options
|
|
96
|
+
open_uri_file = open_uri(uri, **open_uri_options)
|
|
95
97
|
|
|
96
98
|
# Handle the fact that open-uri returns StringIOs for small files.
|
|
97
99
|
extname = extension ? ".#{extension}" : File.extname(open_uri_file.base_uri.path)
|
|
98
|
-
tempfile = ensure_tempfile(open_uri_file, extname)
|
|
100
|
+
tempfile = ensure_tempfile(open_uri_file, extname, tempfile_name)
|
|
99
101
|
OpenURI::Meta.init tempfile, open_uri_file # add back open-uri methods
|
|
100
102
|
tempfile.extend Down::NetHttp::DownloadedFile
|
|
101
103
|
|
|
@@ -104,17 +106,16 @@ module Down
|
|
|
104
106
|
|
|
105
107
|
# Starts retrieving the remote file using Net::HTTP and returns an IO-like
|
|
106
108
|
# object which downloads the response body on-demand.
|
|
107
|
-
def open(url,
|
|
108
|
-
options = merge_options(@options,
|
|
109
|
+
def open(url, ...)
|
|
110
|
+
options = merge_options(@options, ...)
|
|
109
111
|
|
|
110
|
-
max_redirects = options.delete(:max_redirects)
|
|
111
112
|
uri_normalizer = options.delete(:uri_normalizer)
|
|
112
113
|
|
|
113
|
-
uri = ensure_uri(normalize_uri(url, uri_normalizer:
|
|
114
|
+
uri = ensure_uri(normalize_uri(url, uri_normalizer:))
|
|
114
115
|
|
|
115
116
|
# Create a Fiber that halts when response headers are received.
|
|
116
117
|
request = Fiber.new do
|
|
117
|
-
net_http_request(uri, options
|
|
118
|
+
net_http_request(uri, **options) do |response|
|
|
118
119
|
Fiber.yield response
|
|
119
120
|
end
|
|
120
121
|
end
|
|
@@ -126,7 +127,7 @@ module Down
|
|
|
126
127
|
# Build an IO-like object that will retrieve response body on-demand.
|
|
127
128
|
Down::ChunkedIO.new(
|
|
128
129
|
chunks: enum_for(:stream_body, response),
|
|
129
|
-
size: response["Content-Length"]
|
|
130
|
+
size: response["Content-Length"]&.to_i,
|
|
130
131
|
encoding: response.type_params["charset"],
|
|
131
132
|
rewindable: options.fetch(:rewindable, true),
|
|
132
133
|
on_close: -> { request.resume }, # close HTTP connnection
|
|
@@ -141,10 +142,10 @@ module Down
|
|
|
141
142
|
private
|
|
142
143
|
|
|
143
144
|
# Calls open-uri's URI::HTTP#open method. Additionally handles redirects.
|
|
144
|
-
def open_uri(uri,
|
|
145
|
+
def open_uri(uri, max_redirects:, auth_on_redirect: false, **options)
|
|
145
146
|
uri.open(options)
|
|
146
147
|
rescue OpenURI::HTTPRedirect => exception
|
|
147
|
-
raise Down::TooManyRedirects, "too many redirects" if
|
|
148
|
+
raise Down::TooManyRedirects, "too many redirects" if max_redirects == 0
|
|
148
149
|
|
|
149
150
|
# fail if redirect URI scheme is not http or https
|
|
150
151
|
begin
|
|
@@ -152,9 +153,13 @@ module Down
|
|
|
152
153
|
rescue Down::InvalidUrl
|
|
153
154
|
response = rebuild_response_from_open_uri_exception(exception)
|
|
154
155
|
|
|
155
|
-
raise ResponseError.new("Invalid Redirect URI: #{exception.uri}", response:
|
|
156
|
+
raise ResponseError.new("Invalid Redirect URI: #{exception.uri}", response:)
|
|
156
157
|
end
|
|
157
158
|
|
|
159
|
+
# do not leak credentials on redirect
|
|
160
|
+
options.delete("Authorization") unless auth_on_redirect
|
|
161
|
+
options.delete(:http_basic_authentication) unless auth_on_redirect
|
|
162
|
+
|
|
158
163
|
# forward cookies on the redirect
|
|
159
164
|
if !exception.io.meta["set-cookie"].to_s.empty?
|
|
160
165
|
options["Cookie"] ||= ''
|
|
@@ -164,14 +169,14 @@ module Down
|
|
|
164
169
|
options["Cookie"] = (old_cookies | new_cookies).join(';')
|
|
165
170
|
end
|
|
166
171
|
|
|
167
|
-
|
|
172
|
+
max_redirects -= 1
|
|
168
173
|
retry
|
|
169
174
|
rescue OpenURI::HTTPError => exception
|
|
170
175
|
response = rebuild_response_from_open_uri_exception(exception)
|
|
171
176
|
|
|
172
177
|
# open-uri attempts to parse the redirect URI, so we re-raise that exception
|
|
173
178
|
if exception.message.include?("(Invalid Location URI)")
|
|
174
|
-
raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response:
|
|
179
|
+
raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response:)
|
|
175
180
|
end
|
|
176
181
|
|
|
177
182
|
response_error!(response)
|
|
@@ -182,8 +187,8 @@ module Down
|
|
|
182
187
|
# Converts the given IO into a Tempfile if it isn't one already (open-uri
|
|
183
188
|
# returns a StringIO when there is less than 10KB of content), and gives
|
|
184
189
|
# it the specified file extension.
|
|
185
|
-
def ensure_tempfile(io, extension)
|
|
186
|
-
tempfile = Tempfile.new(["down-net_http", extension], binmode: true)
|
|
190
|
+
def ensure_tempfile(io, extension, tempfile_name = nil)
|
|
191
|
+
tempfile = Tempfile.new([tempfile_name || "down-net_http", extension], binmode: true)
|
|
187
192
|
|
|
188
193
|
if io.is_a?(Tempfile)
|
|
189
194
|
# Windows requires file descriptors to be closed before files are moved
|
|
@@ -200,8 +205,8 @@ module Down
|
|
|
200
205
|
end
|
|
201
206
|
|
|
202
207
|
# Makes a Net::HTTP request and follows redirects.
|
|
203
|
-
def net_http_request(uri, options,
|
|
204
|
-
http, request = create_net_http(uri, options)
|
|
208
|
+
def net_http_request(uri, max_redirects:, auth_on_redirect: false, **options, &)
|
|
209
|
+
http, request = create_net_http(uri, **options)
|
|
205
210
|
|
|
206
211
|
begin
|
|
207
212
|
response = http.start do
|
|
@@ -222,28 +227,38 @@ module Down
|
|
|
222
227
|
if response.is_a?(Net::HTTPNotModified)
|
|
223
228
|
raise Down::NotModified
|
|
224
229
|
elsif response.is_a?(Net::HTTPRedirection)
|
|
225
|
-
raise Down::TooManyRedirects if
|
|
230
|
+
raise Down::TooManyRedirects if max_redirects == 0
|
|
226
231
|
|
|
227
232
|
# fail if redirect URI is not a valid http or https URL
|
|
228
233
|
begin
|
|
229
234
|
location = ensure_uri(response["Location"], allow_relative: true)
|
|
230
235
|
rescue Down::InvalidUrl
|
|
231
|
-
raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response:
|
|
236
|
+
raise ResponseError.new("Invalid Redirect URI: #{response["Location"]}", response:)
|
|
232
237
|
end
|
|
233
238
|
|
|
239
|
+
# do not leak credentials on redirect
|
|
240
|
+
options[:headers].delete("Authorization") unless auth_on_redirect
|
|
241
|
+
|
|
234
242
|
# handle relative redirects
|
|
235
|
-
|
|
243
|
+
if location.relative?
|
|
244
|
+
location = uri + location
|
|
245
|
+
uri.user = nil unless auth_on_redirect
|
|
246
|
+
uri.password = nil unless auth_on_redirect
|
|
247
|
+
elsif auth_on_redirect && (uri.user || uri.password)
|
|
248
|
+
# for absolute redirects, carry over URI credentials since the new location won't have them
|
|
249
|
+
options[:http_basic_authentication] ||= [uri.user, uri.password]
|
|
250
|
+
end
|
|
236
251
|
|
|
237
|
-
net_http_request(location, options,
|
|
252
|
+
net_http_request(location, **options, max_redirects: max_redirects - 1, auth_on_redirect:, &)
|
|
238
253
|
end
|
|
239
254
|
end
|
|
240
255
|
|
|
241
256
|
# Build a Net::HTTP object for making a request.
|
|
242
|
-
def create_net_http(uri,
|
|
257
|
+
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, **)
|
|
243
258
|
http_class = Net::HTTP
|
|
244
259
|
|
|
245
|
-
if
|
|
246
|
-
proxy = URI(
|
|
260
|
+
if proxy
|
|
261
|
+
proxy = URI(proxy)
|
|
247
262
|
http_class = Net::HTTP::Proxy(proxy.hostname, proxy.port, proxy.user, proxy.password)
|
|
248
263
|
end
|
|
249
264
|
|
|
@@ -252,10 +267,10 @@ module Down
|
|
|
252
267
|
# Handle SSL parameters (taken from the open-uri implementation).
|
|
253
268
|
if uri.is_a?(URI::HTTPS)
|
|
254
269
|
http.use_ssl = true
|
|
255
|
-
http.verify_mode =
|
|
270
|
+
http.verify_mode = ssl_verify_mode || OpenSSL::SSL::VERIFY_PEER
|
|
256
271
|
store = OpenSSL::X509::Store.new
|
|
257
|
-
if
|
|
258
|
-
Array(
|
|
272
|
+
if ssl_ca_cert
|
|
273
|
+
Array(ssl_ca_cert).each do |cert|
|
|
259
274
|
File.directory?(cert) ? store.add_path(cert) : store.add_file(cert)
|
|
260
275
|
end
|
|
261
276
|
else
|
|
@@ -264,23 +279,20 @@ module Down
|
|
|
264
279
|
http.cert_store = store
|
|
265
280
|
end
|
|
266
281
|
|
|
267
|
-
http.read_timeout =
|
|
268
|
-
http.open_timeout =
|
|
269
|
-
|
|
270
|
-
headers = options[:headers].to_h
|
|
271
|
-
headers["Accept-Encoding"] = "" # Net::HTTP's inflater causes FiberErrors
|
|
282
|
+
http.read_timeout = read_timeout
|
|
283
|
+
http.open_timeout = open_timeout
|
|
272
284
|
|
|
273
|
-
get = Net::HTTP::Get.new(uri, headers)
|
|
285
|
+
get = Net::HTTP::Get.new(uri, headers.to_h)
|
|
274
286
|
|
|
275
|
-
user, password =
|
|
287
|
+
user, password = http_basic_authentication || [uri.user, uri.password]
|
|
276
288
|
get.basic_auth(user, password) if user || password
|
|
277
289
|
|
|
278
290
|
[http, get]
|
|
279
291
|
end
|
|
280
292
|
|
|
281
293
|
# Yields chunks of the response body to the block.
|
|
282
|
-
def stream_body(response, &
|
|
283
|
-
response.read_body(&
|
|
294
|
+
def stream_body(response, &)
|
|
295
|
+
response.read_body(&)
|
|
284
296
|
rescue => exception
|
|
285
297
|
request_error!(exception)
|
|
286
298
|
end
|
|
@@ -370,6 +382,8 @@ module Down
|
|
|
370
382
|
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", ... }, ...)`)
|
|
371
383
|
new_options[:headers] = new_options.select { |key, value| key.is_a?(String) }
|
|
372
384
|
new_options.reject! { |key, value| key.is_a?(String) }
|
|
385
|
+
else
|
|
386
|
+
new_options[:headers] ||= {}
|
|
373
387
|
end
|
|
374
388
|
|
|
375
389
|
options.merge(new_options) do |key, value1, value2|
|
data/lib/down/utils.rb
CHANGED
data/lib/down/version.rb
CHANGED
data/lib/down/wget.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,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: down
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Janko Marohnić
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: addressable
|
|
@@ -24,20 +23,34 @@ dependencies:
|
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '2.8'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: base64
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.3'
|
|
27
40
|
- !ruby/object:Gem::Dependency
|
|
28
41
|
name: minitest
|
|
29
42
|
requirement: !ruby/object:Gem::Requirement
|
|
30
43
|
requirements:
|
|
31
44
|
- - "~>"
|
|
32
45
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
46
|
+
version: '6.0'
|
|
34
47
|
type: :development
|
|
35
48
|
prerelease: false
|
|
36
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
50
|
requirements:
|
|
38
51
|
- - "~>"
|
|
39
52
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
53
|
+
version: '6.0'
|
|
41
54
|
- !ruby/object:Gem::Dependency
|
|
42
55
|
name: mocha
|
|
43
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,50 +85,36 @@ dependencies:
|
|
|
72
85
|
requirements:
|
|
73
86
|
- - "~>"
|
|
74
87
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
88
|
+
version: '1.0'
|
|
76
89
|
- - "<"
|
|
77
90
|
- !ruby/object:Gem::Version
|
|
78
|
-
version:
|
|
91
|
+
version: 1.4.4
|
|
79
92
|
type: :development
|
|
80
93
|
prerelease: false
|
|
81
94
|
version_requirements: !ruby/object:Gem::Requirement
|
|
82
95
|
requirements:
|
|
83
96
|
- - "~>"
|
|
84
97
|
- !ruby/object:Gem::Version
|
|
85
|
-
version: '0'
|
|
98
|
+
version: '1.0'
|
|
86
99
|
- - "<"
|
|
87
100
|
- !ruby/object:Gem::Version
|
|
88
|
-
version:
|
|
101
|
+
version: 1.4.4
|
|
89
102
|
- !ruby/object:Gem::Dependency
|
|
90
103
|
name: http
|
|
91
104
|
requirement: !ruby/object:Gem::Requirement
|
|
92
105
|
requirements:
|
|
93
106
|
- - "~>"
|
|
94
107
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '
|
|
108
|
+
version: '6.0'
|
|
96
109
|
type: :development
|
|
97
110
|
prerelease: false
|
|
98
111
|
version_requirements: !ruby/object:Gem::Requirement
|
|
99
112
|
requirements:
|
|
100
113
|
- - "~>"
|
|
101
114
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
103
|
-
- !ruby/object:Gem::Dependency
|
|
104
|
-
name: posix-spawn
|
|
105
|
-
requirement: !ruby/object:Gem::Requirement
|
|
106
|
-
requirements:
|
|
107
|
-
- - ">="
|
|
108
|
-
- !ruby/object:Gem::Version
|
|
109
|
-
version: '0'
|
|
110
|
-
type: :development
|
|
111
|
-
prerelease: false
|
|
112
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
113
|
-
requirements:
|
|
114
|
-
- - ">="
|
|
115
|
-
- !ruby/object:Gem::Version
|
|
116
|
-
version: '0'
|
|
115
|
+
version: '6.0'
|
|
117
116
|
- !ruby/object:Gem::Dependency
|
|
118
|
-
name:
|
|
117
|
+
name: warning
|
|
119
118
|
requirement: !ruby/object:Gem::Requirement
|
|
120
119
|
requirements:
|
|
121
120
|
- - ">="
|
|
@@ -129,7 +128,7 @@ dependencies:
|
|
|
129
128
|
- !ruby/object:Gem::Version
|
|
130
129
|
version: '0'
|
|
131
130
|
- !ruby/object:Gem::Dependency
|
|
132
|
-
name:
|
|
131
|
+
name: csv
|
|
133
132
|
requirement: !ruby/object:Gem::Requirement
|
|
134
133
|
requirements:
|
|
135
134
|
- - ">="
|
|
@@ -142,7 +141,6 @@ dependencies:
|
|
|
142
141
|
- - ">="
|
|
143
142
|
- !ruby/object:Gem::Version
|
|
144
143
|
version: '0'
|
|
145
|
-
description:
|
|
146
144
|
email:
|
|
147
145
|
- janko.marohnic@gmail.com
|
|
148
146
|
executables: []
|
|
@@ -167,7 +165,6 @@ homepage: https://github.com/janko/down
|
|
|
167
165
|
licenses:
|
|
168
166
|
- MIT
|
|
169
167
|
metadata: {}
|
|
170
|
-
post_install_message:
|
|
171
168
|
rdoc_options: []
|
|
172
169
|
require_paths:
|
|
173
170
|
- lib
|
|
@@ -175,15 +172,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
175
172
|
requirements:
|
|
176
173
|
- - ">="
|
|
177
174
|
- !ruby/object:Gem::Version
|
|
178
|
-
version: '2
|
|
175
|
+
version: '3.2'
|
|
179
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
177
|
requirements:
|
|
181
178
|
- - ">="
|
|
182
179
|
- !ruby/object:Gem::Version
|
|
183
180
|
version: '0'
|
|
184
181
|
requirements: []
|
|
185
|
-
rubygems_version:
|
|
186
|
-
signing_key:
|
|
182
|
+
rubygems_version: 4.0.13
|
|
187
183
|
specification_version: 4
|
|
188
|
-
summary: Robust streaming downloads using Net::HTTP,
|
|
184
|
+
summary: Robust streaming downloads using Net::HTTP, http.rb or HTTPX.
|
|
189
185
|
test_files: []
|