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.
- data/README.rdoc +4 -2
- data/Rakefile +1 -1
- data/lib/pinch.rb +62 -44
- data/spec/pinch_spec.rb +16 -0
- metadata +56 -61
data/README.rdoc
CHANGED
@@ -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
|
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
|
-
|
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
data/lib/pinch.rb
CHANGED
@@ -5,7 +5,7 @@ require 'zlib'
|
|
5
5
|
# @author Peter Hellberg
|
6
6
|
# @author Edward Patel
|
7
7
|
class Pinch
|
8
|
-
VERSION = "0.1
|
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
|
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 =
|
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
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
-
#
|
231
|
-
def
|
232
|
-
|
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
|
-
|
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
|
data/spec/pinch_spec.rb
CHANGED
@@ -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
|
-
|
15
|
-
|
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
|
-
|
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:
|
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
|
-
|
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:
|
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
|
-
|
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:
|
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
|
-
|
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:
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
59
56
|
type: :development
|
60
|
-
|
61
|
-
|
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:
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
101
97
|
requirements: []
|
102
|
-
|
103
98
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.6
|
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
|