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 +0 -0
- data/README.rdoc +50 -0
- data/Rakefile +16 -0
- data/appsendr.gemspec +41 -0
- data/bin/appsendr +14 -0
- data/lib/appsendr/binary_plist.rb +514 -0
- data/lib/appsendr/client.rb +115 -0
- data/lib/appsendr/command.rb +68 -0
- data/lib/appsendr/commands/app.rb +167 -0
- data/lib/appsendr/commands/auth.rb +155 -0
- data/lib/appsendr/commands/base.rb +63 -0
- data/lib/appsendr/commands/build.rb +198 -0
- data/lib/appsendr/commands/deploy.rb +41 -0
- data/lib/appsendr/commands/help.rb +87 -0
- data/lib/appsendr/commands/testers.rb +115 -0
- data/lib/appsendr/commands/version.rb +7 -0
- data/lib/appsendr/constants.rb +5 -0
- data/lib/appsendr/helpers.rb +120 -0
- data/lib/appsendr.rb +3 -0
- metadata +151 -0
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
|