down 2.5.1 → 3.0.0

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.
@@ -0,0 +1,221 @@
1
+ require "open-uri"
2
+ require "net/http"
3
+
4
+ require "down/version"
5
+ require "down/chunked_io"
6
+ require "down/errors"
7
+
8
+ require "tempfile"
9
+ require "fileutils"
10
+ require "cgi"
11
+
12
+ module Down
13
+ module_function
14
+
15
+ def download(uri, options = {})
16
+ NetHttp.download(uri, options)
17
+ end
18
+
19
+ def open(uri, options = {})
20
+ NetHttp.open(uri, options)
21
+ end
22
+
23
+ def copy_to_tempfile(basename, io)
24
+ NetHttp.copy_to_tempfile(basename, io)
25
+ end
26
+
27
+ module NetHttp
28
+ module_function
29
+
30
+ def download(uri, options = {})
31
+ max_size = options.delete(:max_size)
32
+ max_redirects = options.delete(:max_redirects) || 2
33
+ progress_proc = options.delete(:progress_proc)
34
+ content_length_proc = options.delete(:content_length_proc)
35
+
36
+ open_uri_options = {
37
+ "User-Agent" => "Down/#{VERSION}",
38
+ content_length_proc: proc { |size|
39
+ if size && max_size && size > max_size
40
+ raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
41
+ end
42
+ content_length_proc.call(size) if content_length_proc
43
+ },
44
+ progress_proc: proc { |current_size|
45
+ if max_size && current_size > max_size
46
+ raise Down::TooLarge, "file is too large (max is #{max_size/1024/1024}MB)"
47
+ end
48
+ progress_proc.call(current_size) if progress_proc
49
+ },
50
+ redirect: false,
51
+ }
52
+
53
+ if options[:proxy]
54
+ proxy = URI(options.delete(:proxy))
55
+ user = proxy.user
56
+ password = proxy.password
57
+
58
+ if user || password
59
+ proxy.user = nil
60
+ proxy.password = nil
61
+
62
+ open_uri_options[:proxy_http_basic_authentication] = [proxy.to_s, user, password]
63
+ else
64
+ open_uri_options[:proxy] = proxy.to_s
65
+ end
66
+ end
67
+
68
+ open_uri_options.update(options)
69
+
70
+ tries = max_redirects + 1
71
+
72
+ begin
73
+ uri = URI(uri)
74
+
75
+ if uri.class != URI::HTTP && uri.class != URI::HTTPS
76
+ raise URI::InvalidURIError, "url is not http nor https"
77
+ end
78
+
79
+ if uri.user || uri.password
80
+ open_uri_options[:http_basic_authentication] ||= [uri.user, uri.password]
81
+ uri.user = nil
82
+ uri.password = nil
83
+ end
84
+
85
+ downloaded_file = uri.open(open_uri_options)
86
+ rescue OpenURI::HTTPRedirect => redirect
87
+ if (tries -= 1) > 0
88
+ uri = redirect.uri
89
+
90
+ if !redirect.io.meta["set-cookie"].to_s.empty?
91
+ open_uri_options["Cookie"] = redirect.io.meta["set-cookie"]
92
+ end
93
+
94
+ retry
95
+ else
96
+ raise Down::NotFound, "too many redirects"
97
+ end
98
+ rescue OpenURI::HTTPError,
99
+ URI::InvalidURIError,
100
+ Errno::ECONNREFUSED,
101
+ SocketError,
102
+ Timeout::Error
103
+ raise Down::NotFound, "file not found"
104
+ end
105
+
106
+ # open-uri will return a StringIO instead of a Tempfile if the filesize is
107
+ # less than 10 KB, so if it happens we convert it back to Tempfile. We want
108
+ # to do this with a Tempfile as well, because open-uri doesn't preserve the
109
+ # file extension, so we want to run it against #copy_to_tempfile which
110
+ # does.
111
+ open_uri_file = downloaded_file
112
+ downloaded_file = copy_to_tempfile(uri.path, open_uri_file)
113
+ OpenURI::Meta.init downloaded_file, open_uri_file
114
+
115
+ downloaded_file.extend DownloadedFile
116
+ downloaded_file
117
+ end
118
+
119
+ def open(uri, options = {})
120
+ uri = URI(uri)
121
+ http_class = Net::HTTP
122
+
123
+ if options[:proxy]
124
+ proxy = URI(options[:proxy])
125
+ http_class = Net::HTTP::Proxy(proxy.hostname, proxy.port, proxy.user, proxy.password)
126
+ end
127
+
128
+ http = http_class.new(uri.host, uri.port)
129
+
130
+ # taken from open-uri implementation
131
+ if uri.is_a?(URI::HTTPS)
132
+ require "net/https"
133
+ http.use_ssl = true
134
+ http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
135
+ store = OpenSSL::X509::Store.new
136
+ if options[:ssl_ca_cert]
137
+ Array(options[:ssl_ca_cert]).each do |cert|
138
+ File.directory?(cert) ? store.add_path(cert) : store.add_file(cert)
139
+ end
140
+ else
141
+ store.set_default_paths
142
+ end
143
+ http.cert_store = store
144
+ end
145
+
146
+ request_headers = options.select { |key, value| key.is_a?(String) }
147
+ get = Net::HTTP::Get.new(uri.request_uri, request_headers)
148
+ get.basic_auth(uri.user, uri.password) if uri.user || uri.password
149
+
150
+ request = Fiber.new do
151
+ http.start do
152
+ http.request(get) do |response|
153
+ Fiber.yield response
154
+ response.instance_variable_set("@read", true)
155
+ end
156
+ end
157
+ end
158
+
159
+ response = request.resume
160
+
161
+ raise Down::NotFound, "request returned status #{response.code} and body:\n#{response.body}" if response.code.to_i.between?(400, 599)
162
+
163
+ down_params = {
164
+ chunks: response.enum_for(:read_body),
165
+ size: response["Content-Length"] && response["Content-Length"].to_i,
166
+ on_close: -> { request.resume }, # close HTTP connnection
167
+ data: {
168
+ status: response.code.to_i,
169
+ headers: response.each_header.inject({}) { |headers, (downcased_name, value)|
170
+ name = downcased_name.split("-").map(&:capitalize).join("-")
171
+ headers.merge!(name => value)
172
+ },
173
+ response: response,
174
+ }
175
+ }
176
+ down_params[:rewindable] = options[:rewindable] if options.key?(:rewindable)
177
+ down_params[:encoding] = response.type_params["charset"] if response.type_params["charset"]
178
+
179
+ Down::ChunkedIO.new(down_params)
180
+ end
181
+
182
+ def copy_to_tempfile(basename, io)
183
+ tempfile = Tempfile.new(["down", File.extname(basename)], binmode: true)
184
+ if io.is_a?(OpenURI::Meta) && io.is_a?(Tempfile)
185
+ io.close
186
+ tempfile.close
187
+ FileUtils.mv io.path, tempfile.path
188
+ else
189
+ IO.copy_stream(io, tempfile)
190
+ io.rewind
191
+ end
192
+ tempfile.open
193
+ tempfile
194
+ end
195
+
196
+ module DownloadedFile
197
+ def original_filename
198
+ filename_from_content_disposition || filename_from_uri
199
+ end
200
+
201
+ def content_type
202
+ super unless meta["content-type"].to_s.empty?
203
+ end
204
+
205
+ private
206
+
207
+ def filename_from_content_disposition
208
+ content_disposition = meta["content-disposition"].to_s
209
+ filename = content_disposition[/filename="([^"]*)"/, 1] || content_disposition[/filename=(.+)/, 1]
210
+ filename = CGI.unescape(filename.to_s.strip)
211
+ filename unless filename.empty?
212
+ end
213
+
214
+ def filename_from_uri
215
+ path = base_uri.path
216
+ filename = path.split("/").last
217
+ CGI.unescape(filename) if filename
218
+ end
219
+ end
220
+ end
221
+ end
@@ -1,3 +1,3 @@
1
1
  module Down
