ipa_reader 0.5

Sign up to get free protection for your applications and to get access to all the features.
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