downspout 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.7
1
+ 0.2.8
@@ -15,17 +15,16 @@ module Downspout
15
15
  end
16
16
 
17
17
  =begin rdoc
18
- Download a file from a given URL to a given Path on the local system
19
- The path is optional and will default to a generated temporary file
18
+ Download a file from a given URL. The path is optional and will default to a generated temporary file.
20
19
  =end
21
- def self.download_url_to_path( some_url, some_path = nil )
22
- $logger.debug("downspout | download_url_to_path | URL : #{some_url}")
23
- $logger.debug("downspout | download_url_to_path | Download Path : #{some_path}")
20
+ def self.fetch_url( some_url, some_path = nil )
21
+ $logger.debug("downspout | fetch_url | URL : #{some_url}")
22
+ $logger.debug("downspout | fetch_url | Download Path : #{some_path}")
24
23
 
25
24
  begin
26
25
  d = Downspout::Downloader.new( :url => some_url, :path => some_path )
27
26
  rescue Exception => e
28
- $logger.error("downspout | download_url_to_path | Exception : '#{e}'")
27
+ $logger.error("downspout | fetch_url | Exception : '#{e}'")
29
28
  return nil if e.class == Downspout::UnsupportedScheme
30
29
  raise e
31
30
  end
@@ -33,19 +32,16 @@ module Downspout
33
32
  fetched = d.download!
34
33
 
35
34
  if !(fetched) then
36
- $logger.error("downspout | download_url_to_path | Fetch Failed : #{d.url} ")
35
+ $logger.error("downspout | fetch_url | Fetch Failed : #{d.url} ")
37
36
  return nil
38
37
  end
39
38
 
40
- $logger.debug("downspout | download_url_to_path | Local File : #{d.path} ")
39
+ $logger.debug("downspout | fetch_url | Local File : #{d.path} ")
41
40
  return d
42
41
  end
43
42
 
44
- =begin rdoc
45
- Convenience method for downloading a file from an URL without specifying a path for storage.
46
- =end
47
- def self.fetch_url( the_url )
48
- return self.download_url_to_path( the_url )
43
+ def self.download_url_to_path( the_url, the_path ) #:nodoc:
44
+ return self.fetch_url( the_url, the_path )
49
45
  end
50
46
 
51
47
  =begin rdoc
@@ -8,6 +8,8 @@ module Downspout
8
8
  @@curb_allowed = true
9
9
  @@curb_enabled = true
10
10
  @@prefix = 'downspout'
11
+ @@max_redirects = 2
12
+ @@ssl_verification = true
11
13
 
12
14
  def self.tmp_dir
13
15
  return @@tmp_dir
@@ -29,6 +31,14 @@ module Downspout
29
31
  return @@credentials
30
32
  end
31
33
 
34
+ def self.max_redirects
35
+ @@max_redirects
36
+ end
37
+
38
+ def self.max_redirects=( num )
39
+ @@max_redirects = num
40
+ end
41
+
32
42
  def self.network_enabled?
33
43
  return @@network_enabled
34
44
  end
@@ -42,6 +52,10 @@ module Downspout
42
52
  @@network_enabled = true
43
53
  end
44
54
 
55
+ def self.ssl_verification?
56
+ @@ssl_verification
57
+ end
58
+
45
59
  def self.curb_available?
46
60
  begin
47
61
  require 'curb'
@@ -23,6 +23,7 @@ module Downspout
23
23
  @response_headers = {}
24
24
  @started_at = nil
25
25
  @finished_at = nil
26
+ @redirected_url = nil
26
27
 
27
28
  if options.respond_to?(:keys) then
28
29
  options.each do |key, value|
@@ -58,7 +59,7 @@ module Downspout
58
59
  if !(@path.nil?) then
59
60
  @basename = File.basename( @path )
60
61
  else
61
- if !(@uri.path.nil? || @uri.path.empty? || uri.path == '/')
62
+ if !(@uri.path.nil? || @uri.path.empty? || @uri.path == '/')
62
63
  @basename = File.basename( @uri.path )
63
64
  else
64
65
  $logger.debug("downspout | downloader | basename | Bad URI path")
@@ -197,7 +198,7 @@ module Downspout
197
198
  $logger.debug("downspout | downloader | net_http_download | Downloading #{@url} ...")
