fruity_builder 1.0.2 → 1.1.0
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.
- checksums.yaml +4 -4
- data/lib/fruity_builder/plistutil.rb +69 -67
- data/lib/fruity_builder/signing.rb +85 -81
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 840aa45bbec96aa6b16e53b63886b335ceb50b29
|
4
|
+
data.tar.gz: 35dded1625ff0a3d9d70c16c060e4d9b60fa6650
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8a112412efd89a4235d4525129da6671ef56d3d6106c88a3ed1940d159e78297c1e8936c6b5ae1bbe1cab290dd9c2332a0478b2375f176aa6aee3090aca3fa1
|
7
|
+
data.tar.gz: 976fe8ccf7e0ab35f1129886de885680320c7ce7092a2db57d8e9867d316009afd926a5576ba5eaae66a111fed49d857e9512890393e7b52caa945b933cdacd7
|
@@ -2,92 +2,94 @@ require 'fruity_builder/lib/execution'
|
|
2
2
|
require 'ox'
|
3
3
|
|
4
4
|
module FruityBuilder
|
5
|
-
|
5
|
+
module IOS
|
6
|
+
class Plistutil < Execution
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def self.get_xml(options = {})
|
9
|
+
if options.key?(:file)
|
10
|
+
IO.read(options[:file])
|
11
|
+
elsif options.key?(:xml)
|
12
|
+
options[:xml]
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
def self.get_bundle_id(options = {})
|
17
|
+
xml = get_xml(options)
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
raise PlistutilCommandError.new('No XML was passed') unless xml
|
20
|
+
identifiers = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
|
21
|
+
identifiers << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
|
22
|
+
identifiers.flatten.uniq
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
def self.replace_bundle_id(options = {})
|
26
|
+
xml = get_xml(options)
|
26
27
|
|
27
|
-
|
28
|
+
raise PlistutilCommandError.new('No XML was passed') unless xml
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
replacements = xml.scan(/.*CFBundleIdentifier<\/key>\n\t<string>(.*?)<\/string>/)
|
31
|
+
replacements << xml.scan(/.*CFBundleName<\/key>\n\t<string>(.*?)<\/string>/)
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
replacements.flatten.uniq.each do |replacement|
|
34
|
+
xml = xml.gsub(replacement, options[:new_id])
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
IO.write(options[:file], xml) if options.key?(:file)
|
38
|
+
xml
|
39
|
+
end
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
# Check to ensure that plistutil is available
|
42
|
+
# @return [Boolean] true if plistutil is available, false otherwise
|
43
|
+
def self.plistutil_available?
|
44
|
+
result = execute('which plistutil')
|
45
|
+
result.exit == 0
|
46
|
+
end
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
# Gets properties from the IPA and returns them in a hash
|
49
|
+
# @param [String] path path to the IPA/App
|
50
|
+
# @return [Hash] list of properties from the app
|
51
|
+
def self.get_bundle_id_from_app(path)
|
52
|
+
path = Signing.unpack_ipa(path) if Signing.is_ipa?(path)
|
53
|
+
get_bundle_id_from_plist("#{path}/Info.plist")
|
54
|
+
end
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
56
|
+
# Gets properties from the IPA and returns them in a hash
|
57
|
+
# @param [String] plist path to the plist
|
58
|
+
# @return [Hash] list of properties from the app
|
59
|
+
def self.get_bundle_id_from_plist(plist)
|
60
|
+
raise PlistutilCommandError.new('plistutil not found') unless plistutil_available?
|
61
|
+
result = execute("plistutil -i #{plist}")
|
62
|
+
raise PlistutilCommandError.new(result.stderr) if result.exit != 0
|
63
|
+
parse_xml(result.stdout)
|
64
|
+
end
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
66
|
+
def self.parse_xml(xml)
|
67
|
+
info = Ox.parse(xml)
|
68
|
+
nodes = info.locate('*/dict')
|
69
|
+
values = {}
|
70
|
+
last_key = nil
|
71
|
+
nodes.each do |node|
|
72
|
+
node.nodes.each do |child|
|
73
|
+
if child.value == 'key'
|
74
|
+
if child.nodes.first == 'get-task-allow'
|
75
|
+
values['get-task-allow'] = nodes.first.nodes[nodes.first.nodes.index(child)+1].value
|
76
|
+
next
|
77
|
+
end
|
78
|
+
last_key = child.nodes.first
|
79
|
+
elsif child.value == 'string'
|
80
|
+
values[last_key] = child.nodes.first
|
76
81
|
end
|
77
|
-
last_key = child.nodes.first
|
78
|
-
elsif child.value == 'string'
|
79
|
-
values[last_key] = child.nodes.first
|
80
82
|
end
|
81
83
|
end
|
84
|
+
values
|
82
85
|
end
|
83
|
-
values
|
84
86
|
end
|
85
|
-
end
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
# plistutil error class
|
89
|
+
class PlistutilCommandError < StandardError
|
90
|
+
def initialize(msg)
|
91
|
+
super(msg)
|
92
|
+
end
|
91
93
|
end
|
92
94
|
end
|
93
95
|
end
|
@@ -2,117 +2,121 @@ require 'fruity_builder/lib/execution'
|
|
2
2
|
require 'fruity_builder/plistutil'
|
3
3
|
|
4
4
|
module FruityBuilder
|
5
|
-
|
6
|
-
|
5
|
+
module IOS
|
6
|
+
# Namespace for all methods encapsulating idevice calls
|
7
|
+
class Signing < Execution
|
8
|
+
|
9
|
+
# Check to see if the path is an IPA
|
10
|
+
def self.is_ipa?(path)
|
11
|
+
return true if (File.extname path).downcase == '.ipa'
|
12
|
+
false
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
false
|
12
|
-
end
|
15
|
+
def self.unpack_ipa(path)
|
16
|
+
folder = File.dirname(path)
|
17
|
+
target = (File.basename path, (File.extname path))
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
target = (File.basename path, (File.extname path))
|
19
|
+
# Check to see if the target has already been unzipped
|
20
|
+
return Dir["#{folder}/#{target}/Payload/*.app"].first if File.exists? ("#{folder}/#{target}/Payload/")
|
17
21
|
|
18
|
-
|
19
|
-
|
22
|
+
result = execute("unzip '#{path}' -d '#{folder}/#{target}'")
|
23
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
Dir["#{folder}/#{target}/Payload/*.app"].first
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
|
28
|
+
def self.is_app_signed?(app_path)
|
29
|
+
app_path = unpack_ipa(app_path) if is_ipa?(app_path)
|
30
|
+
result = execute("codesign -d -vvvv '#{app_path}'")
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
if result.exit != 0
|
33
|
+
return false if /is not signed/.match(result.stderr)
|
34
|
+
raise SigningCommandError.new(result.stderr)
|
35
|
+
end
|
30
36
|
|
31
|
-
|
32
|
-
return false if /is not signed/.match(result.stderr)
|
33
|
-
raise SigningCommandError.new(result.stderr)
|
37
|
+
true
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
entitlements = options[:entitlements]
|
42
|
-
app = options[:app]
|
43
|
-
original_app = nil
|
40
|
+
def self.sign_app(options = {})
|
41
|
+
cert = options[:cert]
|
42
|
+
entitlements = options[:entitlements]
|
43
|
+
app = options[:app]
|
44
|
+
original_app = nil
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
if is_ipa?(app)
|
47
|
+
original_app = app
|
48
|
+
app = unpack_ipa(app)
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
command = "codesign --force --sign '#{cert}'"
|
52
|
+
# Check to see if the entitlements passed in is a file or the XML
|
53
|
+
unless File.exists?(entitlements)
|
54
|
+
file = Tempfile.new('entitlements')
|
55
|
+
file.write(entitlements)
|
56
|
+
file.close
|
57
|
+
entitlements = file.path
|
58
|
+
command = command + " --entitlements #{entitlements}"
|
59
|
+
end
|
57
60
|
|
58
|
-
|
61
|
+
result = execute(command + " '#{app}'")
|
59
62
|
|
60
|
-
|
63
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
zip_app(app, original_app) if original_app
|
66
|
+
end
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
+
def self.zip_app(payload_path, ipa_path)
|
69
|
+
result = execute("cd #{File.dirname(payload_path)}; zip -r #{ipa_path} ../Payload ")
|
70
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
true
|
73
|
+
end
|
71
74
|
|
72
|
-
|
73
|
-
|
75
|
+
def self.get_signing_certs
|
76
|
+
result = execute('security find-identity -p codesigning -v')
|
74
77
|
|
75
|
-
|
78
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
76
79
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
certs = []
|
81
|
+
result.stdout.split("\n").each do |line|
|
82
|
+
if /\)\s*(\S*)\s*"(.*)"/.match(line)
|
83
|
+
certs << { id: Regexp.last_match[1], name: Regexp.last_match[2] }
|
84
|
+
end
|
81
85
|
end
|
86
|
+
certs
|
82
87
|
end
|
83
|
-
certs
|
84
|
-
end
|
85
88
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
+
def self.get_entitlements(app_path, raw = false)
|
90
|
+
app_path = unpack_ipa(app_path) if is_ipa?(app_path)
|
91
|
+
result = execute("codesign -d --entitlements - #{app_path}")
|
89
92
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
+
if result.exit != 0
|
94
|
+
raise SigningCommandError.new(result.stderr)
|
95
|
+
end
|
93
96
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
+
# Clean up the result as it occasionally contains invalid UTF-8 characters
|
98
|
+
xml = result.stdout.to_s.encode('UTF-8', 'UTF-8', invalid: :replace)
|
99
|
+
xml = xml[xml.index('<')..xml.length]
|
97
100
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
101
|
+
return xml if raw
|
102
|
+
entitlements = Plistutil.parse_xml(xml)
|
103
|
+
return entitlements
|
104
|
+
end
|
102
105
|
|
103
|
-
|
104
|
-
|
106
|
+
def self.enable_get_tasks(app_path)
|
107
|
+
entitlements = get_entitlements(app_path, raw: true)
|
105
108
|
|
106
|
-
|
109
|
+
return entitlements if entitlements.scan(/<key>get-task-allow<\/key>\\n.*<true\/>/).count > 0
|
107
110
|
|
108
|
-
|
111
|
+
entitlements.gsub('<false/>', '<true/>') if entitlements.scan(/<key>get-task-allow<\/key>\\n.*<false\/>/)
|
112
|
+
end
|
109
113
|
end
|
110
|
-
end
|
111
114
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
115
|
+
# Signing error class
|
116
|
+
class SigningCommandError < StandardError
|
117
|
+
def initialize(msg)
|
118
|
+
super(msg)
|
119
|
+
end
|
116
120
|
end
|
117
121
|
end
|
118
122
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fruity_builder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- BBC
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-05-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ox
|