down 2.5.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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