198
199
 
199
200
  begin
200
- response = net_http_fetch( @url , 1)
201
+ response = net_http_fetch( @url )
201
202
  open( @path, "wb" ) do |file|
202
203
  file.write(response.body)
203
204
  end
@@ -230,27 +231,38 @@ module Downspout
230
231
  return true
231
232
  end
232
233
 
233
- def net_http_fetch( url_str, limit = 10 )
234
- $logger.debug("downspout | downloader | net_http_fetch | URL: #{url_str}, Redirects: #{limit}.")
234
+ def net_http_fetch( url_str, redirects = 0 )
235
+ $logger.debug("downspout | downloader | net_http_fetch | URL: #{url_str}, Redirects: #{redirects}.")
236
+
235
237
  raise Downspout::BadURL, 'URL is missing' if url_str.nil?
236
- raise Downspout::ExcessiveRedirects, 'HTTP redirect too deep' if limit == 0
238
+
239
+ if redirects > Downspout::Config.max_redirects then
240
+ raise Downspout::ExcessiveRedirects, 'HTTP redirect too deep'
241
+ end
237
242
 
238
243
  u = URI.parse( url_str )
244
+
245
+ http = Net::HTTP.new( u.host, u.port )
246
+
247
+ if (u.scheme == "https") then
248
+ http.use_ssl = true
249
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if !(Downspout::Config.ssl_verification?)
250
+ end
239
251
 
240
- my_request = Net::HTTP::Get.new( "#{u.path}?#{u.query}" )
252
+ my_request = Net::HTTP::Get.new( u.request_uri )
241
253
 
242
254
  # TODO : implement credentials for downloads via net_http_fetch
243
255
  my_request.basic_auth 'account', 'p4ssw0rd'
244
256
 
245
- @response = Net::HTTP.start( u.host, u.port ) do |http|
246
- http.request( my_request )
247
- end
257
+ @response = http.request( my_request )
248
258
 
249
259
  case @response
250
260
  when Net::HTTPSuccess
251
261
  @response
252
262
  when Net::HTTPRedirection
253
- net_http_fetch( @response['location'], limit - 1 )
263
+ @redirected_url = @response['location']
264
+ # TODO : use the new location to update the file name / extension when unknown
265
+ net_http_fetch( @redirected_url, redirects + 1 )
254
266
  else
255
267
  $logger.error("downspout | downloader | net_http_fetch | Response : #{@response}")
256
268
  @response.error!
@@ -261,10 +273,10 @@ module Downspout
261
273
  $logger.debug("downspout | downloader | curb_http_download | Downloading #{@url} ...")
262
274
 
263
275
  begin
264
- curb = Curl::Easy.download( @url, @path) {|c| c.follow_location=true; c.max_redirects=1;}
265
- rescue Curl::Err::HostResolutionError
276
+ curb = Curl::Easy.download( @url, @path) {|c| c.follow_location=true; c.max_redirects = Downspout::Config.max_redirects;}
277
+ rescue Curl::Err::HostResolutionError => dns_err
266
278
  $logger.error("downspout | downloader | curb_http_download | Curb/Curl DNS Error | #{@uri.host}")
267
- return false
279
+ raise dns_err
268
280
  end
269
281
 
270
282
  $logger.debug("downspout | downloader | curb_http_download | Response Code : #{curb.response_code}")
@@ -277,6 +289,12 @@ module Downspout
277
289
  # populate the response headers from curb header string
278
290
  parse_headers_from_string!( curb.header_str )
279
291
 
292
+ ultimate_url = curb_last_location( curb.header_str )
293
+ if !( ultimate_url == @url ) then
294
+ # re-directed
295
+ @redirected_url = ultimate_url
296
+ end
297
+
280
298
  # populate a 'proxy' HTTPResponse object with the Curb data...
281
299
  hr_klass = Net::HTTPResponse.send('response_class', curb.response_code.to_s)
282
300
  $logger.debug("downspout | downloader | curb_http_download | Response Type : #{hr_klass.name}")
@@ -293,22 +311,42 @@ module Downspout
293
311
  return true
294
312
  end
295
313
 