2
- VERSION = "2.5.1"
2
+ VERSION = "3.0.0"
3
3
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: down
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-13 00:00:00.000000000 Z
11
+ date: 2017-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rake
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: minitest
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -39,47 +25,33 @@ dependencies:
39
25
  - !ruby/object:Gem::Version
40
26
  version: '5.8'
41
27
  - !ruby/object:Gem::Dependency
42
- name: webmock
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.3'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '2.3'
55
- - !ruby/object:Gem::Dependency
56
- name: addressable
28
+ name: mocha
57
29
  requirement: !ruby/object:Gem::Requirement
58
30
  requirements:
59
- - - "<"
31
+ - - ">="
60
32
  - !ruby/object:Gem::Version
61
- version: '2.5'
33
+ version: '0'
62
34
  type: :development
63
35
  prerelease: false
64
36
  version_requirements: !ruby/object:Gem::Requirement
65
37
  requirements:
66
- - - "<"
38
+ - - ">="
67
39
  - !ruby/object:Gem::Version
68
- version: '2.5'
40
+ version: '0'
69
41
  - !ruby/object:Gem::Dependency
70
- name: mocha
42
+ name: http
71
43
  requirement: !ruby/object:Gem::Requirement
72
44
  requirements:
73
- - - ">="
45
+ - - "~>"
74
46
  - !ruby/object:Gem::Version
75
- version: '0'
47
+ version: '2.1'
76
48
  type: :development
77
49
  prerelease: false
78
50
  version_requirements: !ruby/object:Gem::Requirement
79
51
  requirements:
80
- - - ">="
52
+ - - "~>"
81
53
  - !ruby/object:Gem::Version
82
- version: '0'
54
+ version: '2.1'
83
55
  description:
84
56
  email:
85
57
  - janko.marohnic@gmail.com
@@ -92,8 +64,10 @@ files:
92
64
  - down.gemspec
93
65
  - lib/down.rb
94
66
  - lib/down/chunked_io.rb
67
+ - lib/down/errors.rb
68
+ - lib/down/http.rb
69
+ - lib/down/net_http.rb
95
70
  - lib/down/version.rb
96
- - lib/down/wget.rb
97
71
  homepage: https://github.com/janko-m/down
98
72
  licenses:
99
73
  - MIT
@@ -106,7 +80,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
80
  requirements:
107
81
  - - ">="
108
82
  - !ruby/object:Gem::Version
109
- version: '0'
83
+ version: '2.1'
110
84
  required_rubygems_version: !ruby/object:Gem::Requirement
111
85
  requirements:
112
86
  - - ">="
@@ -1,20 +0,0 @@
1
- require "down/version"
2
- require "down/chunked_io"
3
-
4
- require "open3"
5
-
6
- module Down
7
- class Error < StandardError; end
8
- class TooLarge < Error; end
9
- class NotFound < Error; end
10
-
11
- module_function
12
-
13
- def download(url, options = {})
14
- max_size = options.delete(:max_size)
15
- max_redirects = options.delete(:max_redirects) || 2
16
- progress_proc = options.delete(:progress_proc) || options.delete(:progress)
17
- content_length_proc = options.delete(:content_length_proc)
18
- timeout = options.delete(:timeout)
19
- end
20
- end