app-info 2.8.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +12 -7
- data/.github/workflows/ci.yml +7 -5
- data/.github/workflows/create_release.yml +15 -0
- data/.rubocop.yml +33 -11
- data/CHANGELOG.md +107 -1
- data/Gemfile +10 -5
- data/README.md +82 -15
- data/Rakefile +11 -0
- data/app_info.gemspec +14 -5
- data/lib/app_info/aab.rb +76 -110
- data/lib/app_info/android/signature.rb +114 -0
- data/lib/app_info/android/signatures/base.rb +53 -0
- data/lib/app_info/android/signatures/info.rb +158 -0
- data/lib/app_info/android/signatures/v1.rb +63 -0
- data/lib/app_info/android/signatures/v2.rb +121 -0
- data/lib/app_info/android/signatures/v3.rb +131 -0
- data/lib/app_info/android/signatures/v4.rb +18 -0
- data/lib/app_info/android.rb +181 -0
- data/lib/app_info/apk.rb +77 -112
- data/lib/app_info/apple.rb +192 -0
- data/lib/app_info/certificate.rb +176 -0
- data/lib/app_info/const.rb +76 -0
- data/lib/app_info/core_ext/object/try.rb +3 -1
- data/lib/app_info/core_ext/string/inflector.rb +2 -0
- data/lib/app_info/dsym/debug_info.rb +81 -0
- data/lib/app_info/dsym/macho.rb +62 -0
- data/lib/app_info/dsym.rb +35 -135
- data/lib/app_info/error.rb +3 -1
- data/lib/app_info/file.rb +49 -0
- data/lib/app_info/helper/archive.rb +37 -0
- data/lib/app_info/helper/file_size.rb +25 -0
- data/lib/app_info/helper/generate_class.rb +29 -0
- data/lib/app_info/helper/protobuf.rb +12 -0
- data/lib/app_info/helper/signatures.rb +229 -0
- data/lib/app_info/helper.rb +5 -128
- data/lib/app_info/info_plist.rb +66 -29
- data/lib/app_info/ipa/framework.rb +4 -4
- data/lib/app_info/ipa.rb +61 -135
- data/lib/app_info/macos.rb +54 -102
- data/lib/app_info/mobile_provision.rb +66 -48
- data/lib/app_info/pe.rb +322 -0
- data/lib/app_info/png_uncrush.rb +25 -5
- data/lib/app_info/proguard.rb +39 -22
- data/lib/app_info/protobuf/manifest.rb +22 -11
- data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
- data/lib/app_info/protobuf/models/README.md +8 -1
- data/lib/app_info/protobuf/models/Resources.proto +51 -0
- data/lib/app_info/protobuf/models/Resources_pb.rb +42 -0
- data/lib/app_info/protobuf/resources.rb +5 -5
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +93 -43
- metadata +57 -37
data/lib/app_info/macos.rb
CHANGED
@@ -7,63 +7,42 @@ require 'cfpropertylist'
|
|
7
7
|
|
8
8
|
module AppInfo
|
9
9
|
# MacOS App parser
|
10
|
-
class Macos
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def_delegators :mobileprovision, :team_name, :team_identifier,
|
42
|
-
:profile_name, :expired_date
|
43
|
-
|
44
|
-
def distribution_name
|
45
|
-
"#{profile_name} - #{team_name}" if profile_name && team_name
|
46
|
-
end
|
47
|
-
|
48
|
-
def release_type
|
49
|
-
if stored?
|
50
|
-
ExportType::APPSTORE
|
51
|
-
elsif mobileprovision?
|
52
|
-
ExportType::RELEASE
|
53
|
-
else
|
54
|
-
ExportType::DEBUG
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def stored?
|
59
|
-
File.exist?(store_path)
|
60
|
-
end
|
61
|
-
|
10
|
+
class Macos < Apple
|
11
|
+
# @!method min_sdk_version
|
12
|
+
# @see InfoPlist#min_sdk_version
|
13
|
+
def_delegators :info, :min_system_version
|
14
|
+
|
15
|
+
# Full icons metadata
|
16
|
+
# @param [Boolean] convert Convert .icons to .png format
|
17
|
+
# @example uncovert .icons
|
18
|
+
# macos.icons
|
19
|
+
# # => [
|
20
|
+
# # {
|
21
|
+
# # name: 'icon.icns',
|
22
|
+
# # file: '/path/to/icon.icns',
|
23
|
+
# # }
|
24
|
+
# # ]
|
25
|
+
#
|
26
|
+
# @example coverted .icons
|
27
|
+
# macos.icons(convert: true)
|
28
|
+
# # => [
|
29
|
+
# # {
|
30
|
+
# # name: 'converted_icon_32x32.png',
|
31
|
+
# # file: '/path/to/converted_icon_32x32.png',
|
32
|
+
# # dimensions: [32, 32]
|
33
|
+
# # },
|
34
|
+
# # {
|
35
|
+
# # name: 'converted_icon_120x120.png',
|
36
|
+
# # file: '/path/to/converted_icon_120x120.png',
|
37
|
+
# # dimensions: [120, 120]
|
38
|
+
# # },
|
39
|
+
# # ]
|
40
|
+
# @return [Array<Hash{Symbol => String, Array<Integer>}>] icons paths of icons
|
62
41
|
def icons(convert: true)
|
63
42
|
return unless icon_file
|
64
43
|
|
65
44
|
data = {
|
66
|
-
name: File.basename(icon_file),
|
45
|
+
name: ::File.basename(icon_file),
|
67
46
|
file: icon_file
|
68
47
|
}
|
69
48
|
|
@@ -71,63 +50,40 @@ module AppInfo
|
|
71
50
|
data
|
72
51
|
end
|
73
52
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
file = MachO.open(binary_path)
|
78
|
-
case file
|
79
|
-
when MachO::MachOFile
|
80
|
-
[file.cpusubtype]
|
81
|
-
else
|
82
|
-
file.machos.each_with_object([]) do |arch, obj|
|
83
|
-
obj << arch.cpusubtype
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
alias architectures archs
|
88
|
-
|
89
|
-
def hide_developer_certificates
|
90
|
-
mobileprovision.delete('DeveloperCertificates') if mobileprovision?
|
91
|
-
end
|
92
|
-
|
93
|
-
def mobileprovision
|
94
|
-
return unless mobileprovision?
|
95
|
-
|
96
|
-
@mobileprovision ||= MobileProvision.new(mobileprovision_path)
|
97
|
-
end
|
98
|
-
|
99
|
-
def mobileprovision?
|
100
|
-
File.exist?(mobileprovision_path)
|
53
|
+
# @return [Boolean]
|
54
|
+
def stored?
|
55
|
+
::File.exist?(store_path)
|
101
56
|
end
|
102
57
|
|
58
|
+
# @return [String]
|
103
59
|
def mobileprovision_path
|
104
|
-
@mobileprovision_path ||= File.join(app_path, 'Contents', 'embedded.provisionprofile')
|
60
|
+
@mobileprovision_path ||= ::File.join(app_path, 'Contents', 'embedded.provisionprofile')
|
105
61
|
end
|
106
62
|
|
63
|
+
# @return [String]
|
107
64
|
def store_path
|
108
|
-
@store_path ||= File.join(app_path, 'Contents', '_MASReceipt', 'receipt')
|
65
|
+
@store_path ||= ::File.join(app_path, 'Contents', '_MASReceipt', 'receipt')
|
109
66
|
end
|
110
67
|
|
68
|
+
# @return [String]
|
111
69
|
def binary_path
|
112
70
|
return @binary_path if @binary_path
|
113
71
|
|
114
|
-
base_path = File.join(app_path, 'Contents', 'MacOS')
|
72
|
+
base_path = ::File.join(app_path, 'Contents', 'MacOS')
|
115
73
|
binary = info['CFBundleExecutable']
|
116
|
-
return File.join(base_path, binary) if binary
|
74
|
+
return ::File.join(base_path, binary) if binary
|
117
75
|
|
118
|
-
@binary_path ||= Dir.glob(File.join(base_path, '*')).first
|
119
|
-
end
|
120
|
-
|
121
|
-
def info
|
122
|
-
@info ||= InfoPlist.new(info_path)
|
76
|
+
@binary_path ||= Dir.glob(::File.join(base_path, '*')).first
|
123
77
|
end
|
124
78
|
|
79
|
+
# @return [String]
|
125
80
|
def info_path
|
126
|
-
@info_path ||= File.join(app_path, 'Contents', 'Info.plist')
|
81
|
+
@info_path ||= ::File.join(app_path, 'Contents', 'Info.plist')
|
127
82
|
end
|
128
83
|
|
84
|
+
# @return [String]
|
129
85
|
def app_path
|
130
|
-
@app_path ||= Dir.glob(File.join(contents, '*.app')).first
|
86
|
+
@app_path ||= Dir.glob(::File.join(contents, '*.app')).first
|
131
87
|
end
|
132
88
|
|
133
89
|
def clear!
|
@@ -137,16 +93,12 @@ module AppInfo
|
|
137
93
|
|
138
94
|
@contents = nil
|
139
95
|
@app_path = nil
|
140
|
-
@
|
96
|
+
@binary_path = nil
|
141
97
|
@info_path = nil
|
142
98
|
@info = nil
|
143
99
|
@icons = nil
|
144
100
|
end
|
145
101
|
|
146
|
-
def contents
|
147
|
-
@contents ||= unarchive(@file, path: 'macos')
|
148
|
-
end
|
149
|
-
|
150
102
|
private
|
151
103
|
|
152
104
|
def icon_file
|
@@ -155,8 +107,8 @@ module AppInfo
|
|
155
107
|
info.icons.each do |key|
|
156
108
|
next unless value = info[key]
|
157
109
|
|
158
|
-
file = File.join(app_path, 'Contents', 'Resources', "#{value}.icns")
|
159
|
-
next unless File.file?(file)
|
110
|
+
file = ::File.join(app_path, 'Contents', 'Resources', "#{value}.icns")
|
111
|
+
next unless ::File.file?(file)
|
160
112
|
|
161
113
|
return @icon_file = file
|
162
114
|
end
|
@@ -173,14 +125,14 @@ module AppInfo
|
|
173
125
|
file = data[:file]
|
174
126
|
reader = Icns::Reader.new(file)
|
175
127
|
Icns::SIZE_TO_TYPE.each do |size, _|
|
176
|
-
dest_filename = "#{File.basename(file, '.icns')}_#{size}x#{size}.png"
|
177
|
-
dest_file = tempdir(File.join(File.dirname(file), dest_filename), prefix: 'converted')
|
128
|
+
dest_filename = "#{::File.basename(file, '.icns')}_#{size}x#{size}.png"
|
129
|
+
dest_file = tempdir(::File.join(::File.dirname(file), dest_filename), prefix: 'converted')
|
178
130
|
next unless icon_data = reader.image(size: size)
|
179
131
|
|
180
|
-
File.write(dest_file, icon_data, encoding: Encoding::BINARY)
|
132
|
+
::File.write(dest_file, icon_data, encoding: Encoding::BINARY)
|
181
133
|
|
182
134
|
data[:sets] << {
|
183
|
-
name: File.basename(dest_filename),
|
135
|
+
name: ::File.basename(dest_filename),
|
184
136
|
file: dest_file,
|
185
137
|
dimensions: ImageSize.path(dest_file).size
|
186
138
|
}
|
@@ -4,20 +4,37 @@ require 'openssl'
|
|
4
4
|
require 'cfpropertylist'
|
5
5
|
|
6
6
|
module AppInfo
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
# Apple code signing: provisioning profile parser
|
8
|
+
# @see https://developer.apple.com/documentation/technotes/tn3125-inside-code-signing-provisioning-profiles
|
9
|
+
class MobileProvision < File
|
10
|
+
# @return [Symbol] {Manufacturer}
|
11
|
+
def manufacturer
|
12
|
+
Manufacturer::APPLE
|
11
13
|
end
|
12
14
|
|
15
|
+
# @return [Symbol] {Platform}
|
16
|
+
def platform
|
17
|
+
case platforms[0]
|
18
|
+
when :macos
|
19
|
+
Platform::MACOS
|
20
|
+
when :ios
|
21
|
+
Platform::IOS
|
22
|
+
else
|
23
|
+
raise NotImplementedError, "Unkonwn platform: #{platforms[0]}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String, nil]
|
13
28
|
def name
|
14
29
|
mobileprovision.try(:[], 'Name')
|
15
30
|
end
|
16
31
|
|
32
|
+
# @return [String, nil]
|
17
33
|
def app_name
|
18
34
|
mobileprovision.try(:[], 'AppIDName')
|
19
35
|
end
|
20
36
|
|
37
|
+
# @return [Symbol, nil]
|
21
38
|
def type
|
22
39
|
return :development if development?
|
23
40
|
return :adhoc if adhoc?
|
@@ -25,6 +42,7 @@ module AppInfo
|
|
25
42
|
return :enterprise if enterprise?
|
26
43
|
end
|
27
44
|
|
45
|
+
# @return [Array<Symbol>]
|
28
46
|
def platforms
|
29
47
|
return unless platforms = mobileprovision.try(:[], 'Platform')
|
30
48
|
|
@@ -34,83 +52,100 @@ module AppInfo
|
|
34
52
|
end
|
35
53
|
end
|
36
54
|
|
37
|
-
|
38
|
-
platforms[0]
|
39
|
-
end
|
40
|
-
|
55
|
+
# @return [Array<String>, nil]
|
41
56
|
def devices
|
42
57
|
mobileprovision.try(:[], 'ProvisionedDevices')
|
43
58
|
end
|
44
59
|
|
60
|
+
# @return [String, nil]
|
45
61
|
def team_identifier
|
46
62
|
mobileprovision.try(:[], 'TeamIdentifier')
|
47
63
|
end
|
48
64
|
|
65
|
+
# @return [String, nil]
|
49
66
|
def team_name
|
50
67
|
mobileprovision.try(:[], 'TeamName')
|
51
68
|
end
|
52
69
|
|
70
|
+
# @return [String, nil]
|
53
71
|
def profile_name
|
54
72
|
mobileprovision.try(:[], 'Name')
|
55
73
|
end
|
56
74
|
|
75
|
+
# @return [String, nil]
|
57
76
|
def created_date
|
58
77
|
mobileprovision.try(:[], 'CreationDate')
|
59
78
|
end
|
60
79
|
|
80
|
+
# @return [String, nil]
|
61
81
|
def expired_date
|
62
82
|
mobileprovision.try(:[], 'ExpirationDate')
|
63
83
|
end
|
64
84
|
|
85
|
+
# @return [Array<String>, nil]
|
65
86
|
def entitlements
|
66
87
|
mobileprovision.try(:[], 'Entitlements')
|
67
88
|
end
|
68
89
|
|
90
|
+
# return developer certificates.
|
91
|
+
#
|
92
|
+
# @deprecated Use {#certificates} instead of this method.
|
69
93
|
def developer_certs
|
94
|
+
certificates
|
95
|
+
end
|
96
|
+
|
97
|
+
# return developer certificates.
|
98
|
+
#
|
99
|
+
# @return [Array<Certificate>]
|
100
|
+
def certificates
|
70
101
|
certs = mobileprovision.try(:[], 'DeveloperCertificates')
|
71
102
|
return if certs.empty?
|
72
103
|
|
73
|
-
certs.each_with_object([]) do |
|
74
|
-
obj <<
|
104
|
+
certs.each_with_object([]) do |cert_data, obj|
|
105
|
+
obj << Certificate.parse(cert_data)
|
75
106
|
end
|
76
107
|
end
|
77
108
|
|
78
109
|
# Detect is development type of mobileprovision
|
79
110
|
#
|
80
|
-
#
|
111
|
+
# @see https://stackoverflow.com/questions/1003066/what-does-get-task-allow-do-in-xcode
|
112
|
+
# @return [Boolea]
|
81
113
|
def development?
|
82
|
-
case platform
|
83
|
-
when
|
114
|
+
case platform
|
115
|
+
when Platform::IOS
|
84
116
|
entitlements['get-task-allow'] == true
|
85
|
-
when
|
117
|
+
when Platform::MACOS
|
86
118
|
!devices.nil?
|
87
119
|
else
|
88
|
-
raise
|
120
|
+
raise NotImplementedError, "Unknown platform: #{platform}"
|
89
121
|
end
|
90
122
|
end
|
91
123
|
|
92
124
|
# Detect app store type
|
93
125
|
#
|
94
|
-
#
|
126
|
+
# @see https://developer.apple.com/library/archive/qa/qa1830/_index.html
|
127
|
+
# @return [Boolea]
|
95
128
|
def appstore?
|
96
|
-
case platform
|
97
|
-
when
|
129
|
+
case platform
|
130
|
+
when Platform::IOS
|
98
131
|
!development? && entitlements.key?('beta-reports-active')
|
99
|
-
when
|
132
|
+
when Platform::MACOS
|
100
133
|
!development?
|
101
134
|
else
|
102
|
-
raise
|
135
|
+
raise NotImplementedError, "Unknown platform: #{platform}"
|
103
136
|
end
|
104
137
|
end
|
105
138
|
|
139
|
+
# @return [Boolea]
|
106
140
|
def adhoc?
|
107
|
-
return false if platform ==
|
141
|
+
return false if platform == Platform::MACOS # macOS no need adhoc
|
108
142
|
|
109
143
|
!development? && !devices.nil?
|
110
144
|
end
|
111
145
|
|
146
|
+
# @return [Boolea]
|
112
147
|
def enterprise?
|
113
|
-
return false if platform ==
|
148
|
+
return false if platform == Platform::MACOS # macOS no need adhoc
|
114
149
|
|
115
150
|
!development? && !adhoc? && !appstore?
|
116
151
|
end
|
@@ -118,7 +153,8 @@ module AppInfo
|
|
118
153
|
|
119
154
|
# Enabled Capabilites
|
120
155
|
#
|
121
|
-
#
|
156
|
+
# @see https://developer.apple.com/support/app-capabilities/
|
157
|
+
# @return [Array<String>]
|
122
158
|
def enabled_capabilities
|
123
159
|
capabilities = []
|
124
160
|
capabilities << 'In-App Purchase' << 'GameKit' if adhoc? || appstore?
|
@@ -142,10 +178,10 @@ module AppInfo
|
|
142
178
|
when 'com.apple.developer.networking.vpn.api'
|
143
179
|
capabilities << 'Personal VPN'
|
144
180
|
when 'com.apple.developer.healthkit',
|
145
|
-
|
181
|
+
'com.apple.developer.healthkit.access'
|
146
182
|
capabilities << 'HealthKit' unless capabilities.include?('HealthKit')
|
147
183
|
when 'com.apple.developer.icloud-services',
|
148
|
-
|
184
|
+
'com.apple.developer.icloud-container-identifiers'
|
149
185
|
capabilities << 'iCloud' unless capabilities.include?('iCloud')
|
150
186
|
when 'com.apple.developer.in-app-payments'
|
151
187
|
capabilities << 'Apple Pay'
|
@@ -192,18 +228,21 @@ module AppInfo
|
|
192
228
|
capabilities
|
193
229
|
end
|
194
230
|
|
231
|
+
# @return [String, nil]
|
195
232
|
def [](key)
|
196
233
|
mobileprovision.try(:[], key.to_s)
|
197
234
|
end
|
198
235
|
|
236
|
+
# @return [Boolea]
|
199
237
|
def empty?
|
200
238
|
mobileprovision.nil?
|
201
239
|
end
|
202
240
|
|
241
|
+
# @return [CFPropertyList]
|
203
242
|
def mobileprovision
|
204
|
-
return @mobileprovision = nil unless File.exist?(@
|
243
|
+
return @mobileprovision = nil unless ::File.exist?(@file)
|
205
244
|
|
206
|
-
data = File.read(@
|
245
|
+
data = ::File.read(@file)
|
207
246
|
data = strip_plist_wrapper(data) unless bplist?(data)
|
208
247
|
list = CFPropertyList::List.new(data: data).value
|
209
248
|
@mobileprovision = CFPropertyList.native_types(list)
|
@@ -235,26 +274,5 @@ module AppInfo
|
|
235
274
|
end_point = raw.index(end_tag) + end_tag.size - 1
|
236
275
|
raw[start_point..end_point]
|
237
276
|
end
|
238
|
-
|
239
|
-
# Developer Certificate
|
240
|
-
class DeveloperCertificate
|
241
|
-
attr_reader :raw
|
242
|
-
|
243
|
-
def initialize(data)
|
244
|
-
@raw = OpenSSL::X509::Certificate.new(data)
|
245
|
-
end
|
246
|
-
|
247
|
-
def name
|
248
|
-
@raw.subject.to_a.find { |name, _, _| name == 'CN' }[1].force_encoding('UTF-8')
|
249
|
-
end
|
250
|
-
|
251
|
-
def created_date
|
252
|
-
@raw.not_after
|
253
|
-
end
|
254
|
-
|
255
|
-
def expired_date
|
256
|
-
@raw.not_before
|
257
|
-
end
|
258
|
-
end
|
259
277
|
end
|
260
278
|
end
|