314
+ def curb_last_location( header_string )
315
+ matches = header_string.scan(/Location\:\s?(.*)\W/)
316
+
317
+ return nil if matches.nil?
318
+ return nil if (matches.class == Array) && (matches.last.nil?)
319
+
320
+ result = matches.last.first.strip
321
+ $logger.error("downspout | downloader | curb_last_location | #{result}")
322
+ return result
323
+ end
324
+
296
325
  def parse_headers_from_string!( header_str )
326
+ # $logger.debug("downspout | downloader | parse_headers_from_string! | Header String : #{header_str}")
297
327
  header_hash = {}
298
- http_hash = {}
328
+
299
329
  headers = header_str.split("\r\n")
300
-
301
330
  http_info = headers[0]
331
+
332
+ http_hash = {}
302
333
  http_hash[:header] = http_info
303
334
  http_hash[:version] = http_info.split(" ")[0].match("HTTP/([0-9\.]+)")[1]
304
335
  http_hash[:code] = (http_info.split("\r\n")[0].split(" ")[1]).to_i
305
336
  http_hash[:message] = http_info.split("\r\n")[0].split(" ")[2]
306
337
 
307
338
  header_hash["HTTP"] = http_hash
308
-
309
- headers[1..-1].each do |line|
310
- header_name, header_value = line.match(/([\w\-\s]+)\:\s?(.*)/)[1..2]
311
- header_hash[header_name] = header_value
339
+
340
+ headers[1..-1].each do |line|
341
+ next if line.nil? || line.empty?
342
+ begin
343
+ matches = line.match(/([\w\-\s]+)\:\s?(.*)/)
344
+ next if matches.nil? || matches.size < 3
345
+ header_name, header_value = matches[1..2]
346
+ header_hash[header_name] = header_value
347
+ rescue Exception => e
348
+ $logger.warn("downspout | downloader | parse_headers_from_string! | #{line}, Exception : #{e}")
349
+ end
312
350
  end
313
351
 
314
352
  @response_headers = header_hash
@@ -318,20 +356,22 @@ module Downspout
318
356
  result = nil
319
357
 
320
358
  result = file_name_from_content_disposition
359
+ result = file_name_from_redirect if result.nil?
321
360
  result = file_name_from_content_type if result.nil?
322
361
 
323
362
  return result
324
363
  end
325
364
 
326
-
327
365
  # Extracts filename from Content-Disposition Header per RFC 2183
328
366
  # "http://tools.ietf.org/html/rfc2183"
329
367
  def file_name_from_content_disposition
330
-
331
368
  file_name = nil
332
369
 
333
- cd_key = response_headers.keys.select{|k| k =~ /content-disposition/i }.first
370
+ cd_key = response_headers.keys.select{|k| k =~ /content-disposition/i }.first # TODO: better to use the last?
334
371
 
372
+ $logger.debug("downspout | downloader | file_name_from_content_disposition | cd key : #{cd_key}")
373
+ return nil if cd_key.nil?
374
+
335
375
  if cd_key then
336
376
  disposition = @response_headers[cd_key]
337
377
  if disposition then
@@ -357,6 +397,19 @@ module Downspout
357
397
  $logger.debug("downspout | downloader | file_name_from_content_type | #{file_name}")
358
398
  return file_name
359
399
  end
400
+
401
+ def file_name_from_redirect
402
+ return nil if @redirected_url.nil?
403
+
404
+ my_uri = URI::parse( @redirected_url )
405
+
406
+ if !(my_uri.path.nil? || my_uri.path.empty? || my_uri.path == '/')
407
+ return File.basename( my_uri.path )
408
+ else
409
+ $logger.debug("downspout | downloader | basename | Bad URI path")
410
+ return nil
411
+ end
412
+ end
360
413
 
361
414
  end
362
415
 
@@ -1,10 +1,10 @@
1
1
  if !(defined?( $logger )) then
2
- if defined?( RAILS_DEFAULT_LOGGER ) then
3
- $logger = RAILS_DEFAULT_LOGGER
2
+ if ( defined?( Rails ) && !(Rails.logger.nil?) ) then
3
+ $logger = Rails.logger
4
4
  else
5
5
  require 'logger'
6
6
  $logger = Logger.new( STDERR )
7
- $logger.level = Logger::INFO
7
+ $logger.level = Logger::WARN
8
8
  end
9
9
  end
10
10
 
data/lib/downspout.rb CHANGED
@@ -4,6 +4,7 @@ require 'rubygems'
4
4
  require 'fileutils'
