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.
Files changed (7) hide show
  1. data/.gemtest +0 -0
  2. data/MIT-LICENSE +19 -0
  3. data/README.rdoc +48 -0
  4. data/Rakefile +9 -0
  5. data/lib/pinch.rb +156 -0
  6. data/spec/pinch_spec.rb +24 -0
  7. 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
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.test_files = FileList['spec/*_spec.rb']
7
+ t.ruby_opts = ['-rubygems'] if defined? Gem
8
+ t.ruby_opts << '-I.'
9
+ end
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
@@ -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