down 5.3.1 → 5.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69943db91082a470760e9f809f3cd96f05bd57c4f76869522cdb996215f93c48
4
- data.tar.gz: 4f7b62080a645c26d5a4ace80e723d707ad6fcdbeaf845f247d6ed4977d96c89
3
+ metadata.gz: 5e25eaeebd866fe407bcb39282937568f1c5489feb0b3ac479d92c2b19d8920c
4
+ data.tar.gz: 151e9d28039d7698a32e25ff7b1dae39c32e871ed295809f11a586c5f6221bcc
5
5
  SHA512:
6
- metadata.gz: b6a0a24ade6b6f67bf7e6c91d8aff07e3d18a7b8d67521a78ec0da2516d6518c62d45a0d1ef77176523f64d4a83862ca289aa730c05eaeb6e51e655967c396e3
7
- data.tar.gz: dafabbb4c78cd47d40cd7b74c1df15270ebc2485dccc52e754879cfe0e844edb3cd1322c7a1f4bc81075f81fd980215b8e8189d4db664537273d473d109dbd55
6
+ metadata.gz: 3ee68adf94333adcb92f9453ec0c65e23437458e6586f0078471ba8b5691d3ca0b5e377656a16fb6750ee585f255cd5af38218dbf873fd6b7e5e9ec1997a8997
7
+ data.tar.gz: 49f80f1b039ba67e86e1515aac927382249f2bdca33cb8d71e8be8753d82d6f69c56d010be4521c2e661a51662adb698478348949fc01ac22e1419212cfc640c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 5.4.1 (2023-05-20)
2
+
3
+ * Handle additional params in `Content-Disposition` header (@janko)
4
+
5
+ * Add ability to detect response URI when using net/http (@aglushkov)
6
+
7
+ * Avoid deprecation warning in HTTPX (@ollym)
8
+
9
+ * Handle unknown response status in net/http backend (@janko)
10
+
11
+ ## 5.4.0 (2022-12-26)
12
+
13
+ * Add new HTTPX backend, which supports HTTP/2 protocol among other features (@HoneyryderChuck)
14
+
1
15
  ## 5.3.1 (2022-03-25)
2
16
 
3
17
  * Correctly split cookie headers on `;` instead of `,` when forwarding them on redirects (@ermolaev)
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] or `wget` as the backend
5
- HTTP library.
4
+ files. It can use [open-uri] + `Net::HTTP`, [http.rb], [HTTPX], or `wget` as
5
+ the backend HTTP library.
6
6
 
7
7
  ## Installation
8
8
 
@@ -234,6 +234,7 @@ The following backends are available:
234
234
 