5
5
  require 'uri'
6
6
  require 'net/http'
7
+ require 'net/https'
7
8
  require 'net/ftp'
8
9
 
9
10
  # add this directory to the path...
@@ -17,26 +17,40 @@ class DownspoutTest < Test::Unit::TestCase
17
17
 
18
18
  $test_ws.mount(TestServlet.path, TestServlet)
19
19
 
20
+ n_deep_proc = Proc.new { |req, resp|
21
+ resp.body = '3-deep redirector proc mounted on #{req.script_name}'
22
+
23
+ num = req.query['n'].to_i
24
+
25
+ if num > 1 then
26
+ resp.set_redirect( HTTPStatus::MovedPermanently, "/deep/?n=#{num - 1}")
27
+ else
28
+ resp.set_redirect( HTTPStatus::MovedPermanently, '/READ_ME.rdoc')
29
+ end
30
+ }
31
+
32
+ $test_ws.mount('/deep', HTTPServlet::ProcHandler.new(n_deep_proc) )
33
+
20
34
  two_deep_proc = Proc.new { |req, resp|
21
35
  resp.body = '2-deep redirector proc mounted on #{req.script_name}'
22
- resp.set_redirect( HTTPStatus::MovedPermanently, '/one/deep')
36
+ resp.set_redirect( HTTPStatus::MovedPermanently, '/deep/one')
23
37
  }
24
38
 
25
- $test_ws.mount('/two/deep/', HTTPServlet::ProcHandler.new(two_deep_proc) )
39
+ $test_ws.mount('/deep/two', HTTPServlet::ProcHandler.new(two_deep_proc) )
26
40
 
27
41
  redir_proc = Proc.new { |req, resp|
28
42
  resp.body = 'redirector proc mounted on #{req.script_name}'
29
43
  resp.set_redirect( HTTPStatus::MovedPermanently, '/READ_ME.rdoc')
30
44
  }
31
45
 
32
- $test_ws.mount('/one/deep/', HTTPServlet::ProcHandler.new(redir_proc) )
46
+ $test_ws.mount('/deep/one', HTTPServlet::ProcHandler.new(redir_proc) )
33
47
 
34
48
  $test_ws.mount("/images", HTTPServlet::FileHandler,
35
49
  File.join( Test::App.root, "test", "fixtures"), {:FancyIndexing => true} )
36
50
 
37
51
  $test_ws_thread = Thread.new { $test_ws.start }
38
52
  end
39
-
53
+
40
54
  def test_download_rdoc_from_servlet
41
55
  some_url = $test_read_me_url
42
56
 
@@ -69,10 +83,12 @@ class DownspoutTest < Test::Unit::TestCase
69
83
  end
70
84
 
71
85
  should "fail with Curb error in case of excessive redirects" do
72
- two_deep_url = "http://127.0.0.1:8899/two/deep?curby"
86
+ assert_equal 2, Downspout::Config.max_redirects
87
+
88
+ too_deep_url = "http://127.0.0.1:8899/deep?n=3&test=curb"
73
89
 
74
90
  assert_raise Curl::Err::TooManyRedirectsError do
75
- dl = Downspout.fetch_url( two_deep_url )
91
+ dl = Downspout.fetch_url( too_deep_url )
76
92
  end
77
93
  end
78
94
 
@@ -82,12 +98,13 @@ class DownspoutTest < Test::Unit::TestCase
82
98
  end
83
99
 
84
100
  should "fail with Downspout error in case of excessive redirects" do
85
- two_deep_url = "http://127.0.0.1:8899/two/deep?no-curb"
101
+ assert_equal 2, Downspout::Config.max_redirects
102
+
103
+ too_deep_url = "http://127.0.0.1:8899/deep/?n=3&test=no-curb"
86
104
 
87
105
  assert_raise Downspout::ExcessiveRedirects do
88
- dl = Downspout.fetch_url( two_deep_url )
106
+ dl = Downspout.fetch_url( too_deep_url )
89
107
  end
90
-
91
108
  end
92
109
 
93
110
  teardown do
@@ -102,10 +119,12 @@ class DownspoutTest < Test::Unit::TestCase
102
119
  end
103
120
 
104
121
  should "fail due to excessive redirects" do
