appsendr 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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