pinch 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/README.rdoc +4 -2
  2. data/Rakefile +1 -1
  3. data/lib/pinch.rb +62 -44
  4. data/spec/pinch_spec.rb +16 -0
  5. metadata +56 -61
@@ -8,10 +8,12 @@ over HTTP/1.1 using nothing but the Ruby Standard Library (net/http and zlib)
8
8
  Pinch is the Ruby implementation of an idea that my colleague Edward Patel
9
9
  had a while back.
10
10
 
11
- The first version was written in Objetive-C and we thought it would be cool
11
+ The first version was written in Objective-C and we thought it would be cool
12
12
  if we could bring that functionality to Ruby :)
13
13
 
14
- I’ve tested Pinch on 1.9.2. YMMV.
14
+ That version is available on https://github.com/epatel/pinch-objc/
15
+
16
+ I’ve tested Pinch on 1.9.2, 1.8.7 and MacRuby 0.10, YMMV.
15
17
 
16
18
  == Installation
17
19
 
data/Rakefile CHANGED
@@ -5,5 +5,5 @@ task :default => :test
5
5
  Rake::TestTask.new(:test) do |t|
6
6
  t.test_files = FileList['spec/*_spec.rb']
7
7
  t.ruby_opts = ['-rubygems'] if defined? Gem
8
- t.ruby_opts << '-I.'
8
+ t.ruby_opts << '-w -I.'
9
9
  end
@@ -5,7 +5,7 @@ require 'zlib'
5
5
  # @author Peter Hellberg
6
6
  # @author Edward Patel
7
7
  class Pinch
8
- VERSION = "0.1.0"
8
+ VERSION = "0.2.1"
9
9
 
10
10
  attr_reader :uri
11
11
 
@@ -53,11 +53,13 @@ class Pinch
53
53
  # Initializes a new Pinch object
54
54
  #
55
55
  # @param [String] url Full URL to the ZIP file
56
+ # @param [Strin] redirects (Optional) Number of redirects to follow
56
57
  # @note You might want to use Pinch.get instead.
57
58
  #
58
- def initialize(url)
59
- @uri = URI.parse(url)
60
- @files = {}
59
+ def initialize(url, redirects = 5)
60
+ @uri = URI.parse(url)
61
+ @files = {}
62
+ @redirects = redirects
61
63
  end
62
64
 
63
65
  ##
@@ -83,14 +85,28 @@ class Pinch
83
85
  #
84
86
  def content_length
85
87
  @content_length ||= begin
