ipa_reader 0.5

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/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7@ipa_reader
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.5 / 2010-09-30
2
+
3
+ * Initial Version
4
+
data/README.txt ADDED
@@ -0,0 +1,63 @@
1
+ ipa_reader
2
+ by Nicholas Schlueter
3
+ http://twitter.com/schlu
4
+
5
+ == DESCRIPTION:
6
+
7
+ Reads metadata form iPhone Package Archive Files (ipa).
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ I am using this gem to get version to build the over the air iPhone Ad Hoc distribution plist file.
12
+
13
+ == USAGE:
14
+
15
+ irb > require 'rubygems'
16
+ => true
17
+ irb > require 'ipa_reader'
18
+ => true
19
+ irb > ipa_file = IpaReader::IpaFile.new("/path/to/file.ipa")
20
+ => #<IpaReader::IpaFile:0x1012a9458>
21
+ irb > ipa_file.version
22
+ => "1.2.2.4"
23
+ irb > ipa_file.name
24
+ => "MultiG"
25
+ irb > ipa_file.target_os_version
26
+ => "4.1"
27
+ irb > ipa_file.minimum_os_version
28
+ => "3.1"
29
+ irb > ipa_file.url_schemes
30
+ => []
31
+ irb > ipa_file.bundle_identifier
32
+ => "com.dcrails.multig"
33
+ irb > ipa_file.icon_prerendered
34
+ => false
35
+
36
+ == INSTALL:
37
+
38
+ gem install ipa_reader
39
+
40
+ == LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2010
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+
2
+ begin
3
+ require 'bones'
4
+ rescue LoadError
5
+ abort '### Please install the "bones" gem ###'
6
+ end
7
+
8
+ task :default => 'test:run'
9
+ task 'gem:release' => 'test:run'
10
+
11
+ Bones {
12
+ name 'ipa_reader'
13
+ authors 'Nicholas Schlueter'
14
+ email 'schlueter@gmail.com'
15
+ url 'http://github.com/schlueter/Ipa-Reader'
16
+ depends_on "zip", "2.0.2"
17
+ }
18
+
data/bin/ipa_reader ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib ipa_reader]))
5
+
6
+ # Put your code here
7
+
@@ -0,0 +1,48 @@
1
+ begin
2
+ require 'zip'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'zip'
6
+ end
7
+ module IpaReader
8
+ class IpaFile
9
+ attr_accessor :plist
10
+ def initialize(file_path)
11
+ info_plist_file = nil
12
+ Zip::ZipFile.foreach(file_path) { |f| info_plist_file = f if f.name.match(/\/Info.plist/) }
13
+ self.plist = Plist::Binary.decode_binary_plist(info_plist_file.get_input_stream.read)
14
+ end
15
+
16
+ def version
17
+ plist["CFBundleVersion"]
18
+ end
19
+
20
+ def name
21
+ plist["CFBundleDisplayName"]
22
+ end
23
+
24
+ def target_os_version
25
+ plist["DTPlatformVersion"].match(/[\d\.]*/)[0]
26
+ end
27
+
28
+ def minimum_os_version
29
+ plist["MinimumOSVersion"].match(/[\d\.]*/)[0]
30
+ end
31
+
32
+ def url_schemes
33
+ if plist["CFBundleURLTypes"] && plist["CFBundleURLTypes"][0] && plist["CFBundleURLTypes"][0]["CFBundleURLSchemes"]
34
+ plist["CFBundleURLTypes"][0]["CFBundleURLSchemes"]
35
+ else
36
+ []
37
+ end
38
+ end
39
+
40
+ def bundle_identifier
41
+ plist["CFBundleIdentifier"]
42
+ end
43
+
44
+ def icon_prerendered
45
+ plist["UIPrerenderedIcon"] == true
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,490 @@
1
+ require "date"
2
+ require "nkf"
3
+ require "set"
4
+ require "stringio"
5
+
6
+ module IpaReader
7
+ module Plist
8
+ module Binary
9
+ # Encodes +obj+ as a binary property list. If +obj+ is an Array, Hash, or
10
+ # Set, the property list includes its contents.
11
+ def self.binary_plist(obj)
12
+ encoded_objs = flatten_collection(obj)
13
+ ref_byte_size = min_byte_size(encoded_objs.length - 1)
14
+ encoded_objs.collect! {|o| binary_plist_obj(o, ref_byte_size)}
15
+ # Write header and encoded objects.
16
+ plist = "bplist00" + encoded_objs.join
17
+ # Write offset table.
18
+ offset_table_addr = plist.length
19
+ offset = 8
20
+ offset_table = []
21
+ encoded_objs.each do |o|
22
+ offset_table << offset
23
+ offset += o.length
24
+ end
25
+ offset_byte_size = min_byte_size(offset)
26
+ offset_table.each do |offset|
27
+ plist += pack_int(offset, offset_byte_size)
28
+ end
29
+ # Write trailer.
30
+ plist += "\0\0\0\0\0\0" # Six unused bytes
31
+ plist += [
32
+ offset_byte_size,
33
+ ref_byte_size,
34
+ encoded_objs.length >> 32, encoded_objs.length & 0xffffffff,
35
+ 0, 0, # Index of root object
36
+ offset_table_addr >> 32, offset_table_addr & 0xffffffff
37
+ ].pack("CCNNNNNN")
38
+ plist
39
+ end
40
+
41
+ def self.decode_binary_plist(plist)
42
+ # Check header.
43
+ unless plist[0, 6] == "bplist"
44
+ raise ArgumentError, "argument is not a binary property list"
45
+ end
46
+ version = plist[6, 2]
47
+ unless version == "00"
48
+ raise ArgumentError,
49
+ "don't know how to decode format version #{version}"
50
+ end
51
+ # Read trailer.
52
+ trailer = plist[-26, 26].unpack("CCNNNNNN")
53
+ offset_byte_size = trailer[0]
54
+ ref_byte_size = trailer[1]
55
+ encoded_objs_length = combine_ints(32, trailer[2], trailer[3])
56
+ root_index = combine_ints(32, trailer[4], trailer[5])
57
+ offset_table_addr = combine_ints(32, trailer[6], trailer[7])
58
+ # Decode objects.
59
+ root_offset = offset_for_index(plist, offset_table_addr,
60
+ offset_byte_size, root_index)
61
+ root_obj = decode_binary_plist_obj(plist, root_offset, ref_byte_size)
62
+ unflatten_collection(root_obj, [root_obj], plist, offset_table_addr,
63
+ offset_byte_size, ref_byte_size)
64
+ end
65
+
66
+ private
67
+
68
+ # These marker bytes are prefixed to objects in a binary property list to
69
+ # indicate the type of the object.
70
+ CFBinaryPlistMarkerNull = 0x00 # :nodoc:
71
+ CFBinaryPlistMarkerFalse = 0x08 # :nodoc:
72
+ CFBinaryPlistMarkerTrue = 0x09 # :nodoc:
73
+ CFBinaryPlistMarkerFill = 0x0F # :nodoc:
74
+ CFBinaryPlistMarkerInt = 0x10 # :nodoc:
75
+ CFBinaryPlistMarkerReal = 0x20 # :nodoc:
76
+ CFBinaryPlistMarkerDate = 0x33 # :nodoc:
77
+ CFBinaryPlistMarkerData = 0x40 # :nodoc:
78
+ CFBinaryPlistMarkerASCIIString = 0x50 # :nodoc:
79
+ CFBinaryPlistMarkerUnicode16String = 0x60 # :nodoc:
80
+ CFBinaryPlistMarkerUID = 0x80 # :nodoc:
81
+ CFBinaryPlistMarkerArray = 0xA0 # :nodoc:
82
+ CFBinaryPlistMarkerSet = 0xC0 # :nodoc:
83
+ CFBinaryPlistMarkerDict = 0xD0 # :nodoc:
84
+
85
+ # POSIX uses a reference time of 1970-01-01T00:00:00Z; Cocoa's reference
86
+ # time is in 2001. This interval is for converting between the two.
87
+ NSTimeIntervalSince1970 = 978307200.0 # :nodoc:
88
+
89
+ # Takes an object (nominally a collection, like an Array, Set, or Hash, but
90
+ # any object is acceptable) and flattens it into a one-dimensional array.
91
+ # Non-collection objects appear in the array as-is, but the contents of
92
+ # Arrays, Sets, and Hashes are modified like so: (1) The contents of the
93
+ # collection are added, one-by-one, to the one-dimensional array. (2) The
94
+ # collection itself is modified so that it contains indexes pointing to the
95
+ # objects in the one-dimensional array. Here's an example with an Array:
96
+ #
97
+ # ary = [:a, :b, :c]
98
+ # flatten_collection(ary) # => [[1, 2, 3], :a, :b, :c]
99
+ #
100
+ # In the case of a Hash, keys and values are both appended to the one-
101
+ # dimensional array and then replaced with indexes.
102
+ #
103
+ # hsh = {:a => "blue", :b => "purple", :c => "green"}
104
+ # flatten_collection(hsh)
105
+ # # => [{1 => 2, 3 => 4, 5 => 6}, :a, "blue", :b, "purple", :c, "green"]
106
+ #
107
+ # An object will never be added to the one-dimensional array twice. If a
108
+ # collection refers to an object more than once, the object will be added
109
+ # to the one-dimensional array only once.
110
+ #
111
+ # ary = [:a, :a, :a]
112
+ # flatten_collection(ary) # => [[1, 1, 1], :a]
113
+ #
114
+ # The +obj_list+ and +id_refs+ parameters are private; they're used for
115
+ # descending into sub-collections recursively.
116
+ def self.flatten_collection(collection, obj_list = [], id_refs = {})
117
+ case collection
118
+ when Array, Set
119
+ if id_refs[collection.object_id]
120
+ return obj_list[id_refs[collection.object_id]]
121
+ end
122
+ obj_refs = collection.class.new
123
+ id_refs[collection.object_id] = obj_list.length
124
+ obj_list << obj_refs
125
+ collection.each do |obj|
126
+ flatten_collection(obj, obj_list, id_refs)
127
+ obj_refs << id_refs[obj.object_id]
128
+ end
129
+ return obj_list
130
+ when Hash
131
+ if id_refs[collection.object_id]
132
+ return obj_list[id_refs[collection.object_id]]
133
+ end
134
+ obj_refs = {}
135
+ id_refs[collection.object_id] = obj_list.length
136
+ obj_list << obj_refs
137
+ collection.each do |key, value|
138
+ key = key.to_s if key.is_a?(Symbol)
139
+ flatten_collection(key, obj_list, id_refs)
140
+ flatten_collection(value, obj_list, id_refs)
141
+ obj_refs[id_refs[key.object_id]] = id_refs[value.object_id]
142
+ end
143
+ return obj_list
144
+ else
145
+ unless id_refs[collection.object_id]
146
+ id_refs[collection.object_id] = obj_list.length
147
+ obj_list << collection
148
+ end
149
+ return obj_list
150
+ end
151
+ end
152
+
153
+ def self.unflatten_collection(collection, obj_list, plist,
154
+ offset_table_addr, offset_byte_size, ref_byte_size)
155
+ case collection
156
+ when Array, Set
157
+ collection.collect! do |index|
158
+ if obj = obj_list[index]
159
+ obj
160
+ else
161
+ offset = offset_for_index(plist, offset_table_addr, offset_byte_size,
162
+ index)
163
+ obj = decode_binary_plist_obj(plist, offset, ref_byte_size)
164
+ obj_list[index] = obj
165
+ unflatten_collection(obj, obj_list, plist, offset_table_addr,
166
+ offset_byte_size, ref_byte_size)
167
+ end
168
+ end
169
+ when Hash
170
+ hsh = {}
171
+ collection.each do |key, value|
172
+ unless key_obj = obj_list[key]
173
+ offset = offset_for_index(plist, offset_table_addr, offset_byte_size,
174
+ key)
175
+ key_obj = decode_binary_plist_obj(plist, offset, ref_byte_size)
176
+ obj_list[key] = key_obj
177
+ key_obj = unflatten_collection(key_obj, obj_list, plist,
178
+ offset_table_addr, offset_byte_size, ref_byte_size)
179
+ end
180
+ unless value_obj = obj_list[value]
181
+ offset = offset_for_index(plist, offset_table_addr, offset_byte_size,
182
+ value)
183
+ value_obj = decode_binary_plist_obj(plist, offset, ref_byte_size)
184
+ obj_list[value] = value_obj
185
+ value_obj = unflatten_collection(value_obj, obj_list, plist,
186
+ offset_table_addr, offset_byte_size, ref_byte_size)
187
+ end
188
+ hsh[key_obj] = value_obj
189
+ end
190
+ collection.replace(hsh)
191
+ end
192
+ return collection
193
+ end
194
+
195
+ # Returns a binary property list fragment that represents +obj+. The
196
+ # returned string is not a complete property list, just a fragment that
197
+ # describes +obj+, and is not useful without a header, offset table, and
198
+ # trailer.
199
+ #
200
+ # The following classes are recognized: String, Float, Integer, the Boolean
201
+ # classes, Time, IO, StringIO, Array, Set, and Hash. IO and StringIO
202
+ # objects are rewound, read, and the contents stored as data (i.e., Cocoa
203
+ # applications will decode them as NSData). All other classes are dumped
204
+ # with Marshal and stored as data.
205
+ #
206
+ # Note that subclasses of the supported classes will be encoded as though
207
+ # they were the supported superclass. Thus, a subclass of (for example)
208
+ # String will be encoded and decoded as a String, not as the subclass:
209
+ #
210
+ # class ExampleString < String
211
+ # ...
212
+ # end
213
+ #
214
+ # s = ExampleString.new("disquieting plantlike mystery")
215
+ # encoded_s = binary_plist_obj(s)
216
+ # decoded_s = decode_binary_plist_obj(encoded_s)
217
+ # puts decoded_s.class # => String
218
+ #
219
+ # +ref_byte_size+ is the number of bytes to use for storing references to
220
+ # other objects.
221
+ def self.binary_plist_obj(obj, ref_byte_size = 4)
222
+ case obj
223
+ when String
224
+ obj = obj.to_s if obj.is_a?(Symbol)
225
+ # This doesn't really work. NKF's guess method is really, really bad
226
+ # at discovering UTF8 when only a handful of characters are multi-byte.
227
+ encoding = NKF.guess2(obj)
228
+ if encoding == NKF::ASCII && obj =~ /[\x80-\xff]/
229
+ encoding = NKF::UTF8
230
+ end
231
+ if [NKF::ASCII, NKF::BINARY, NKF::UNKNOWN].include?(encoding)
232
+ result = (CFBinaryPlistMarkerASCIIString |
233
+ (obj.length < 15 ? obj.length : 0xf)).chr
234
+ result += binary_plist_obj(obj.length) if obj.length >= 15
235
+ result += obj
236
+ return result
237
+ else
238
+ # Convert to UTF8.
239
+ if encoding == NKF::UTF8
240
+ utf8 = obj
241
+ else
242
+ utf8 = NKF.nkf("-m0 -w", obj)
243
+ end
244
+ # Decode each character's UCS codepoint.
245
+ codepoints = []
246
+ i = 0
247
+ while i < utf8.length
248
+ byte = utf8[i]
249
+ if byte & 0xe0 == 0xc0
250
+ codepoints << ((byte & 0x1f) << 6) + (utf8[i+1] & 0x3f)
251
+ i += 1
252
+ elsif byte & 0xf0 == 0xe0
253
+ codepoints << ((byte & 0xf) << 12) + ((utf8[i+1] & 0x3f) << 6) +
254
+ (utf8[i+2] & 0x3f)
255
+ i += 2
256
+ elsif byte & 0xf8 == 0xf0
257
+ codepoints << ((byte & 0xe) << 18) + ((utf8[i+1] & 0x3f) << 12) +
258
+ ((utf8[i+2] & 0x3f) << 6) + (utf8[i+3] & 0x3f)
259
+ i += 3
260
+ else
261
+ codepoints << byte
262
+ end
263
+ if codepoints.last > 0xffff
264
+ raise(ArgumentError, "codepoint too high - only the Basic Multilingual Plane can be encoded")
265
+ end
266
+ i += 1
267
+ end
268
+ # Return string of 16-bit codepoints.
269
+ data = codepoints.pack("n*")
270
+ result = (CFBinaryPlistMarkerUnicode16String |
271
+ (codepoints.length < 15 ? codepoints.length : 0xf)).chr
272
+ result += binary_plist_obj(codepoints.length) if codepoints.length >= 15
273
+ result += data
274
+ return result
275
+ end
276
+ when Float
277
+ return (CFBinaryPlistMarkerReal | 3).chr + [obj].pack("G")
278
+ when Integer
279
+ nbytes = min_byte_size(obj)
280
+ size_bits = { 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4 }[nbytes]
281
+ return (CFBinaryPlistMarkerInt | size_bits).chr + pack_int(obj, nbytes)
282
+ when TrueClass
283
+ return CFBinaryPlistMarkerTrue.chr
284
+ when FalseClass
285
+ return CFBinaryPlistMarkerFalse.chr
286
+ when Time
287
+ return CFBinaryPlistMarkerDate.chr +
288
+ [obj.to_f - NSTimeIntervalSince1970].pack("G")
289
+ when IO, StringIO
290
+ obj.rewind
291
+ return binary_plist_data(obj.read)
292
+ when Array
293
+ # Must be an array of object references as returned by flatten_collection.
294
+ result = (CFBinaryPlistMarkerArray | (obj.length < 15 ? obj.length : 0xf)).chr
295
+ result += binary_plist_obj(obj.length) if obj.length >= 15
296
+ result += obj.collect! { |i| pack_int(i, ref_byte_size) }.join
297
+ when Set
298
+ # Must be a set of object references as returned by flatten_collection.
299
+ result = (CFBinaryPlistMarkerSet | (obj.length < 15 ? obj.length : 0xf)).chr
300
+ result += binary_plist_obj(obj.length) if obj.length >= 15
301
+ result += obj.to_a.collect! { |i| pack_int(i, ref_byte_size) }.join
302
+ when Hash
303
+ # Must be a table of object references as returned by flatten_collection.
304
+ result = (CFBinaryPlistMarkerDict | (obj.length < 15 ? obj.length : 0xf)).chr
305
+ result += binary_plist_obj(obj.length) if obj.length >= 15
306
+ result += obj.keys.collect! { |i| pack_int(i, ref_byte_size) }.join
307
+ result += obj.values.collect! { |i| pack_int(i, ref_byte_size) }.join
308
+ else
309
+ return binary_plist_data(Marshal.dump(obj))
310
+ end
311
+ end
312
+
313
+ def self.decode_binary_plist_obj(plist, offset, ref_byte_size)
314
+ case plist[offset]
315
+ when CFBinaryPlistMarkerASCIIString..(CFBinaryPlistMarkerASCIIString | 0xf)
316
+ length, offset = decode_length(plist, offset)
317
+ return plist[offset, length]
318
+ when CFBinaryPlistMarkerUnicode16String..(CFBinaryPlistMarkerUnicode16String | 0xf)
319
+ length, offset = decode_length(plist, offset)
320
+ codepoints = plist[offset, length * 2].unpack("n*")
321
+ str = ""
322
+ codepoints.each do |codepoint|
323
+ if codepoint <= 0x7f
324
+ ch = ' '
325
+ ch[0] = to_i
326
+ elsif codepoint <= 0x7ff
327
+ ch = ' '
328
+ ch[0] = ((codepoint & 0x7c0) >> 6) | 0xc0
329
+ ch[1] = codepoint & 0x3f | 0x80
330
+ else
331
+ ch = ' '
332
+ ch[0] = ((codepoint & 0xf000) >> 12) | 0xe0
333
+ ch[1] = ((codepoint & 0xfc0) >> 6) | 0x80
334
+ ch[2] = codepoint & 0x3f | 0x80
335
+ end
336
+ str << ch
337
+ end
338
+ return str
339
+ when CFBinaryPlistMarkerReal | 3
340
+ return plist[offset+1, 8].unpack("G").first
341
+ when CFBinaryPlistMarkerInt..(CFBinaryPlistMarkerInt | 0xf)
342
+ num_bytes = 2 ** (plist[offset] & 0xf)
343
+ return unpack_int(plist[offset+1, num_bytes])
344
+ when CFBinaryPlistMarkerTrue
345
+ return true
346
+ when CFBinaryPlistMarkerFalse
347
+ return false
348
+ when CFBinaryPlistMarkerDate
349
+ secs = plist[offset+1, 8].unpack("G").first + NSTimeIntervalSince1970
350
+ return Time.at(secs)
351
+ when CFBinaryPlistMarkerData..(CFBinaryPlistMarkerData | 0xf)
352
+ length, offset = decode_length(plist, offset)
353
+ return StringIO.new(plist[offset, length])
354
+ when CFBinaryPlistMarkerArray..(CFBinaryPlistMarkerArray | 0xf)
355
+ ary = []
356
+ length, offset = decode_length(plist, offset)
357
+ length.times do
358
+ ary << unpack_int(plist[offset, ref_byte_size])
359
+ offset += ref_byte_size
360
+ end
361
+ return ary
362
+ when CFBinaryPlistMarkerDict..(CFBinaryPlistMarkerDict | 0xf)
363
+ hsh = {}
364
+ keys = []
365
+ length, offset = decode_length(plist, offset)
366
+ length.times do
367
+ keys << unpack_int(plist[offset, ref_byte_size])
368
+ offset += ref_byte_size
369
+ end
370
+ length.times do |i|
371
+ hsh[keys[i]] = unpack_int(plist[offset, ref_byte_size])
372
+ offset += ref_byte_size
373
+ end
374
+ return hsh
375
+ end
376
+ end
377
+
378
+ # Returns a binary property list fragment that represents a data object
379
+ # with the contents of the string +data+. A Cocoa application would decode
380
+ # this fragment as NSData. Like binary_plist_obj, the value returned by
381
+ # this method is not usable by itself; it is only useful as part of a
382
+ # complete binary property list with a header, offset table, and trailer.
383
+ def self.binary_plist_data(data)
384
+ result = (CFBinaryPlistMarkerData |
385
+ (data.length < 15 ? data.length : 0xf)).chr
386
+ result += binary_plist_obj(data.length) if data.length > 15
387
+ result += data
388
+ return result
389
+ end
390
+
391
+ # Determines the minimum number of bytes that is a power of two and can
392
+ # represent the integer +i+. Raises a RangeError if the number of bytes
393
+ # exceeds 16. Note that the property list format considers integers of 1,
394
+ # 2, and 4 bytes to be unsigned, while 8- and 16-byte integers are signed;
395
+ # thus negative integers will always require at least 8 bytes of storage.
396
+ def self.min_byte_size(i)
397
+ if i < 0
398
+ i = i.abs - 1
399
+ else
400
+ if i <= 0xff
401
+ return 1
402
+ elsif i <= 0xffff
403
+ return 2
404
+ elsif i <= 0xffffffff
405
+ return 4
406
+ end
407
+ end
408
+ if i <= 0x7fffffffffffffff
409
+ return 8
410
+ elsif i <= 0x7fffffffffffffffffffffffffffffff
411
+ return 16
412
+ end
413
+ raise(RangeError, "integer too big - exceeds 128 bits")
414
+ end
415
+
416
+ # Packs an integer +i+ into its binary representation in the specified
417
+ # number of bytes. Byte order is big-endian. Negative integers cannot be
418
+ # stored in 1, 2, or 4 bytes.
419
+ def self.pack_int(i, num_bytes)
420
+ if i < 0 && num_bytes < 8
421
+ raise(ArgumentError, "negative integers require 8 or 16 bytes of storage")
422
+ end
423
+ case num_bytes
424
+ when 1
425
+ [i].pack("c")
426
+ when 2
427
+ [i].pack("n")
428
+ when 4
429
+ [i].pack("N")
430
+ when 8
431
+ [(i >> 32) & 0xffffffff, i & 0xffffffff].pack("NN")
432
+ when 16
433
+ [i >> 96, (i >> 64) & 0xffffffff, (i >> 32) & 0xffffffff,
434
+ i & 0xffffffff].pack("NNNN")
435
+ else
436
+ raise(ArgumentError, "num_bytes must be 1, 2, 4, 8, or 16")
437
+ end
438
+ end
439
+
440
+ def self.combine_ints(num_bits, *ints)
441
+ i = ints.pop
442
+ shift_bits = num_bits
443
+ ints.reverse.each do |i_part|
444
+ i += i_part << shift_bits
445
+ shift_bits += num_bits
446
+ end
447
+ return i
448
+ end
449
+
450
+ def self.offset_for_index(plist, table_addr, offset_byte_size, index)
451
+ offset = plist[table_addr + index * offset_byte_size, offset_byte_size]
452
+ unpack_int(offset)
453
+ end
454
+
455
+ def self.unpack_int(s)
456
+ case s.length
457
+ when 1
458
+ s.unpack("C").first
459
+ when 2
460
+ s.unpack("n").first
461
+ when 4
462
+ s.unpack("N").first
463
+ when 8
464
+ i = combine_ints(32, *(s.unpack("NN")))
465
+ (i & 0x80000000_00000000 == 0) ?
466
+ i :
467
+ -(i ^ 0xffffffff_ffffffff) - 1
468
+ when 16
469
+ i = combine_ints(32, *(s.unpack("NNNN")))
470
+ (i & 0x80000000_00000000_00000000_00000000 == 0) ?
471
+ i :
472
+ -(i ^ 0xffffffff_ffffffff_ffffffff_ffffffff) - 1
473
+ else
474
+ raise(ArgumentError, "length must be 1, 2, 4, 8, or 16 bytes")
475
+ end
476
+ end
477
+
478
+ def self.decode_length(plist, offset)
479
+ if plist[offset] & 0xf == 0xf
480
+ offset += 1
481
+ length = decode_binary_plist_obj(plist, offset, 0)
482
+ offset += min_byte_size(length) + 1
483
+ return length, offset
484
+ else
485
+ return (plist[offset] & 0xf), (offset + 1)
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end
data/lib/ipa_reader.rb ADDED
@@ -0,0 +1,65 @@
1
+
2
+ module IpaReader
3
+
4
+ # :stopdoc:
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ @version ||= File.read(path('version.txt')).strip
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args, &block )
20
+ rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ if block
22
+ begin
23
+ $LOAD_PATH.unshift LIBPATH
24
+ rv = block.call
25
+ ensure
26
+ $LOAD_PATH.shift
27
+ end
28
+ end
29
+ return rv
30
+ end
31
+
32
+ # Returns the lpath for the module. If any arguments are given,
33
+ # they will be joined to the end of the path using
34
+ # <tt>File.join</tt>.
35
+ #
36
+ def self.path( *args, &block )
37
+ rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
38
+ if block
39
+ begin
40
+ $LOAD_PATH.unshift PATH
41
+ rv = block.call
42
+ ensure
43
+ $LOAD_PATH.shift
44
+ end
45
+ end
46
+ return rv
47
+ end
48
+
49
+ # Utility method used to require all files ending in .rb that lie in the
50
+ # directory below this file that has the same name as the filename passed
51
+ # in. Optionally, a specific _directory_ name can be passed in such that
52
+ # the _filename_ does not have to be equivalent to the directory.
53
+ #
54
+ def self.require_all_libs_relative_to( fname, dir = nil )
55
+ dir ||= ::File.basename(fname, '.*')
56
+ search_me = ::File.expand_path(
57
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
58
+
59
+ Dir.glob(search_me).sort.each {|rb| require rb}
60
+ end
61
+
62
+ end # module IpaReader
63
+
64
+ IpaReader.require_all_libs_relative_to(__FILE__)
65
+
@@ -0,0 +1,6 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ describe IpaReader do
5
+ end
6
+
@@ -0,0 +1,15 @@
1
+
2
+ require File.expand_path(
3
+ File.join(File.dirname(__FILE__), %w[.. lib ipa_reader]))
4
+
5
+ Spec::Runner.configure do |config|
6
+ # == Mock Framework
7
+ #
8
+ # RSpec uses it's own mocking framework by default. If you prefer to
9
+ # use mocha, flexmock or RR, uncomment the appropriate line:
10
+ #
11
+ # config.mock_with :mocha
12
+ # config.mock_with :flexmock
13
+ # config.mock_with :rr
14
+ end
15
+
data/test/MultiG.ipa ADDED
Binary file
@@ -0,0 +1,42 @@
1
+ require File.dirname(__FILE__) + '/../lib/ipa_reader'
2
+ require 'test/unit'
3
+
4
+ class IpaReaderTest < Test::Unit::TestCase
5
+ def setup
6
+ @ipa_file = IpaReader::IpaFile.new(File.dirname(__FILE__) + '/MultiG.ipa')
7
+ end
8
+
9
+ def test_parse
10
+ assert(@ipa_file.plist.keys.count > 0)
11
+ end
12
+
13
+ def test_version
14
+ assert_equal(@ipa_file.version, "1.2.2.4")
15
+ end
16
+
17
+ def test_name
18
+ assert_equal(@ipa_file.name, "MultiG")
19
+ end
20
+
21
+ def test_target_os_version
22
+ assert_equal(@ipa_file.target_os_version, "4.1")
23
+ end
24
+
25
+ def test_minimum_os_version
26
+ assert_equal(@ipa_file.minimum_os_version, "3.1")
27
+ end
28
+
29
+ def test_url_schemes
30
+ assert_equal(@ipa_file.url_schemes, [])
31
+ end
32
+
33
+ def test_bundle_identifier
34
+ assert_equal("com.dcrails.multig", @ipa_file.bundle_identifier)
35
+ end
36
+
37
+ def test_icon_prerendered
38
+ assert_equal(false, @ipa_file.icon_prerendered)
39
+ end
40
+
41
+
42
+ end
data/version.txt ADDED
@@ -0,0 +1 @@
1
+ 0.5
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ipa_reader
3
+ version: !ruby/object:Gem::Version
4
+ hash: 1
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 5
9
+ version: "0.5"
10
+ platform: ruby
11
+ authors:
12
+ - Nicholas Schlueter
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-30 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bones
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 25
29
+ segments:
30
+ - 3
31
+ - 4
32
+ - 7
33
+ version: 3.4.7
34
+ type: :development
35
+ version_requirements: *id001
36
+ description: Reads metadata form iPhone Package Archive Files (ipa).
37
+ email: schlueter@gmail.com
38
+ executables:
39
+ - ipa_reader
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - README.txt
45
+ - bin/ipa_reader
46
+ - version.txt
47
+ files:
48
+ - .rvmrc
49
+ - History.txt
50
+ - README.txt
51
+ - Rakefile
52
+ - bin/ipa_reader
53
+ - lib/ipa_reader.rb
54
+ - lib/ipa_reader/ipa_file.rb
55
+ - lib/ipa_reader/plist_binary.rb
56
+ - spec/ipa_reader_spec.rb
57
+ - spec/spec_helper.rb
58
+ - test/MultiG.ipa
59
+ - test/test_ipa_reader.rb
60
+ - version.txt
61
+ has_rdoc: true
62
+ homepage: http://github.com/schlueter/Ipa-Reader
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --main
68
+ - README.txt
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project: ipa_reader
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Reads metadata form iPhone Package Archive Files (ipa)
96
+ test_files:
97
+ - test/test_ipa_reader.rb