pinch 0.0.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/.gemtest +0 -0
- data/MIT-LICENSE +19 -0
- data/README.rdoc +48 -0
- data/Rakefile +9 -0
- data/lib/pinch.rb +156 -0
- data/spec/pinch_spec.rb +24 -0
- metadata +73 -0
data/.gemtest
ADDED
File without changes
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Peter Hellberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= Pinch
|
2
|
+
|
3
|
+
Retrieve a file from inside a zip file, over the network!
|
4
|
+
|
5
|
+
Pinch makes it possible to download a specific file from within a ZIP file
|
6
|
+
over HTTP/1.1 using nothing but the Ruby Standard Library (net/http and zlib)
|
7
|
+
|
8
|
+
Pinch is the Ruby implementation of an idea that my colleague Edward Patel
|
9
|
+
had a while back.
|
10
|
+
|
11
|
+
The first version was written in Objetive-C and we thought it would be cool
|
12
|
+
if we could bring that functionality to Ruby :)
|
13
|
+
|
14
|
+
I’ve tested Pitch on 1.9.2. YMMV.
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
gem install pinch
|
19
|
+
|
20
|
+
== Usage example
|
21
|
+
|
22
|
+
require 'pinch'
|
23
|
+
|
24
|
+
data = Pinch.get 'URL_TO_ZIP_FILE', 'path/file.ext'
|
25
|
+
|
26
|
+
puts data
|
27
|
+
|
28
|
+
== LICENSE
|
29
|
+
|
30
|
+
Copyright (c) 2011 Peter Hellberg and Edward Patel
|
31
|
+
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
33
|
+
of this software and associated documentation files (the "Software"), to deal
|
34
|
+
in the Software without restriction, including without limitation the rights
|
35
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
36
|
+
copies of the Software, and to permit persons to whom the Software is
|
37
|
+
furnished to do so, subject to the following conditions:
|
38
|
+
|
39
|
+
The above copyright notice and this permission notice shall be included in
|
40
|
+
all copies or substantial portions of the Software.
|
41
|
+
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
43
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
44
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
45
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
46
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
47
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
48
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
data/lib/pinch.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'net/http'
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
class Pinch
|
6
|
+
VERSION = "0.0.1"
|
7
|
+
|
8
|
+
attr_reader :uri
|
9
|
+
attr_reader :file_name
|
10
|
+
attr_reader :content_length
|
11
|
+
attr_reader :central_directory
|
12
|
+
attr_reader :file_headers
|
13
|
+
|
14
|
+
def self.get(url, file_name)
|
15
|
+
new(url).data(file_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(url)
|
19
|
+
@uri = URI.parse(url)
|
20
|
+
@files = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def file_headers
|
24
|
+
@file_headers ||= begin
|
25
|
+
raise RuntimeError, "Couldn’t find the central directory." if central_directory.nil?
|
26
|
+
|
27
|
+
headers = {}
|
28
|
+
tmp = central_directory
|
29
|
+
|
30
|
+
begin
|
31
|
+
cd = tmp.unpack('VvvvvvvVVVvvvvvVV')
|
32
|
+
break if cd[1] == 0
|
33
|
+
|
34
|
+
length = 46+cd[10]+cd[11]+cd[12]
|
35
|
+
current_file_name = tmp[46...46+cd[10]]
|
36
|
+
tmp = tmp[length..-1]
|
37
|
+
headers[current_file_name] = cd
|
38
|
+
end while true
|
39
|
+
|
40
|
+
headers
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def data(file_name)
|
45
|
+
raise Errno::ENOENT if file_headers[file_name].nil?
|
46
|
+
|
47
|
+
req = Net::HTTP::Get.new(uri.path)
|
48
|
+
req.set_range(file_headers[file_name][16],
|
49
|
+
file_headers[file_name][16] +
|
50
|
+
file_headers[file_name][8] +
|
51
|
+
file_headers[file_name][10] +
|
52
|
+
file_headers[file_name][11] +
|
53
|
+
file_headers[file_name][12] + 30)
|
54
|
+
|
55
|
+
res = Net::HTTP.start(uri.host, uri.port) do |http|
|
56
|
+
http.request(req)
|
57
|
+
end
|
58
|
+
|
59
|
+
###########################################################################
|
60
|
+
# Local file header
|
61
|
+
###########################################################################
|
62
|
+
|
63
|
+
#0 uint32 localFileHeaderSignature
|
64
|
+
#1 uint16 versionNeededToExtract
|
65
|
+
#2 uint16 generalPurposeBitFlag
|
66
|
+
#3 uint16 compressionMethod
|
67
|
+
#4 uint16 fileLastModificationTime
|
68
|
+
#5 uint16 fileLastModificationDate
|
69
|
+
#6 uint32 CRC32
|
70
|
+
#7 uint32 compressedSize
|
71
|
+
#8 uint32 uncompressedSize
|
72
|
+
#9 uint16 fileNameLength
|
73
|
+
#10 uint16 extraFieldLength
|
74
|
+
|
75
|
+
local_file_header = res.body.unpack('VvvvvvVVVvv')
|
76
|
+
file_data = res.body[30+local_file_header[9]+local_file_header[10]..-1]
|
77
|
+
|
78
|
+
if local_file_header[3] == 0
|
79
|
+
raise NotImplementedError, "Unable to read uncompressed ZIP files for now"
|
80
|
+
else
|
81
|
+
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(file_data)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def central_directory
|
86
|
+
#0 uint32 centralDirectoryFileHeaderSignature
|
87
|
+
#1 uint16 versionMadeBy
|
88
|
+
#2 uint16 versionNeededToExtract
|
89
|
+
#3 uint16 generalPurposeBitFlag
|
90
|
+
#4 uint16 compressionMethod
|
91
|
+
#5 uint16 fileLastModificationTime
|
92
|
+
#6 uint16 fileLastModificationDate
|
93
|
+
#7 uint32 CRC32
|
94
|
+
#8 uint32 compressedSize
|
95
|
+
#9 uint32 uncompressedSize
|
96
|
+
#10 uint16 fileNameLength
|
97
|
+
#11 uint16 extraFieldLength
|
98
|
+
#12 uint16 fileCommentLength
|
99
|
+
#13 uint16 diskNumberWhereFileStarts
|
100
|
+
#14 uint16 internalFileAttributes
|
101
|
+
#15 uint32 externalFileAttributes
|
102
|
+
#16 uint32 relativeOffsetOfLocalFileHeader
|
103
|
+
|
104
|
+
@central_directory ||= begin
|
105
|
+
req = Net::HTTP::Get.new(uri.path)
|
106
|
+
req.set_range(end_of_central_directory_record[5],
|
107
|
+
end_of_central_directory_record[5] +
|
108
|
+
end_of_central_directory_record[4])
|
109
|
+
|
110
|
+
res = Net::HTTP.start(uri.host, uri.port) { |http|
|
111
|
+
http.request(req)
|
112
|
+
}
|
113
|
+
|
114
|
+
if [200, 206].include?(res.code)
|
115
|
+
raise RuntimeError, "Couldn’t find the ZIP file (HTTP: #{res.code})"
|
116
|
+
else
|
117
|
+
res.body
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Retrieve the content length of the file
|
123
|
+
def content_length
|
124
|
+
@content_length ||= Net::HTTP.start(@uri.host, @uri.port) { |http|
|
125
|
+
http.head(@uri.path)
|
126
|
+
}['Content-Length'].to_i
|
127
|
+
end
|
128
|
+
|
129
|
+
def end_of_central_directory_record
|
130
|
+
#0 uint16 numberOfThisDisk;
|
131
|
+
#1 uint16 diskWhereCentralDirectoryStarts;
|
132
|
+
#2 uint16 numberOfCentralDirectoryRecordsOnThisDisk;
|
133
|
+
#3 uint16 totalNumberOfCentralDirectoryRecords;
|
134
|
+
#4 uint32 sizeOfCentralDirectory;
|
135
|
+
#5 uint32 offsetOfStartOfCentralDirectory;
|
136
|
+
#6 uint16 ZIPfileCommentLength;
|
137
|
+
|
138
|
+
@end_of_central_directory_record ||= begin
|
139
|
+
# Retrieve a 4k of data from the end of the zip file
|
140
|
+
request = Net::HTTP::Get.new(uri.path)
|
141
|
+
offset = content_length >= 4096 ? content_length-4096 : 0
|
142
|
+
|
143
|
+
request.set_range(offset, content_length)
|
144
|
+
|
145
|
+
response = Net::HTTP.start(uri.host, uri.port) do |http|
|
146
|
+
http.request(request)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Unpack the body into a hex string
|
150
|
+
hex = response.body.unpack("H*")[0]
|
151
|
+
|
152
|
+
# Split on the end record signature, and unpack the last one
|
153
|
+
[hex.split("504b0506").last].pack("H*").unpack("vvvvVVv")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/spec/pinch_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'minitest/pride'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/spec'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + '/../lib/pinch'
|
6
|
+
|
7
|
+
describe Pinch do
|
8
|
+
describe "when calling get" do
|
9
|
+
it "should return the contents of the file" do
|
10
|
+
url = 'http://ftp.sunet.se/pub/lang/smalltalk/Squeak/current_stable/Squeak3.8-6665-full.zip'
|
11
|
+
file = 'ReadMe.txt'
|
12
|
+
data = Pinch.get url, file
|
13
|
+
data.must_match /Morphic graphics architecture/
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not pinch from a zip file that is not deflated (for now)" do
|
17
|
+
lambda {
|
18
|
+
url = 'http://memention.com/ericjohnson-canabalt-ios-ef43b7d.zip'
|
19
|
+
file = 'ericjohnson-canabalt-ios-ef43b7d/README.TXT'
|
20
|
+
data = Pinch.get url, file
|
21
|
+
}.must_raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pinch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Hellberg
|
9
|
+
- Edward Patel
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-07-03 00:00:00.000000000 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: minitest
|
18
|
+
requirement: &2156678820 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *2156678820
|
27
|
+
description: Pinch makes it possible to download a specific file from within a ZIP
|
28
|
+
file over HTTP 1.1.
|
29
|
+
email: peter@c7.se
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.rdoc
|
34
|
+
- MIT-LICENSE
|
35
|
+
files:
|
36
|
+
- lib/pinch.rb
|
37
|
+
- MIT-LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- .gemtest
|
41
|
+
- spec/pinch_spec.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: https://github.com/peterhellberg/pinch
|
44
|
+
licenses:
|
45
|
+
- MIT-LICENSE
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options:
|
48
|
+
- --main
|
49
|
+
- README.rdoc
|
50
|
+
- --charset=UTF-8
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.8.7
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.6.2
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Retrieve a file from inside a zip file, over the network!
|
72
|
+
test_files:
|
73
|
+
- spec/pinch_spec.rb
|