appsendr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest ADDED
File without changes
data/README.rdoc ADDED
@@ -0,0 +1,50 @@
1
+ # == Synopsis
2
+ # Ruby command-line app that interacts with the Apple Developer Portal, Xcode, and the AppSendr webservice
3
+ #
4
+ # == Examples
5
+ # Calling this in a Xcode project directory will build the project with an active configuration of AdHoc
6
+ # appsendr
7
+ #
8
+ # Other examples:
9
+ # appsendr -q
10
+ # appsendr --verbose
11
+ #
12
+ # == Usage
13
+ # appsendr [options]
14
+ #
15
+ # For help use: appsendr -h
16
+ #
17
+ # == Options
18
+ # -c, --active_config Sets the active configuration for the XCode project
19
+ # -u, --username Apple Developer portal username
20
+ # -p, --password Apple Developer portal password
21
+ # -d, --devices Path to profiles if different then devices.ad
22
+ # -h, --help Displays help message
23
+ # -v, --version Display the version, then exit
24
+ # -q, --quiet Output as little as possible, overrides verbose
25
+ # -V, --verbose Verbose output
26
+ #
27
+ #
28
+ # == Author
29
+ # Nolan Brown
30
+ #
31
+ # == Copyright
32
+ # Copyright (c) 2010 Nolan Brown. Licensed under the MIT License:
33
+ # http://www.opensource.org/licenses/mit-license.php
34
+
35
+
36
+ === General Commands
37
+
38
+ help # show this usage
39
+ version # show the gem version
40
+
41
+ build <active configuration> # build and deploy app
42
+ build:clean # clean project
43
+
44
+ testers # list testers for app
45
+ testers:add <email> <name> #t
46
+ testers:remove <email>
47
+ testers:clear
48
+
49
+ deploy <active configuration> #deploy the current build
50
+ deploy:notify #notify testers about the latest build
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+ require 'lib/appsendr/constants'
5
+
6
+ Echoe.new('appsendr', AppSendr::VERSION) do |p|
7
+ p.description = "A gem that will build and distribute an iphone/ipad app to the web for remote install"
8
+ p.url = "http://www.appsendr.com/gem"
9
+ p.author = "AppSendr"
10
+ p.email = "nolanbrown@gmail.com"
11
+ p.ignore_pattern = ["tmp/*", "script/*"]
12
+ p.development_dependencies = []
13
+ p.runtime_dependencies = ["rest-client >=1.4.0", "rubyzip", "json_pure >=1.2.0"]
14
+ p.has_rdoc = true
15
+
16
+ end
data/appsendr.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{appsendr}
5
+ s.version = "0.0.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["AppSendr"]
9
+ s.date = %q{2010-11-20}
10
+ s.default_executable = %q{appsendr}
11
+ s.description = %q{A gem that will build and distribute an iphone/ipad app to the web for remote install}
12
+ s.email = %q{nolanbrown@gmail.com}
13
+ s.executables = ["appsendr"]
14
+ s.extra_rdoc_files = ["README.rdoc", "bin/appsendr", "lib/appsendr.rb", "lib/appsendr/binary_plist.rb", "lib/appsendr/client.rb", "lib/appsendr/command.rb", "lib/appsendr/commands/app.rb", "lib/appsendr/commands/auth.rb", "lib/appsendr/commands/base.rb", "lib/appsendr/commands/build.rb", "lib/appsendr/commands/deploy.rb", "lib/appsendr/commands/help.rb", "lib/appsendr/commands/testers.rb", "lib/appsendr/commands/version.rb", "lib/appsendr/constants.rb", "lib/appsendr/helpers.rb"]
15
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "appsendr.gemspec", "bin/appsendr", "lib/appsendr.rb", "lib/appsendr/binary_plist.rb", "lib/appsendr/client.rb", "lib/appsendr/command.rb", "lib/appsendr/commands/app.rb", "lib/appsendr/commands/auth.rb", "lib/appsendr/commands/base.rb", "lib/appsendr/commands/build.rb", "lib/appsendr/commands/deploy.rb", "lib/appsendr/commands/help.rb", "lib/appsendr/commands/testers.rb", "lib/appsendr/commands/version.rb", "lib/appsendr/constants.rb", "lib/appsendr/helpers.rb"]
16
+ s.homepage = %q{http://www.appsendr.com/gem}
17
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Appsendr", "--main", "README.rdoc"]
18
+ s.require_paths = ["lib"]
19
+ s.rubyforge_project = %q{appsendr}
20
+ s.rubygems_version = %q{1.3.7}
21
+ s.summary = %q{A gem that will build and distribute an iphone/ipad app to the web for remote install}
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 3
26
+
27
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
28
+ s.add_runtime_dependency(%q<rest-client>, [">= 1.4.0"])
29
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0"])
30
+ s.add_runtime_dependency(%q<json_pure>, [">= 1.2.0"])
31
+ else
32
+ s.add_dependency(%q<rest-client>, [">= 1.4.0"])
33
+ s.add_dependency(%q<rubyzip>, [">= 0"])
34
+ s.add_dependency(%q<json_pure>, [">= 1.2.0"])
35
+ end
36
+ else
37
+ s.add_dependency(%q<rest-client>, [">= 1.4.0"])
38
+ s.add_dependency(%q<rubyzip>, [">= 0"])
39
+ s.add_dependency(%q<json_pure>, [">= 1.2.0"])
40
+ end
41
+ end
data/bin/appsendr ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'appsendr'
7
+ require 'appsendr/command'
8
+
9
+ args = ARGV.dup
10
+ ARGV.clear
11
+ command = args.shift.strip rescue 'help'
12
+
13
+ AppSendr::Command.run(command, args)
14
+
@@ -0,0 +1,514 @@
1
+ require "date"
2
+ require "nkf"
3
+ require "set"
4
+ require "stringio"
5
+
6
+ ## https://github.com/schlueter/Ipa-Reader/blob/master/lib/ipa_reader/ipa_file.rb
7
+ # (The MIT License)
8
+ #
9
+ # Copyright (c) 2010
10
+ #
11
+ # Permission is hereby granted, free of charge, to any person obtaining
12
+ # a copy of this software and associated documentation files (the
13
+ # 'Software'), to deal in the Software without restriction, including
14
+ # without limitation the rights to use, copy, modify, merge, publish,
15
+ # distribute, sublicense, and/or sell copies of the Software, and to
16
+ # permit persons to whom the Software is furnished to do so, subject to
17
+ # the following conditions:
18
+ #
19
+ # The above copyright notice and this permission notice shall be
20
+ # included in all copies or substantial portions of the Software.
21
+ #
22
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
23
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
+
30
+ module AppSendr
31
+ module Plist
32
+ module Binary
33
+ # Encodes +obj+ as a binary property list. If +obj+ is an Array, Hash, or
34
+ # Set, the property list includes its contents.
35
+ def self.binary_plist(obj)
36
+ encoded_objs = flatten_collection(obj)
37
+ ref_byte_size = min_byte_size(encoded_objs.length - 1)
38
+ encoded_objs.collect! {|o| binary_plist_obj(o, ref_byte_size)}
39
+ # Write header and encoded objects.
40
+ plist = "bplist00" + encoded_objs.join
41
+ # Write offset table.
42
+ offset_table_addr = plist.length
43
+ offset = 8
44
+ offset_table = []
45
+ encoded_objs.each do |o|
46
+ offset_table << offset
47
+ offset += o.length
48
+ end
49
+ offset_byte_size = min_byte_size(offset)
50
+ offset_table.each do |offset|
51
+ plist += pack_int(offset, offset_byte_size)
52
+ end
53
+ # Write trailer.
54
+ plist += "\0\0\0\0\0\0" # Six unused bytes
55
+ plist += [
56
+ offset_byte_size,
57
+ ref_byte_size,
58
+ encoded_objs.length >> 32, encoded_objs.length & 0xffffffff,
59
+ 0, 0, # Index of root object
60
+ offset_table_addr >> 32, offset_table_addr & 0xffffffff
61
+ ].pack("CCNNNNNN")
62
+ plist
63
+ end
64
+
65
+ def self.decode_binary_plist(plist)
66
+ # Check header.
67
+ unless plist[0, 6] == "bplist"
68
+ raise ArgumentError, "argument is not a binary property list"
69
+ end
70
+ version = plist[6, 2]
71
+ unless version == "00"
72
+ raise ArgumentError,
73
+ "don't know how to decode format version #{version}"
74
+ end
75
+ # Read trailer.
76
+ trailer = plist[-26, 26].unpack("CCNNNNNN")
77
+ offset_byte_size = trailer[0]
78
+ ref_byte_size = trailer[1]
79
+ encoded_objs_length = combine_ints(32, trailer[2], trailer[3])
80
+ root_index = combine_ints(32, trailer[4], trailer[5])
81
+ offset_table_addr = combine_ints(32, trailer[6], trailer[7])
82
+ # Decode objects.
83
+ root_offset = offset_for_index(plist, offset_table_addr,
84
+ offset_byte_size, root_index)
85
+ root_obj = decode_binary_plist_obj(plist, root_offset, ref_byte_size)
86
+ unflatten_collection(root_obj, [root_obj], plist, offset_table_addr,
87
+ offset_byte_size, ref_byte_size)
88
+ end
89
+
90
+ private
91
+
92
+ # These marker bytes are prefixed to objects in a binary property list to
93
+ # indicate the type of the object.
94
+ CFBinaryPlistMarkerNull = 0x00 # :nodoc:
95
+ CFBinaryPlistMarkerFalse = 0x08 # :nodoc:
96
+ CFBinaryPlistMarkerTrue = 0x09 # :nodoc:
97
+ CFBinaryPlistMarkerFill = 0x0F # :nodoc:
98
+ CFBinaryPlistMarkerInt = 0x10 # :nodoc:
99
+ CFBinaryPlistMarkerReal = 0x20 # :nodoc:
100
+ CFBinaryPlistMarkerDate = 0x33 # :nodoc:
101
+ CFBinaryPlistMarkerData = 0x40 # :nodoc:
102
+ CFBinaryPlistMarkerASCIIString = 0x50 # :nodoc:
103
+ CFBinaryPlistMarkerUnicode16String = 0x60 # :nodoc:
104
+ CFBinaryPlistMarkerUID = 0x80 # :nodoc:
105
+ CFBinaryPlistMarkerArray = 0xA0 # :nodoc:
106
+ CFBinaryPlistMarkerSet = 0xC0 # :nodoc:
107
+ CFBinaryPlistMarkerDict = 0xD0 # :nodoc:
108
+
109
+ # POSIX uses a reference time of 1970-01-01T00:00:00Z; Cocoa's reference
110
+ # time is in 2001. This interval is for converting between the two.
111
+ NSTimeIntervalSince1970 = 978307200.0 # :nodoc:
112
+
113
+ # Takes an object (nominally a collection, like an Array, Set, or Hash, but
114
+ # any object is acceptable) and flattens it into a one-dimensional array.
115
+ # Non-collection objects appear in the array as-is, but the contents of
116
+ # Arrays, Sets, and Hashes are modified like so: (1) The contents of the
117
+ # collection are added, one-by-one, to the one-dimensional array. (2) The
118
+ # collection itself is modified so that it contains indexes pointing to the
119
+ # objects in the one-dimensional array. Here's an example with an Array:
120
+ #
121
+ # ary = [:a, :b, :c]
122
+ # flatten_collection(ary) # => [[1, 2, 3], :a, :b, :c]
123
+ #
124
+ # In the case of a Hash, keys and values are both appended to the one-
125
+ # dimensional array and then replaced with indexes.
126
+ #
127
+ # hsh = {:a => "blue", :b => "purple", :c => "green"}
128
+ # flatten_collection(hsh)
129
+ # # => [{1 => 2, 3 => 4, 5 => 6}, :a, "blue", :b, "purple", :c, "green"]
130
+ #
131
+ # An object will never be added to the one-dimensional array twice. If a
132
+ # collection refers to an object more than once, the object will be added
133
+ # to the one-dimensional array only once.
134
+ #
135
+ # ary = [:a, :a, :a]
136
+ # flatten_collection(ary) # => [[1, 1, 1], :a]
137
+ #
138
+ # The +obj_list+ and +id_refs+ parameters are private; they're used for
139
+ # descending into sub-collections recursively.
140
+ def self.flatten_collection(collection, obj_list = [], id_refs = {})
141
+ case collection
142
+ when Array, Set
143
+ if id_refs[collection.object_id]
144
+ return obj_list[id_refs[collection.object_id]]
145
+ end
146
+ obj_refs = collection.class.new
147
+ id_refs[collection.object_id] = obj_list.length
148
+ obj_list << obj_refs
149
+ collection.each do |obj|
150
+ flatten_collection(obj, obj_list, id_refs)
151
+ obj_refs << id_refs[obj.object_id]
152
+ end
153
+ return obj_list
154
+ when Hash
155
+ if id_refs[collection.object_id]
156
+ return obj_list[id_refs[collection.object_id]]
157
+ end
158
+ obj_refs = {}
159
+ id_refs[collection.object_id] = obj_list.length
160
+ obj_list << obj_refs
161
+ collection.each do |key, value|
162
+ key = key.to_s if key.is_a?(Symbol)
163
+ flatten_collection(key, obj_list, id_refs)
164
+ flatten_collection(value, obj_list, id_refs)
165
+ obj_refs[id_refs[key.object_id]] = id_refs[value.object_id]
166
+ end
167
+ return obj_list
168
+ else
169
+ unless id_refs[collection.object_id]
170
+ id_refs[collection.object_id] = obj_list.length
171
+ obj_list << collection
172
+ end
173
+ return obj_list
174
+ end
175
+ end
176
+
177
+ def self.unflatten_collection(collection, obj_list, plist,
178
+ offset_table_addr, offset_byte_size, ref_byte_size)
179
+ case collection
180
+ when Array, Set
181
+ collection.collect! do |index|
182
+ if obj = obj_list[index]
183
+ obj
184
+ else
185
+ offset = offset_for_index(plist, offset_table_addr, offset_byte_size,
186
+ index)
187
+ obj = decode_binary_plist_obj(plist, offset, ref_byte_size)
188
+ obj_list[index] = obj
189
+ unflatten_collection(obj, obj_list, plist, offset_table_addr,
190
+ offset_byte_size, ref_byte_size)
191
+ end
192
+ end
193
+ when Hash
194
+ hsh = {}
195
+ collection.each do |key, value|
196
+ unless key_obj = obj_list[key]
197
+ offset = offset_for_index(plist, offset_table_addr, offset_byte_size,
198
+ key)
199
+ key_obj = decode_binary_plist_obj(plist, offset, ref_byte_size)
200
+ obj_list[key] = key_obj
201
+ key_obj = unflatten_collection(key_obj, obj_list, plist,
202
+ offset_table_addr, offset_byte_size, ref_byte_size)
203
+ end
204
+ unless value_obj = obj_list[value]
205
+ offset = offset_for_index(plist, offset_table_addr, offset_byte_size,
206
+ value)
207
+ value_obj = decode_binary_plist_obj(plist, offset, ref_byte_size)
208
+ obj_list[value] = value_obj
209
+ value_obj = unflatten_collection(value_obj, obj_list, plist,
210
+ offset_table_addr, offset_byte_size, ref_byte_size)
211
+ end
212
+ hsh[key_obj] = value_obj
213
+ end
214
+ collection.replace(hsh)
215
+ end
216
+ return collection
217
+ end
218
+
219
+ # Returns a binary property list fragment that represents +obj+. The
220
+ # returned string is not a complete property list, just a fragment that
221
+ # describes +obj+, and is not useful without a header, offset table, and
222
+ # trailer.
223
+ #
224
+ # The following classes are recognized: String, Float, Integer, the Boolean
225
+ # classes, Time, IO, StringIO, Array, Set, and Hash. IO and StringIO
226
+ # objects are rewound, read, and the contents stored as data (i.e., Cocoa
227
+ # applications will decode them as NSData). All other classes are dumped
228
+ # with Marshal and stored as data.
229
+ #
230
+ # Note that subclasses of the supported classes will be encoded as though
231
+ # they were the supported superclass. Thus, a subclass of (for example)
232
+ # String will be encoded and decoded as a String, not as the subclass:
233
+ #
234
+ # class ExampleString < String
235
+ # ...
236
+ # end
237
+ #
238
+ # s = ExampleString.new("disquieting plantlike mystery")
239
+ # encoded_s = binary_plist_obj(s)
240
+ # decoded_s = decode_binary_plist_obj(encoded_s)
241
+ # puts decoded_s.class # => String
242
+ #
243
+ # +ref_byte_size+ is the number of bytes to use for storing references to
244
+ # other objects.
245
+ def self.binary_plist_obj(obj, ref_byte_size = 4)
246
+ case obj
247
+ when String
248
+ obj = obj.to_s if obj.is_a?(Symbol)
249
+ # This doesn't really work. NKF's guess method is really, really bad
250
+ # at discovering UTF8 when only a handful of characters are multi-byte.
251
+ encoding = NKF.guess2(obj)
252
+ if encoding == NKF::ASCII && obj =~ /[\x80-\xff]/
253
+ encoding = NKF::UTF8
254
+ end
255
+ if [NKF::ASCII, NKF::BINARY, NKF::UNKNOWN].include?(encoding)
256
+ result = (CFBinaryPlistMarkerASCIIString |
257
+ (obj.length < 15 ? obj.length : 0xf)).chr
258
+ result += binary_plist_obj(obj.length) if obj.length >= 15
259
+ result += obj
260
+ return result
261
+ else
262
+ # Convert to UTF8.
263
+ if encoding == NKF::UTF8
264
+ utf8 = obj
265
+ else
266
+ utf8 = NKF.nkf("-m0 -w", obj)
267
+ end
268
+ # Decode each character's UCS codepoint.
269
+ codepoints = []
270
+ i = 0
271
+ while i < utf8.length
272
+ byte = utf8[i]
273
+ if byte & 0xe0 == 0xc0
274
+ codepoints << ((byte & 0x1f) << 6) + (utf8[i+1] & 0x3f)
275
+ i += 1
276
+ elsif byte & 0xf0 == 0xe0
277
+ codepoints << ((byte & 0xf) << 12) + ((utf8[i+1] & 0x3f) << 6) +
278
+ (utf8[i+2] & 0x3f)
279
+ i += 2
280
+ elsif byte & 0xf8 == 0xf0
281
+ codepoints << ((byte & 0xe) << 18) + ((utf8[i+1] & 0x3f) << 12) +
282
+ ((utf8[i+2] & 0x3f) << 6) + (utf8[i+3] & 0x3f)
283
+ i += 3
284
+ else
285
+ codepoints << byte
286
+ end
287
+ if codepoints.last > 0xffff
288
+ raise(ArgumentError, "codepoint too high - only the Basic Multilingual Plane can be encoded")
289
+ end
290
+ i += 1
291
+ end
292
+ # Return string of 16-bit codepoints.
293
+ data = codepoints.pack("n*")
294
+ result = (CFBinaryPlistMarkerUnicode16String |
295
+ (codepoints.length < 15 ? codepoints.length : 0xf)).chr
296
+ result += binary_plist_obj(codepoints.length) if codepoints.length >= 15
297
+ result += data
298
+ return result
299
+ end
300
+ when Float
301
+ return (CFBinaryPlistMarkerReal | 3).chr + [obj].pack("G")
302
+ when Integer
303
+ nbytes = min_byte_size(obj)
304
+ size_bits = { 1 => 0, 2 => 1, 4 => 2, 8 => 3, 16 => 4 }[nbytes]
305
+ return (CFBinaryPlistMarkerInt | size_bits).chr + pack_int(obj, nbytes)
306
+ when TrueClass
307
+ return CFBinaryPlistMarkerTrue.chr
308
+ when FalseClass
309
+ return CFBinaryPlistMarkerFalse.chr
310
+ when Time
311
+ return CFBinaryPlistMarkerDate.chr +
312
+ [obj.to_f - NSTimeIntervalSince1970].pack("G")
313
+ when IO, StringIO
314
+ obj.rewind
315
+ return binary_plist_data(obj.read)
316
+ when Array
317
+ # Must be an array of object references as returned by flatten_collection.
318
+ result = (CFBinaryPlistMarkerArray | (obj.length < 15 ? obj.length : 0xf)).chr
319
+ result += binary_plist_obj(obj.length) if obj.length >= 15
320
+ result += obj.collect! { |i| pack_int(i, ref_byte_size) }.join
321
+ when Set
322
+ # Must be a set of object references as returned by flatten_collection.
323
+ result = (CFBinaryPlistMarkerSet | (obj.length < 15 ? obj.length : 0xf)).chr
324
+ result += binary_plist_obj(obj.length) if obj.length >= 15
325
+ result += obj.to_a.collect! { |i| pack_int(i, ref_byte_size) }.join
326
+ when Hash
327
+ # Must be a table of object references as returned by flatten_collection.
328
+ result = (CFBinaryPlistMarkerDict | (obj.length < 15 ? obj.length : 0xf)).chr
329
+ result += binary_plist_obj(obj.length) if obj.length >= 15
330
+ result += obj.keys.collect! { |i| pack_int(i, ref_byte_size) }.join
331
+ result += obj.values.collect! { |i| pack_int(i, ref_byte_size) }.join
332
+ else
333
+ return binary_plist_data(Marshal.dump(obj))
334
+ end
335
+ end
336
+
337
+ def self.decode_binary_plist_obj(plist, offset, ref_byte_size)
338
+ case plist[offset]
339
+ when CFBinaryPlistMarkerASCIIString..(CFBinaryPlistMarkerASCIIString | 0xf)
340
+ length, offset = decode_length(plist, offset)
341
+ return plist[offset, length]
342
+ when CFBinaryPlistMarkerUnicode16String..(CFBinaryPlistMarkerUnicode16String | 0xf)
343
+ length, offset = decode_length(plist, offset)
344
+ codepoints = plist[offset, length * 2].unpack("n*")
345
+ str = ""
346
+ codepoints.each do |codepoint|
347
+ if codepoint <= 0x7f
348
+ ch = ' '
349
+ ch[0] = to_i
350
+ elsif codepoint <= 0x7ff
351
+ ch = ' '
352
+ ch[0] = ((codepoint & 0x7c0) >> 6) | 0xc0
353
+ ch[1] = codepoint & 0x3f | 0x80
354
+ else
355
+ ch = ' '
356
+ ch[0] = ((codepoint & 0xf000) >> 12) | 0xe0
357
+ ch[1] = ((codepoint & 0xfc0) >> 6) | 0x80
358
+ ch[2] = codepoint & 0x3f | 0x80
359
+ end
360
+ str << ch
361
+ end
362
+ return str
363
+ when CFBinaryPlistMarkerReal | 3
364
+ return plist[offset+1, 8].unpack("G").first
365
+ when CFBinaryPlistMarkerInt..(CFBinaryPlistMarkerInt | 0xf)
366
+ num_bytes = 2 ** (plist[offset] & 0xf)
367
+ return unpack_int(plist[offset+1, num_bytes])
368
+ when CFBinaryPlistMarkerTrue
369
+ return true
370
+ when CFBinaryPlistMarkerFalse
371
+ return false
372
+ when CFBinaryPlistMarkerDate
373
+ secs = plist[offset+1, 8].unpack("G").first + NSTimeIntervalSince1970
374
+ return Time.at(secs)
375
+ when CFBinaryPlistMarkerData..(CFBinaryPlistMarkerData | 0xf)
376
+ length, offset = decode_length(plist, offset)
377
+ return StringIO.new(plist[offset, length])
378
+ when CFBinaryPlistMarkerArray..(CFBinaryPlistMarkerArray | 0xf)
379
+ ary = []
380
+ length, offset = decode_length(plist, offset)
381
+ length.times do
382
+ ary << unpack_int(plist[offset, ref_byte_size])
383
+ offset += ref_byte_size
384
+ end
385
+ return ary
386
+ when CFBinaryPlistMarkerDict..(CFBinaryPlistMarkerDict | 0xf)
387
+ hsh = {}
388
+ keys = []
389
+ length, offset = decode_length(plist, offset)
390
+ length.times do
391
+ keys << unpack_int(plist[offset, ref_byte_size])
392
+ offset += ref_byte_size
393
+ end
394
+ length.times do |i|
395
+ hsh[keys[i]] = unpack_int(plist[offset, ref_byte_size])
396
+ offset += ref_byte_size
397
+ end
398
+ return hsh
399
+ end
400
+ end
401
+
402
+ # Returns a binary property list fragment that represents a data object
403
+ # with the contents of the string +data+. A Cocoa application would decode
404
+ # this fragment as NSData. Like binary_plist_obj, the value returned by
405
+ # this method is not usable by itself; it is only useful as part of a
406
+ # complete binary property list with a header, offset table, and trailer.
407
+ def self.binary_plist_data(data)
408
+ result = (CFBinaryPlistMarkerData |
409
+ (data.length < 15 ? data.length : 0xf)).chr
410
+ result += binary_plist_obj(data.length) if data.length > 15
411
+ result += data
412
+ return result
413
+ end
414
+
415
+ # Determines the minimum number of bytes that is a power of two and can
416
+ # represent the integer +i+. Raises a RangeError if the number of bytes
417
+ # exceeds 16. Note that the property list format considers integers of 1,
418
+ # 2, and 4 bytes to be unsigned, while 8- and 16-byte integers are signed;
419
+ # thus negative integers will always require at least 8 bytes of storage.
420
+ def self.min_byte_size(i)
421
+ if i < 0
422
+ i = i.abs - 1
423
+ else
424
+ if i <= 0xff
425
+ return 1
426
+ elsif i <= 0xffff
427
+ return 2
428
+ elsif i <= 0xffffffff
429
+ return 4
430
+ end
431
+ end
432
+ if i <= 0x7fffffffffffffff
433
+ return 8
434
+ elsif i <= 0x7fffffffffffffffffffffffffffffff
435
+ return 16
436
+ end
437
+ raise(RangeError, "integer too big - exceeds 128 bits")
438
+ end
439
+
440
+ # Packs an integer +i+ into its binary representation in the specified
441
+ # number of bytes. Byte order is big-endian. Negative integers cannot be
442
+ # stored in 1, 2, or 4 bytes.
443
+ def self.pack_int(i, num_bytes)
444
+ if i < 0 && num_bytes < 8
445
+ raise(ArgumentError, "negative integers require 8 or 16 bytes of storage")
446
+ end
447
+ case num_bytes
448
+ when 1
449
+ [i].pack("c")
450
+ when 2
451
+ [i].pack("n")
452
+ when 4
453
+ [i].pack("N")
454
+ when 8
455
+ [(i >> 32) & 0xffffffff, i & 0xffffffff].pack("NN")
456
+ when 16
457
+ [i >> 96, (i >> 64) & 0xffffffff, (i >> 32) & 0xffffffff,
458
+ i & 0xffffffff].pack("NNNN")
459
+ else
460
+ raise(ArgumentError, "num_bytes must be 1, 2, 4, 8, or 16")
461
+ end
462
+ end
463
+
464
+ def self.combine_ints(num_bits, *ints)
465
+ i = ints.pop
466
+ shift_bits = num_bits
467
+ ints.reverse.each do |i_part|
468
+ i += i_part << shift_bits
469
+ shift_bits += num_bits
470
+ end
471
+ return i
472
+ end
473
+
474
+ def self.offset_for_index(plist, table_addr, offset_byte_size, index)
475
+ offset = plist[table_addr + index * offset_byte_size, offset_byte_size]
476
+ unpack_int(offset)
477
+ end
478
+
479
+ def self.unpack_int(s)
480
+ case s.length
481
+ when 1
482
+ s.unpack("C").first
483
+ when 2
484
+ s.unpack("n").first
485
+ when 4
486
+ s.unpack("N").first
487
+ when 8
488
+ i = combine_ints(32, *(s.unpack("NN")))
489
+ (i & 0x80000000_00000000 == 0) ?
490
+ i :
491
+ -(i ^ 0xffffffff_ffffffff) - 1
492
+ when 16
493
+ i = combine_ints(32, *(s.unpack("NNNN")))
494
+ (i & 0x80000000_00000000_00000000_00000000 == 0) ?
495
+ i :
496
+ -(i ^ 0xffffffff_ffffffff_ffffffff_ffffffff) - 1
497
+ else
498
+ raise(ArgumentError, "length must be 1, 2, 4, 8, or 16 bytes")
499
+ end
500
+ end
501
+
502
+ def self.decode_length(plist, offset)
503
+ if plist[offset] & 0xf == 0xf
504
+ offset += 1
505
+ length = decode_binary_plist_obj(plist, offset, 0)
506
+ offset += min_byte_size(length) + 1
507
+ return length, offset
508
+ else
509
+ return (plist[offset] & 0xf), (offset + 1)
510
+ end
511
+ end
512
+ end
513
+ end
514
+ end