105
- @obj = Downspout::Downloader.new( :url => "http://127.0.0.1:8899/two/deep?over-draft" )
122
+ @obj = Downspout::Downloader.new( :url => "http://127.0.0.1:8899/deep/?n=2&test=over-draft" )
106
123
 
107
124
  assert_raise Downspout::ExcessiveRedirects do
108
- @obj.send('net_http_fetch', @obj.url, 1 ) # uses send to bypass Curb and force Net::HTTP
125
+ # use send to bypass Curb and force Net::HTTP
126
+ # also pass 1 so that the second redirect is too much
127
+ @obj.send('net_http_fetch', @obj.url, 1 )
109
128
  end
110
129
  end
111
130
 
@@ -113,7 +132,8 @@ class DownspoutTest < Test::Unit::TestCase
113
132
  @obj = Downspout::Downloader.new( :url => "http://fu.man.chu/deep/nested/resource?over-draft" )
114
133
 
115
134
  assert_raise SocketError do
116
- @obj.send('net_http_fetch', @obj.url) # uses send to bypass Curb and force Net::HTTP
135
+ # uses send to bypass Curb and force Net::HTTP
136
+ @obj.send('net_http_fetch', @obj.url)
117
137
  end
118
138
  end
119
139
 
@@ -125,3 +145,4 @@ class DownspoutTest < Test::Unit::TestCase
125
145
  end
126
146
 
127
147
  end
148
+
@@ -0,0 +1,54 @@
1
+ require 'test_helper'
2
+
3
+ class FocusedTest < Test::Unit::TestCase
4
+ context "Downspout" do
5
+ context "Focus" do
6
+ setup do
7
+ @expected_url = "https://download.github.com/rails-rails-v2.3.11-0-gb0c3d45.tar.gz"
8
+ @header_str = "HTTP/1.1 302 Found
9
+ Server: nginx/0.7.67
10
+ Date: Mon, 18 Apr 2011 01:53:16 GMT
11
+ Content-Type: text/html; charset=utf-8
12
+ Connection: keep-alive
13
+ Status: 302 Found
14
+ Location: https://nodeload.github.com/rails/rails/tarball/v2.3.11
15
+ X-Runtime: 3ms
16
+ Content-Length: 121
17
+ Set-Cookie: _gh_sess=BAh7BzoRbG9jYWxlX2d1ZXNzMCIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--719def2ab2d99283383e374327fcdbb716c45c12; path=/; expires=Fri, 01 Jan 2021 00:00:00 GMT; secure; HttpOnly
18
+ Cache-Control: no-cache
19
+ Strict-Transport-Security: max-age=2592000
20
+
21
+ HTTP/1.1 302 Moved Temporarily
22
+ Server: nginx/0.7.67
23
+ Date: Mon, 18 Apr 2011 01:53:17 GMT
24
+ Transfer-Encoding: chunked
25
+ Connection: keep-alive
26
+ Location: https://download.github.com/rails-rails-v2.3.11-0-gb0c3d45.tar.gz
27
+
28
+ HTTP/1.1 200 OK
29
+ Server: nginx/0.7.67
30
+ Date: Mon, 18 Apr 2011 01:53:17 GMT
31
+ Content-Type: application/octet-stream
32
+ Content-Length: 3416081
33
+ Last-Modified: Mon, 18 Apr 2011 01:45:25 GMT
34
+ Connection: keep-alive
35
+ Accept-Ranges: bytes"
36
+ end
37
+
38
+ should "grep last location header" do
39
+ last_location = @header_str.scan(/Location\:\s?(.*)\W/).last.first
40
+
41
+ assert_equal @expected_url, last_location
42
+ end
43
+
44
+ should "match last_location method" do
45
+ @obj = Downspout::Downloader.new( :url => "http://bad.url/path" )
46
+ last_location = @obj.send('curb_last_location', @header_str )
47
+
48
+ assert_equal String, last_location.class
49
+ assert_equal @expected_url, last_location
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -4,28 +4,56 @@ class ConfigTest < Test::Unit::TestCase
4
4
  context "Downspout" do
5
5
  context "Config" do
6
6
 
7
- should "respond to tmp_dir" do
8
- assert Downspout::Config.respond_to?(:tmp_dir)
7
+ should "respond to max_redirects" do
8
+ assert Downspout::Config.respond_to?(:max_redirects)
9
9
  end