235
235
  * [Down::NetHttp](#downnethttp) (default)
236
236
  * [Down::Http](#downhttp)
237
+ * [Down::Httpx](#downhttpx)
237
238
  * [Down::Wget](#downwget)
238
239
 
239
240
  You can use the backend directly:
@@ -442,6 +443,28 @@ down = Down::Http.new(method: :post)
442
443
  down.download("http://example.org/image.jpg")
443
444
  ```
444
445
 
446
+ ### Down::Httpx
447
+
448
+ The `Down::Httpx` backend implements downloads using the [HTTPX] gem, which
449
+ supports the HTTP/2 protocol, in addition to many other features.
450
+
451
+ ```rb
452
+ gem "down", "~> 5.0"
453
+ gem "httpx", "~> 0.22"
454
+ ```
455
+ ```rb
456
+ require "down/httpx"
457
+
458
+ tempfile = Down::Httpx.download("http://nature.com/forest.jpg")
459
+ tempfile #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20150925-55456-z7vxqz.jpg>
460
+
461
+ io = Down::Httpx.open("http://nature.com/forest.jpg")
462
+ io #=> #<Down::ChunkedIO ...>
463
+ ```
464
+
465
+ It's implemented in much of the same way as `Down::Http`, so be sure to check
466
+ its docs for ways to pass additional options.
467
+
445
468
  ### Down::Wget (experimental)
446
469
 
447
470
  The `Down::Wget` backend implements downloads using the `wget` command line
@@ -488,26 +511,21 @@ wget.download("http://nature.com/forest.jpg")
488
511
  wget.open("http://nature.com/forest.jpg")
489
512
  ```
490
513
 
491
- ## Supported Ruby versions
514
+ ## Development
492
515
 
493
- * MRI 2.3
494
- * MRI 2.4
495
- * MRI 2.5
496
- * MRI 2.6
497
- * MRI 2.7
498
- * JRuby 9.2
516
+ Tests require that a [httpbin] server is running locally, which you can do via Docker:
499
517
 
500
- ## Development
518
+ ```sh
519
+ $ docker pull kennethreitz/httpbin
520
+ $ docker run -p 80:80 kennethreitz/httpbin
521
+ ```
501
522
 
502
- You can run tests with
523
+ Then you can run tests:
503
524
 
504
525
  ```
505
526
  $ bundle exec rake test
506
527
  ```
507
528
 
508
- The test suite pulls and runs [kennethreitz/httpbin] as a Docker container, so
509
- you'll need to have Docker installed and running.
510
-
511
529
  ## License
512
530
 
513
531
  [MIT](LICENSE.txt)
@@ -515,5 +533,6 @@ you'll need to have Docker installed and running.
515
533
  [open-uri]: http://ruby-doc.org/stdlib-2.3.0/libdoc/open-uri/rdoc/OpenURI.html
516
534
  [Net::HTTP]: https://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html
517
535
  [http.rb]: https://github.com/httprb/http
536
+ [HTTPX]: https://github.com/HoneyryderChuck/httpx
518
537
  [Addressable::URI]: https://github.com/sporkmonger/addressable
519
- [kennethreitz/httpbin]: https://github.com/kennethreitz/httpbin
538
+ [httpbin]: https://github.com/postmanlabs/httpbin
data/down.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "minitest", "~> 5.8"
21
21
  spec.add_development_dependency "mocha", "~> 1.5"
22
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "httpx", "~> 0.22", ">= 0.22.2"
23
24
  # http 5.0 drop support of ruby 2.3 and 2.4. We still support those versions.
24
25
  if RUBY_VERSION >= "2.5"
25
26
  spec.add_development_dependency "http", "~> 5.0"
@@ -28,6 +29,5 @@ Gem::Specification.new do |spec|
28
29
  end
29
30
  spec.add_development_dependency "posix-spawn" unless RUBY_ENGINE == "jruby"
30
31
  spec.add_development_dependency "http_parser.rb" unless RUBY_ENGINE == "jruby"
31
- spec.add_development_dependency "docker-api"
32
32
  spec.add_development_dependency "warning" if RUBY_VERSION >= "2.4"
33
33
  end
data/lib/down/http.rb CHANGED
@@ -52,7 +52,7 @@ module Down
52
52
 
53
53
  tempfile.extend Down::Http::DownloadedFile
54
54
  tempfile.url = response.uri.to_s
55
- tempfile.headers = response.headers.to_h
55
+ tempfile.headers = normalize_headers(response.headers.to_h)
56
56
 
57
57
  download_result(tempfile, destination)
58
58
  rescue
data/lib/down/httpx.rb ADDED
@@ -0,0 +1,175 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "uri"
4
+ require "tempfile"
5
+ require "httpx"
6
+
7
+ require "down/backend"
8
+
9
+
10
+ module Down
11
+ # Provides streaming downloads implemented with HTTPX.
12
+ class Httpx < Backend
13
+ # Initializes the backend
14
+
15
+ USER_AGENT = "Down/#{Down::VERSION}"
16
+
17
+ def initialize(**options, &block)
18
+ @method = options.delete(:method) || :get
19
+ headers = options.delete(:headers) || {}
20
+ @client = HTTPX
21
+ .plugin(:follow_redirects, max_redirects: 2)
22
+ .plugin(:basic_authentication)
23
+ .plugin(:stream)
24
+ .with(
25
+ headers: { "user-agent": USER_AGENT }.merge(headers),
26
+ timeout: { connect_timeout: 30, write_timeout: 30, read_timeout: 30 },
27
+ **options
28
+ )
29
+
30
+ @client = block.call(@client) if block
31
+ end
32
+
33
+
34
+ # Downlods the remote file to disk. Accepts HTTPX options via a hash or a
35
+ # block, and some additional options as well.
36
+ def download(url, max_size: nil, progress_proc: nil, content_length_proc: nil, destination: nil, extension: nil, **options, &block)
37
+ client = @client
38
+
39
+ response = request(client, url, **options, &block)
40
+
41
+ content_length = nil
42
+
43
+ if response.headers.key?("content-length")
44
+ content_length = response.headers["content-length"].to_i
45
+
46
+ content_length_proc.call(content_length) if content_length_proc
47
+
48
+ if max_size && content_length > max_size
49
+ response.close
50
+ raise Down::TooLarge, "file is too large (#{content_length/1024/1024}MB, max is #{max_size/1024/1024}MB)"
51
+ end
52
+ end
53
+
54
+ extname = extension ? ".#{extension}" : File.extname(response.uri.path)
55
+ tempfile = Tempfile.new(["down-http", extname], binmode: true)
56
+
57
+ stream_body(response) do |chunk|
58
+ tempfile.write(chunk)
59
+ chunk.clear # deallocate string
60
+
61
+ progress_proc.call(tempfile.size) if progress_proc
62
+
63
+ if max_size && tempfile.size > max_size
64
+ raise Down::TooLarge, "file is too large (#{tempfile.size/1024/1024}MB, max is #{max_size/1024/1024}MB)"
65
+ end
66
+ end
67
+
68
+ tempfile.open # flush written content
69
+
70
+ tempfile.extend DownloadedFile
71
+ tempfile.url = response.uri.to_s
72
+ tempfile.headers = normalize_headers(response.headers.to_h)
73
+ tempfile.content_type = response.content_type.mime_type
74
+ tempfile.charset = response.body.encoding
75
+
76
+ download_result(tempfile, destination)
77
+ rescue
78
+ tempfile.close! if tempfile
79
+ raise
80
+ end
81
+
82
+ # Starts retrieving the remote file and returns an IO-like object which
83
+ # downloads the response body on-demand. Accepts HTTP.rb options via a hash
84
+ # or a block.
85
+ def open(url, rewindable: true, **options, &block)
86
+ response = request(@client, url, stream: true, **options, &block)
87
+ size = response.headers["content-length"]
88
+ size = size.to_i if size
89
+ Down::ChunkedIO.new(
90
+ chunks: enum_for(:stream_body, response),
91
+ size: size,
92
+ encoding: response.body.encoding,
93
+ rewindable: rewindable,
94
+ data: {
95
+ status: response.status,
96
+ headers: normalize_headers(response.headers.to_h),
97
+ response: response
98
+ },
99
+ )
100
+ end
101
+
102
+ private
103
+
104
+ # Yields chunks of the response body to the block.
105
+ def stream_body(response, &block)
106
+ response.each(&block)
107
+ rescue => exception
108
+ request_error!(exception)
109
+ end
110
+
111
+ def request(client, url, method: @method, **options, &block)
112
+ response = send_request(client, method, url, **options, &block)
113
+ response.raise_for_status
114
+ response_error!(response) unless (200..299).include?(response.status)
115
+ response
116
+ rescue HTTPX::HTTPError
117
+ response_error!(response)
118
+ rescue => error
119
+ request_error!(error)
120
+ end
121
+
122
+ def send_request(client, method, url, **options, &block)
123
+ uri = URI(url)
124
+ client = @client
125
+ if uri.user || uri.password
126
+ client = client.basic_auth(uri.user, uri.password)
127
+ uri.user = uri.password = nil
128
+ end
129
+ client = block.call(client) if block
130
+
131
+ client.request(method.to_s.upcase, uri, stream: true, **options)
132
+ rescue => exception
133
+ request_error!(exception)
134
+ end
135
+
136
+ # Raises non-sucessful response as a Down::ResponseError.
137
+ def response_error!(response)
138
+ args = [response.status.to_s, response]
139
+
140
+ case response.status
141
+ when 300..399 then raise Down::TooManyRedirects, "too many redirects"
142
+ when 404 then raise Down::NotFound.new(*args)
143
+ when 400..499 then raise Down::ClientError.new(*args)
144
+ when 500..599 then raise Down::ServerError.new(*args)
145
+ else raise Down::ResponseError.new(*args)
146
+ end
147
+ end
148
+
149
+ # Re-raise HTTP.rb exceptions as Down::Error exceptions.
150
+ def request_error!(exception)
151
+ case exception
152
+ when URI::Error, HTTPX::UnsupportedSchemeError
153
+ raise Down::InvalidUrl, exception.message
154
+ when HTTPX::ConnectionError
155
+ raise Down::ConnectionError, exception.message
156
+ when HTTPX::TimeoutError
157
+ raise Down::TimeoutError, exception.message
158
+ when OpenSSL::SSL::SSLError
159
+ raise Down::SSLError, exception.message
160
+ else
161
+ raise exception
162
+ end
163
+ end
164
+
165
+ # Defines some additional attributes for the returned Tempfile.
166
+ module DownloadedFile
167
+ attr_accessor :url, :headers, :charset, :content_type
168
+
169
+ def original_filename
170
+ Utils.filename_from_content_disposition(headers["Content-Disposition"]) ||
171
+ Utils.filename_from_path(URI.parse(url).path)
172
+ end
173
+ end
174
+ end
175
+ end
data/lib/down/net_http.rb CHANGED
@@ -270,7 +270,7 @@ module Down
270
270
  headers = options[:headers].to_h
271
271
  headers["Accept-Encoding"] = "" # Net::HTTP's inflater causes FiberErrors
272
272
 
273
- get = Net::HTTP::Get.new(uri.request_uri, headers)
273
+ get = Net::HTTP::Get.new(uri, headers)
274
274
 
275
275
  user, password = options[:http_basic_authentication] || [uri.user, uri.password]
276
276
  get.basic_auth(user, password) if user || password
@@ -312,13 +312,14 @@ module Down
312
312
  # rebuild the Net::HTTP response object.
313
313
  def rebuild_response_from_open_uri_exception(exception)
314
314
  code, message = exception.io.status
315
+ message ||= "Unknown"
315
316
 
316
317
  response_class = Net::HTTPResponse::CODE_TO_OBJ.fetch(code) do |c|
317
318
  Net::HTTPResponse::CODE_CLASS_TO_OBJ.fetch(c[0]) do
318
319
  Net::HTTPUnknownResponse
319
320
  end
320
321
  end
321
- response = response_class.new(nil, code, message)
322
+ response = response_class.new(nil, code, message)
322
323
 
323
324
  exception.io.metas.each do |name, values|
324
325
  values.each { |value| response.add_field(name, value) }
data/lib/down/utils.rb CHANGED
@@ -11,7 +11,7 @@ module Down
11
11
  escaped_filename =
12
12
  content_disposition[/filename\*=UTF-8''(\S+)/, 1] ||
13
13
  content_disposition[/filename="([^"]*)"/, 1] ||
14
- content_disposition[/filename=(\S+)/, 1]
14
+ content_disposition[/filename=([^\s;]+)/, 1]
15
15
 
16
16
  filename = CGI.unescape(escaped_filename.to_s)
17
17
 
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.3.1"
4
+ VERSION = "5.4.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: down
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.1
4
+ version: 5.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-25 00:00:00.000000000 Z
11
+ date: 2023-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -67,35 +67,41 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: http
70
+ name: httpx
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '5.0'
75
+ version: '0.22'
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 0.22.2
76
79
  type: :development
77
80
  prerelease: false
78
81
  version_requirements: !ruby/object:Gem::Requirement
79
82
  requirements:
80
83
  - - "~>"
81
84
  - !ruby/object:Gem::Version
82
- version: '5.0'
85
+ version: '0.22'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 0.22.2
83
89
  - !ruby/object:Gem::Dependency
84
- name: posix-spawn
90
+ name: http
85
91
  requirement: !ruby/object:Gem::Requirement
86
92
  requirements:
87
- - - ">="
93
+ - - "~>"
88
94
  - !ruby/object:Gem::Version
89
- version: '0'
95
+ version: '5.0'
90
96
  type: :development
91
97
  prerelease: false
92
98
  version_requirements: !ruby/object:Gem::Requirement
93
99
  requirements:
94
- - - ">="
100
+ - - "~>"
95
101
  - !ruby/object:Gem::Version
96
- version: '0'
102
+ version: '5.0'
97
103
  - !ruby/object:Gem::Dependency
98
- name: http_parser.rb
104
+ name: posix-spawn
99
105
  requirement: !ruby/object:Gem::Requirement
100
106
  requirements:
101
107
  - - ">="
@@ -109,7 +115,7 @@ dependencies:
109
115
  - !ruby/object:Gem::Version
110
116
  version: '0'
111
117
  - !ruby/object:Gem::Dependency
112
- name: docker-api
118
+ name: http_parser.rb
113
119
  requirement: !ruby/object:Gem::Requirement
114
120
  requirements:
115
121
  - - ">="
@@ -152,6 +158,7 @@ files:
152
158
  - lib/down/chunked_io.rb
153
159
  - lib/down/errors.rb
154
160
  - lib/down/http.rb
161
+ - lib/down/httpx.rb
155
162
  - lib/down/net_http.rb
156
163
  - lib/down/utils.rb
157
164
  - lib/down/version.rb
@@ -175,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
182
  - !ruby/object:Gem::Version
176
183
  version: '0'
177
184
  requirements: []
178
- rubygems_version: 3.3.3
185
+ rubygems_version: 3.4.12
179
186
  signing_key:
180
187
  specification_version: 4
181
188
  summary: Robust streaming downloads using Net::HTTP, HTTP.rb or wget.