86
- response = prepared_connection.start { |http|
88
+ response = connection(@uri).start { |http|
87
89
  http.head(@uri.path)
88
90
  }
89
91
 
90
92
  # Raise exception if the response code isn’t in the 2xx range
91
93
  response.error! unless response.kind_of?(Net::HTTPSuccess)
92
94
 
95
+ # Raise exception if the server doesn’t support the Range header
96
+ unless (response['Accept-Ranges'] or "").include?('bytes')
97
+ raise RangeHeaderException,
98
+ "Range HTTP header not supported on #{@uri.host}"
99
+ end
100
+
93
101
  response['Content-Length'].to_i
102
+ rescue Net::HTTPRetriableError => e
103
+ @uri = URI.parse(e.response['Location'])
104
+
105
+ if (@redirects -= 1) > 0
106
+ retry
107
+ else
108
+ raise TooManyRedirects, "Gave up at on #{@uri.host}"
109
+ end
94
110
  end
95
111
  end
96
112
 
@@ -138,27 +154,6 @@ private
138
154
  end
139
155
 
140
156
  def file_headers
141
- @file_headers ||= begin
142
- raise RuntimeError, "Couldn’t find the central directory." if central_directory.nil?
143
-
144
- headers = {}
145
- tmp = central_directory
146
-
147
- begin
148
- cd = tmp.unpack('VvvvvvvVVVvvvvvVV')
149
- break if cd[1] == 0
150
-
151
- length = 46+cd[10]+cd[11]+cd[12]
152
- current_file_name = tmp[46...46+cd[10]]
153
- tmp = tmp[length..-1]
154
- headers[current_file_name] = cd
155
- end while true
156
-
157
- headers
158
- end
159
- end
160
-
161
- def central_directory
162
157
  #0 uint32 centralDirectoryFileHeaderSignature
163
158
  #1 uint16 versionMadeBy
164
159
  #2 uint16 versionNeededToExtract
@@ -177,6 +172,26 @@ private
177
172
  #15 uint32 externalFileAttributes
178
173
  #16 uint32 relativeOffsetOfLocalFileHeader
179
174
 
175
+ @file_headers ||= begin
176
+ raise RuntimeError, "Couldn’t find the central directory." if central_directory.nil?
177
+
178
+ headers = {}
179
+
180
+ central_directory.
181
+ unpack("H*")[0].
182
+ split("504b0102")[1..-1].
183
+ each do |fh|
184
+ data = ["504b0102#{fh}"].pack('H*')
185
+ file_header = data.unpack('VvvvvvvVVVvvvvvVV')
186
+ file_name = data[46...46+file_header[10]]
187
+ headers[file_name] = file_header
188
+ end
189
+
190
+ headers
191
+ end
192
+ end
193
+
194
+ def central_directory
180
195
  @central_directory ||= begin
181
196
  offset_start = end_of_central_directory_record[5]
182
197
  offset_end = end_of_central_directory_record[5] + end_of_central_directory_record[4]
@@ -202,15 +217,18 @@ private
202
217
 
203
218
  @end_of_central_directory_record ||= begin
204
219
  # Retrieve a 4k of data from the end of the zip file
205
- offset = content_length >= 4096 ? content_length-4096 : 0
220
+ offset = content_length >= 4096 ? content_length-4096 : 0
206
221
 
207
222
  response = fetch_data(offset, content_length)
208
223
 
209
- # Unpack the body into a hex string
210
- hex = response.body.unpack("H*")[0]
211
-
212
- # Split on the end record signature, and unpack the last one
213
- [hex.split("504b0506").last].pack("H*").unpack("vvvvVVv")
224
+ # Unpack the body into a hex string then split on
225
+ # the end record signature, and finally unpack the last one.
226
+ [response.body.
227
+ unpack("H*")[0].
228
+ split("504b0506").
229
+ last[0...36]].
230
+ pack("H*").
231
+ unpack("vvvvVVv")
214
232
 
215
233
  # Skipping the hex unpack and splitting on
216
234
  # PK\x05\x06 instead was for some reason slower.
@@ -222,22 +240,22 @@ private
222
240
  def fetch_data(offset_start, offset_end)
223
241
  request = Net::HTTP::Get.new(@uri.request_uri)
224
242
  request.set_range(offset_start, offset_end)
225
-
226
- prepared_connection.request(request)
243
+ connection(@uri).request(request)
227
244
  end
228
245
 
229
246
  ##
230
- # Prepare the connection and GET request
231
- def prepared_connection
232
- @prepared_connection ||= begin
233
- http = Net::HTTP.new(@uri.host, @uri.port)
234
-
235
- if @uri.is_a?(URI::HTTPS)
236
- http.use_ssl = true
237
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
238
- end
247
+ # A connection that automatically enables SSL (No verification)
248
+ def connection(uri)
249
+ http = Net::HTTP.new(uri.host, uri.port)
239
250
 
240
- http
251
+ if uri.is_a?(URI::HTTPS)
252
+ http.use_ssl = true
253
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
241
254
  end
255
+
256
+ http
242
257
  end
258
+
259
+ class RangeHeaderException < StandardError; end
260
+ class TooManyRedirects < StandardError; end
243
261
  end
@@ -1,3 +1,8 @@
1
+ # Require psych under MRI to remove warning messages
2
+ if Object.const_defined?(:RUBY_ENGINE) && RUBY_ENGINE == "ruby"
3
+ require 'psych'
4
+ end
5
+
1
6
  require 'minitest/pride'
2
7
  require 'minitest/autorun'
3
8
  require 'minitest/spec'
@@ -12,6 +17,17 @@ end
12
17
  require File.dirname(__FILE__) + '/../lib/pinch'
13
18
 
14
19
  describe Pinch do
20
+ describe "url that redirects to the pinch zipball" do
21
+ it "should throw an exception about missing Range header support on GitHub" do
22
+ VCR.use_cassette('pinch_zipball') do
23
+ @url = 'https://github.com/peterhellberg/pinch/zipball/master'
24
+ lambda {
25
+ Pinch.file_list(@url)
26
+ }.must_raise Pinch::RangeHeaderException
27
+ end
28
+ end
29
+ end
30
+
15
31
  describe "when calling get on a compressed ZIP file" do
16
32
  it "should return the contents of the file" do
17
33
  VCR.use_cassette('squeak') do
metadata CHANGED
@@ -1,109 +1,104 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: pinch
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
4
5
  prerelease:
5
- version: 0.1.0
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Peter Hellberg
9
9
  - Edward Patel
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
-
14
- date: 2011-07-19 00:00:00 +02:00
15
- default_executable:
16
- dependencies:
17
- - !ruby/object:Gem::Dependency
13
+ date: 2011-09-08 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
18
16
  name: yard
19
- prerelease: false
20
- requirement: &id001 !ruby/object:Gem::Requirement
17
+ requirement: &70351992865600 !ruby/object:Gem::Requirement
21
18
  none: false
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: "0"
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
26
23
  type: :development
27
- version_requirements: *id001
28
- - !ruby/object:Gem::Dependency
29
- name: minitest
30
24
  prerelease: false
31
- requirement: &id002 !ruby/object:Gem::Requirement
25
+ version_requirements: *70351992865600
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: &70351992864920 !ruby/object:Gem::Requirement
32
29
  none: false
33
- requirements:
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: "0"
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
37
34
  type: :development
38
- version_requirements: *id002
39
- - !ruby/object:Gem::Dependency
40
- name: fakeweb
41
35
  prerelease: false
42
- requirement: &id003 !ruby/object:Gem::Requirement
36
+ version_requirements: *70351992864920
37
+ - !ruby/object:Gem::Dependency
38
+ name: fakeweb
39
+ requirement: &70351992864160 !ruby/object:Gem::Requirement
43
40
  none: false
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: "0"
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
48
45
  type: :development
49
- version_requirements: *id003
50
- - !ruby/object:Gem::Dependency
51
- name: vcr
52
46
  prerelease: false
53
- requirement: &id004 !ruby/object:Gem::Requirement
47
+ version_requirements: *70351992864160
48
+ - !ruby/object:Gem::Dependency
49
+ name: vcr
50
+ requirement: &70351992863460 !ruby/object:Gem::Requirement
54
51
  none: false
55
- requirements:
56
- - - ">="
57
- - !ruby/object:Gem::Version
58
- version: "0"
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
59
56
  type: :development
60
- version_requirements: *id004
61
- description: Pinch makes it possible to download a specific file from within a ZIP file over HTTP 1.1.
57
+ prerelease: false
58
+ version_requirements: *70351992863460
59
+ description: Pinch makes it possible to download a specific file from within a ZIP
60
+ file over HTTP 1.1.
62
61
  email: peter@c7.se
63
62
  executables: []
64
-
65
63
  extensions: []
66
-
67
- extra_rdoc_files:
64
+ extra_rdoc_files:
68
65
  - README.rdoc
69
66
  - MIT-LICENSE
70
- files:
67
+ files:
71
68
  - lib/pinch.rb
72
69
  - MIT-LICENSE
73
70
  - README.rdoc
74
71
  - Rakefile
75
72
  - .gemtest
76
73
  - spec/pinch_spec.rb
77
- has_rdoc: true
78
74
  homepage: http://peterhellberg.github.com/pinch/
79
- licenses:
75
+ licenses:
80
76
  - MIT-LICENSE
81
77
  post_install_message:
82
- rdoc_options:
78
+ rdoc_options:
83
79
  - --main
84
80
  - README.rdoc
85
81
  - --charset=UTF-8
86
- require_paths:
82
+ require_paths:
87
83
  - lib
88
84
  - lib
89
- required_ruby_version: !ruby/object:Gem::Requirement
85
+ required_ruby_version: !ruby/object:Gem::Requirement
90
86
  none: false
91
- requirements:
92
- - - ">="
93
- - !ruby/object:Gem::Version
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
94
90
  version: 1.8.7
95
- required_rubygems_version: !ruby/object:Gem::Requirement
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
92
  none: false
97
- requirements:
98
- - - ">="
99
- - !ruby/object:Gem::Version
100
- version: "0"
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
101
97
  requirements: []
102
-
103
98
  rubyforge_project:
104
- rubygems_version: 1.6.2
99
+ rubygems_version: 1.8.6
105
100
  signing_key:
106
101
  specification_version: 3
107
102
  summary: Retrieve a file from inside a zip file, over the network!
108
- test_files:
103
+ test_files:
109
104
  - spec/pinch_spec.rb