10
10
 
11
- should "default to '/tmp/downloads/' for Downspout::Config#tmp_dir" do
12
- assert_equal "/tmp/downloads/", Downspout::Config.tmp_dir
11
+ should "respond to tmp_dir" do
12
+ assert Downspout::Config.respond_to?(:tmp_dir)
13
13
  end
14
14
 
15
15
  should "respond to Network Enabled" do
16
16
  assert Downspout::Config.respond_to?("network_enabled?")
17
17
  end
18
18
 
19
- should "default to Network Enabled" do
20
- assert Downspout::Config.network_enabled?
21
- end
22
-
23
19
  should "respond to Disable Networking" do
24
20
  assert Downspout::Config.respond_to?("disable_networking!")
25
21
  end
26
22
 
23
+ should "respond to SSL Verification" do
24
+ assert Downspout::Config.respond_to?("ssl_verification?")
25
+ end
26
+
27
+ context "defaults" do
28
+
29
+ should "use '/tmp/downloads/' for Downspout::Config#tmp_dir" do
30
+ assert_equal "/tmp/downloads/", Downspout::Config.tmp_dir
31
+ end
32
+
33
+ should "be 2 redirect max" do
34
+ assert_equal 2, Downspout::Config.max_redirects
35
+ end
36
+
37
+ should "be Network Enabled" do
38
+ assert Downspout::Config.network_enabled?
39
+ end
40
+
41
+ should "use 'downspout' Default Prefix" do
42
+ assert_equal "downspout", Downspout::Config.default_prefix
43
+ end
44
+
45
+ should "use Curb library if available" do
46
+ assert Downspout::Config.curb_available?
47
+ assert Downspout::Config.enable_curb!
48
+ end
49
+
50
+ should "use SSL Verification" do
51
+ assert Downspout::Config.ssl_verification?
52
+ end
53
+
54
+ end
55
+
27
56
  should "allow setting Default Prefix" do
28
- assert_equal "downspout", Downspout::Config.default_prefix
29
57
  Downspout::Config.default_prefix = "fu-manchu"
30
58
  assert_equal "fu-manchu", Downspout::Config.default_prefix
31
59
  end
@@ -44,17 +72,19 @@ class ConfigTest < Test::Unit::TestCase
44
72
  assert Downspout::Config.curb_available?
45
73
  end
46
74
 
47
- should "enable Curb if library is available" do
48
- assert Downspout::Config.curb_available?
49
- assert Downspout::Config.enable_curb!
50
- end
51
-
52
75
  should "support enabling network operations" do
53
76
  assert !(Downspout::Config.network_enabled?)
54
77
  assert Downspout::Config.enable_networking!
55
78
  assert Downspout::Config.network_enabled?
56
79
  end
57
80
 
81
+ should "allow customization of redirect maximum" do
82
+ Downspout::Config.max_redirects = 3
83
+ assert_equal 3, Downspout::Config.max_redirects
84
+ Downspout::Config.max_redirects = 2
85
+ assert_equal 2, Downspout::Config.max_redirects
86
+ end
87
+
58
88
  context "Host-Based Credentials" do
59
89
  should "respond to Credentials" do
60
90
  assert Downspout::Config.respond_to?(:credentials)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: downspout
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 7
10
- version: 0.2.7
9
+ - 8
10
+ version: 0.2.8
11
11
  platform: ruby
12
12
  authors:
13
13
  - Phi.Sanders
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-15 00:00:00 -04:00
18
+ date: 2011-04-17 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -122,6 +122,7 @@ files:
122
122
  - test/downspout_test.rb
123
123
  - test/fixtures/faux_headers.txt
124
124
  - test/fixtures/ruby.png
125
+ - test/focused_test.rb
125
126
  - test/servlet.rb
126
127
  - test/test_helper.rb
127
128
  - test/test_logger.rb
@@ -168,6 +169,7 @@ specification_version: 3
168
169
  summary: Downspout is an easy-to-use ruby library for downloading files from URLs.
169
170
  test_files:
170
171
  - test/downspout_test.rb
172
+ - test/focused_test.rb
171
173
  - test/servlet.rb
172
174
  - test/test_helper.rb
173
175
  - test/test_logger.rb