pinch 0.1.0 → 0.2.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.
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