app-info 2.8.4 → 3.0.0.beta1
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 +3 -1
- data/.rubocop.yml +31 -11
- data/CHANGELOG.md +30 -2
- data/Gemfile +7 -1
- data/README.md +74 -9
- data/Rakefile +11 -0
- data/app_info.gemspec +12 -3
- data/lib/app_info/aab.rb +58 -39
- data/lib/app_info/android/signature.rb +114 -0
- data/lib/app_info/android/signatures/base.rb +49 -0
- data/lib/app_info/android/signatures/info.rb +152 -0
- data/lib/app_info/android/signatures/v1.rb +59 -0
- data/lib/app_info/android/signatures/v2.rb +117 -0
- data/lib/app_info/android/signatures/v3.rb +127 -0
- data/lib/app_info/android/signatures/v4.rb +14 -0
- data/lib/app_info/apk.rb +43 -46
- data/lib/app_info/certificate.rb +181 -0
- data/lib/app_info/const.rb +41 -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 +72 -0
- data/lib/app_info/dsym/macho.rb +55 -0
- data/lib/app_info/dsym.rb +27 -134
- data/lib/app_info/error.rb +8 -0
- data/lib/app_info/file.rb +23 -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 -126
- data/lib/app_info/info_plist.rb +14 -6
- data/lib/app_info/ipa/framework.rb +4 -4
- data/lib/app_info/ipa.rb +41 -36
- data/lib/app_info/macos.rb +34 -26
- data/lib/app_info/mobile_provision.rb +19 -30
- data/lib/app_info/pe.rb +226 -0
- data/lib/app_info/png_uncrush.rb +5 -4
- data/lib/app_info/proguard.rb +11 -17
- data/lib/app_info/protobuf/manifest.rb +16 -9
- data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
- data/lib/app_info/protobuf/models/README.md +7 -0
- data/lib/app_info/protobuf/models/Resources_pb.rb +2 -0
- data/lib/app_info/protobuf/resources.rb +5 -5
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +91 -42
- metadata +46 -35
data/lib/app_info/info_plist.rb
CHANGED
@@ -6,11 +6,19 @@ require 'app_info/png_uncrush'
|
|
6
6
|
|
7
7
|
module AppInfo
|
8
8
|
# iOS Info.plist parser
|
9
|
-
class InfoPlist
|
9
|
+
class InfoPlist < File
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# Icon Key
|
13
|
+
ICON_KEYS = {
|
14
|
+
Device::IPHONE => ['CFBundleIcons'],
|
15
|
+
Device::IPAD => ['CFBundleIcons~ipad'],
|
16
|
+
Device::UNIVERSAL => ['CFBundleIcons', 'CFBundleIcons~ipad'],
|
17
|
+
Device::MACOS => %w[CFBundleIconFile CFBundleIconName]
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
def file_type
|
21
|
+
Format::INFOPLIST
|
14
22
|
end
|
15
23
|
|
16
24
|
def version
|
@@ -126,7 +134,7 @@ module AppInfo
|
|
126
134
|
private
|
127
135
|
|
128
136
|
def info
|
129
|
-
return unless File.file?(@file)
|
137
|
+
return unless ::File.file?(@file)
|
130
138
|
|
131
139
|
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: @file).value)
|
132
140
|
end
|
@@ -134,9 +142,9 @@ module AppInfo
|
|
134
142
|
def app_path
|
135
143
|
@app_path ||= case device_type
|
136
144
|
when Device::MACOS
|
137
|
-
File.dirname(@file)
|
145
|
+
::File.dirname(@file)
|
138
146
|
else
|
139
|
-
File.expand_path('../', @file)
|
147
|
+
::File.expand_path('../', @file)
|
140
148
|
end
|
141
149
|
end
|
142
150
|
end
|
@@ -8,7 +8,7 @@ module AppInfo
|
|
8
8
|
extend Forwardable
|
9
9
|
|
10
10
|
def self.parse(path, name = 'Frameworks')
|
11
|
-
files = Dir.glob(File.join(path, name.to_s, '*'))
|
11
|
+
files = Dir.glob(::File.join(path, name.to_s, '*'))
|
12
12
|
return [] if files.empty?
|
13
13
|
|
14
14
|
files.sort.each_with_object([]) do |file, obj|
|
@@ -26,7 +26,7 @@ module AppInfo
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def name
|
29
|
-
File.basename(file)
|
29
|
+
::File.basename(file)
|
30
30
|
end
|
31
31
|
|
32
32
|
def macho
|
@@ -37,11 +37,11 @@ module AppInfo
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def lib?
|
40
|
-
File.file?(file)
|
40
|
+
::File.file?(file)
|
41
41
|
end
|
42
42
|
|
43
43
|
def info
|
44
|
-
@info ||= InfoPlist.new(File.join(file, 'Info.plist'))
|
44
|
+
@info ||= InfoPlist.new(::File.join(file, 'Info.plist'))
|
45
45
|
end
|
46
46
|
|
47
47
|
def to_s
|
data/lib/app_info/ipa.rb
CHANGED
@@ -7,7 +7,7 @@ require 'cfpropertylist'
|
|
7
7
|
|
8
8
|
module AppInfo
|
9
9
|
# IPA parser
|
10
|
-
class IPA
|
10
|
+
class IPA < File
|
11
11
|
include Helper::HumanFileSize
|
12
12
|
include Helper::Archive
|
13
13
|
extend Forwardable
|
@@ -25,18 +25,26 @@ module AppInfo
|
|
25
25
|
INHOUSE = 'Enterprise' # Rename and Alias to enterprise
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
# return file size
|
29
|
+
# @example Read file size in integer
|
30
|
+
# aab.size # => 3618865
|
31
|
+
#
|
32
|
+
# @example Read file size in human readabale
|
33
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
34
|
+
#
|
35
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
36
|
+
# @return [Integer, String]
|
32
37
|
def size(human_size: false)
|
33
38
|
file_to_human_size(@file, human_size: human_size)
|
34
39
|
end
|
35
40
|
|
36
|
-
def
|
41
|
+
def file_type
|
42
|
+
Format::IPA
|
43
|
+
end
|
44
|
+
|
45
|
+
def platform
|
37
46
|
Platform::IOS
|
38
47
|
end
|
39
|
-
alias file_type os
|
40
48
|
|
41
49
|
def_delegators :info, :iphone?, :ipad?, :universal?, :build_version, :name,
|
42
50
|
:release_version, :identifier, :bundle_id, :display_name,
|
@@ -70,7 +78,7 @@ module AppInfo
|
|
70
78
|
end
|
71
79
|
|
72
80
|
def archs
|
73
|
-
return unless File.exist?(bundle_path)
|
81
|
+
return unless ::File.exist?(bundle_path)
|
74
82
|
|
75
83
|
file = MachO.open(bundle_path)
|
76
84
|
case file
|
@@ -114,14 +122,14 @@ module AppInfo
|
|
114
122
|
end
|
115
123
|
|
116
124
|
def mobileprovision?
|
117
|
-
File.exist?(mobileprovision_path)
|
125
|
+
::File.exist?(mobileprovision_path)
|
118
126
|
end
|
119
127
|
|
120
128
|
def mobileprovision_path
|
121
129
|
filename = 'embedded.mobileprovision'
|
122
|
-
@mobileprovision_path ||= File.join(@file, filename)
|
123
|
-
unless File.exist?(@mobileprovision_path)
|
124
|
-
@mobileprovision_path = File.join(app_path, filename)
|
130
|
+
@mobileprovision_path ||= ::File.join(@file, filename)
|
131
|
+
unless ::File.exist?(@mobileprovision_path)
|
132
|
+
@mobileprovision_path = ::File.join(app_path, filename)
|
125
133
|
end
|
126
134
|
|
127
135
|
@mobileprovision_path
|
@@ -134,15 +142,15 @@ module AppInfo
|
|
134
142
|
end
|
135
143
|
|
136
144
|
def metadata?
|
137
|
-
File.exist?(metadata_path)
|
145
|
+
::File.exist?(metadata_path)
|
138
146
|
end
|
139
147
|
|
140
148
|
def metadata_path
|
141
|
-
@metadata_path ||= File.join(contents, 'iTunesMetadata.plist')
|
149
|
+
@metadata_path ||= ::File.join(contents, 'iTunesMetadata.plist')
|
142
150
|
end
|
143
151
|
|
144
152
|
def bundle_path
|
145
|
-
@bundle_path ||= File.join(app_path, info.bundle_name)
|
153
|
+
@bundle_path ||= ::File.join(app_path, info.bundle_name)
|
146
154
|
end
|
147
155
|
|
148
156
|
def info
|
@@ -150,35 +158,32 @@ module AppInfo
|
|
150
158
|
end
|
151
159
|
|
152
160
|
def info_path
|
153
|
-
@info_path ||= File.join(app_path, 'Info.plist')
|
161
|
+
@info_path ||= ::File.join(app_path, 'Info.plist')
|
154
162
|
end
|
155
163
|
|
156
164
|
def app_path
|
157
|
-
@app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
|
165
|
+
@app_path ||= Dir.glob(::File.join(contents, 'Payload', '*.app')).first
|
158
166
|
end
|
159
167
|
|
160
168
|
IPHONE_KEY = 'CFBundleIcons'
|
161
169
|
IPAD_KEY = 'CFBundleIcons~ipad'
|
162
170
|
|
163
171
|
def icons_path
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
@icons_path << file
|
172
|
+
@icons_path ||= lambda {
|
173
|
+
icon_keys.each_with_object([]) do |name, icons|
|
174
|
+
filenames = info.try(:[], name)
|
175
|
+
.try(:[], 'CFBundlePrimaryIcon')
|
176
|
+
.try(:[], 'CFBundleIconFiles')
|
177
|
+
|
178
|
+
next if filenames.nil? || filenames.empty?
|
179
|
+
|
180
|
+
filenames.each do |filename|
|
181
|
+
Dir.glob(::File.join(app_path, "#{filename}*")).find_all.each do |file|
|
182
|
+
icons << file
|
183
|
+
end
|
177
184
|
end
|
178
185
|
end
|
179
|
-
|
180
|
-
|
181
|
-
@icons_path
|
186
|
+
}.call
|
182
187
|
end
|
183
188
|
|
184
189
|
def clear!
|
@@ -197,7 +202,7 @@ module AppInfo
|
|
197
202
|
end
|
198
203
|
|
199
204
|
def contents
|
200
|
-
@contents ||= unarchive(@file,
|
205
|
+
@contents ||= unarchive(@file, prefix: 'ios')
|
201
206
|
end
|
202
207
|
|
203
208
|
private
|
@@ -206,7 +211,7 @@ module AppInfo
|
|
206
211
|
uncrushed_file = uncrush ? uncrush_png(file) : nil
|
207
212
|
|
208
213
|
{
|
209
|
-
name: File.basename(file),
|
214
|
+
name: ::File.basename(file),
|
210
215
|
file: file,
|
211
216
|
uncrushed_file: uncrushed_file,
|
212
217
|
dimensions: PngUncrush.dimensions(file)
|
@@ -217,7 +222,7 @@ module AppInfo
|
|
217
222
|
def uncrush_png(src_file)
|
218
223
|
dest_file = tempdir(src_file, prefix: 'uncrushed')
|
219
224
|
PngUncrush.decompress(src_file, dest_file)
|
220
|
-
File.exist?(dest_file) ? dest_file : nil
|
225
|
+
::File.exist?(dest_file) ? dest_file : nil
|
221
226
|
end
|
222
227
|
|
223
228
|
def icon_keys
|
data/lib/app_info/macos.rb
CHANGED
@@ -7,7 +7,7 @@ require 'cfpropertylist'
|
|
7
7
|
|
8
8
|
module AppInfo
|
9
9
|
# MacOS App parser
|
10
|
-
class Macos
|
10
|
+
class Macos < File
|
11
11
|
include Helper::HumanFileSize
|
12
12
|
include Helper::Archive
|
13
13
|
extend Forwardable
|
@@ -21,18 +21,26 @@ module AppInfo
|
|
21
21
|
APPSTORE = 'AppStore'
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# return file size
|
25
|
+
# @example Read file size in integer
|
26
|
+
# aab.size # => 3618865
|
27
|
+
#
|
28
|
+
# @example Read file size in human readabale
|
29
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
30
|
+
#
|
31
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
32
|
+
# @return [Integer, String]
|
28
33
|
def size(human_size: false)
|
29
34
|
file_to_human_size(@file, human_size: human_size)
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
37
|
+
def file_type
|
38
|
+
Format::MACOS
|
39
|
+
end
|
40
|
+
|
41
|
+
def platform
|
33
42
|
Platform::MACOS
|
34
43
|
end
|
35
|
-
alias file_type os
|
36
44
|
|
37
45
|
def_delegators :info, :macos?, :iphone?, :ipad?, :universal?, :build_version, :name,
|
38
46
|
:release_version, :identifier, :bundle_id, :display_name,
|
@@ -56,14 +64,14 @@ module AppInfo
|
|
56
64
|
end
|
57
65
|
|
58
66
|
def stored?
|
59
|
-
File.exist?(store_path)
|
67
|
+
::File.exist?(store_path)
|
60
68
|
end
|
61
69
|
|
62
70
|
def icons(convert: true)
|
63
71
|
return unless icon_file
|
64
72
|
|
65
73
|
data = {
|
66
|
-
name: File.basename(icon_file),
|
74
|
+
name: ::File.basename(icon_file),
|
67
75
|
file: icon_file
|
68
76
|
}
|
69
77
|
|
@@ -72,7 +80,7 @@ module AppInfo
|
|
72
80
|
end
|
73
81
|
|
74
82
|
def archs
|
75
|
-
return unless File.exist?(binary_path)
|
83
|
+
return unless ::File.exist?(binary_path)
|
76
84
|
|
77
85
|
file = MachO.open(binary_path)
|
78
86
|
case file
|
@@ -97,25 +105,25 @@ module AppInfo
|
|
97
105
|
end
|
98
106
|
|
99
107
|
def mobileprovision?
|
100
|
-
File.exist?(mobileprovision_path)
|
108
|
+
::File.exist?(mobileprovision_path)
|
101
109
|
end
|
102
110
|
|
103
111
|
def mobileprovision_path
|
104
|
-
@mobileprovision_path ||= File.join(app_path, 'Contents', 'embedded.provisionprofile')
|
112
|
+
@mobileprovision_path ||= ::File.join(app_path, 'Contents', 'embedded.provisionprofile')
|
105
113
|
end
|
106
114
|
|
107
115
|
def store_path
|
108
|
-
@store_path ||= File.join(app_path, 'Contents', '_MASReceipt', 'receipt')
|
116
|
+
@store_path ||= ::File.join(app_path, 'Contents', '_MASReceipt', 'receipt')
|
109
117
|
end
|
110
118
|
|
111
119
|
def binary_path
|
112
120
|
return @binary_path if @binary_path
|
113
121
|
|
114
|
-
base_path = File.join(app_path, 'Contents', 'MacOS')
|
122
|
+
base_path = ::File.join(app_path, 'Contents', 'MacOS')
|
115
123
|
binary = info['CFBundleExecutable']
|
116
|
-
return File.join(base_path, binary) if binary
|
124
|
+
return ::File.join(base_path, binary) if binary
|
117
125
|
|
118
|
-
@binary_path ||= Dir.glob(File.join(base_path, '*')).first
|
126
|
+
@binary_path ||= Dir.glob(::File.join(base_path, '*')).first
|
119
127
|
end
|
120
128
|
|
121
129
|
def info
|
@@ -123,11 +131,11 @@ module AppInfo
|
|
123
131
|
end
|
124
132
|
|
125
133
|
def info_path
|
126
|
-
@info_path ||= File.join(app_path, 'Contents', 'Info.plist')
|
134
|
+
@info_path ||= ::File.join(app_path, 'Contents', 'Info.plist')
|
127
135
|
end
|
128
136
|
|
129
137
|
def app_path
|
130
|
-
@app_path ||= Dir.glob(File.join(contents, '*.app')).first
|
138
|
+
@app_path ||= Dir.glob(::File.join(contents, '*.app')).first
|
131
139
|
end
|
132
140
|
|
133
141
|
def clear!
|
@@ -137,14 +145,14 @@ module AppInfo
|
|
137
145
|
|
138
146
|
@contents = nil
|
139
147
|
@app_path = nil
|
140
|
-
@
|
148
|
+
@binary_path = nil
|
141
149
|
@info_path = nil
|
142
150
|
@info = nil
|
143
151
|
@icons = nil
|
144
152
|
end
|
145
153
|
|
146
154
|
def contents
|
147
|
-
@contents ||= unarchive(@file,
|
155
|
+
@contents ||= unarchive(@file, prefix: 'macos')
|
148
156
|
end
|
149
157
|
|
150
158
|
private
|
@@ -155,8 +163,8 @@ module AppInfo
|
|
155
163
|
info.icons.each do |key|
|
156
164
|
next unless value = info[key]
|
157
165
|
|
158
|
-
file = File.join(app_path, 'Contents', 'Resources', "#{value}.icns")
|
159
|
-
next unless File.file?(file)
|
166
|
+
file = ::File.join(app_path, 'Contents', 'Resources', "#{value}.icns")
|
167
|
+
next unless ::File.file?(file)
|
160
168
|
|
161
169
|
return @icon_file = file
|
162
170
|
end
|
@@ -173,14 +181,14 @@ module AppInfo
|
|
173
181
|
file = data[:file]
|
174
182
|
reader = Icns::Reader.new(file)
|
175
183
|
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')
|
184
|
+
dest_filename = "#{::File.basename(file, '.icns')}_#{size}x#{size}.png"
|
185
|
+
dest_file = tempdir(::File.join(::File.dirname(file), dest_filename), prefix: 'converted')
|
178
186
|
next unless icon_data = reader.image(size: size)
|
179
187
|
|
180
|
-
File.write(dest_file, icon_data, encoding: Encoding::BINARY)
|
188
|
+
::File.write(dest_file, icon_data, encoding: Encoding::BINARY)
|
181
189
|
|
182
190
|
data[:sets] << {
|
183
|
-
name: File.basename(dest_filename),
|
191
|
+
name: ::File.basename(dest_filename),
|
184
192
|
file: dest_file,
|
185
193
|
dimensions: ImageSize.path(dest_file).size
|
186
194
|
}
|
@@ -5,9 +5,9 @@ require 'cfpropertylist'
|
|
5
5
|
|
6
6
|
module AppInfo
|
7
7
|
# .mobileprovision file parser
|
8
|
-
class MobileProvision
|
9
|
-
def
|
10
|
-
|
8
|
+
class MobileProvision < File
|
9
|
+
def file_type
|
10
|
+
Format::MOBILEPROVISION
|
11
11
|
end
|
12
12
|
|
13
13
|
def name
|
@@ -66,12 +66,22 @@ module AppInfo
|
|
66
66
|
mobileprovision.try(:[], 'Entitlements')
|
67
67
|
end
|
68
68
|
|
69
|
+
# return developer certificates.
|
70
|
+
#
|
71
|
+
# @deprecated Use {#certificates} instead of this method.
|
69
72
|
def developer_certs
|
73
|
+
certificates
|
74
|
+
end
|
75
|
+
|
76
|
+
# return developer certificates.
|
77
|
+
#
|
78
|
+
# @return [Array<Certificate>]
|
79
|
+
def certificates
|
70
80
|
certs = mobileprovision.try(:[], 'DeveloperCertificates')
|
71
81
|
return if certs.empty?
|
72
82
|
|
73
|
-
certs.each_with_object([]) do |
|
74
|
-
obj <<
|
83
|
+
certs.each_with_object([]) do |cert_data, obj|
|
84
|
+
obj << Certificate.parse(cert_data)
|
75
85
|
end
|
76
86
|
end
|
77
87
|
|
@@ -142,10 +152,10 @@ module AppInfo
|
|
142
152
|
when 'com.apple.developer.networking.vpn.api'
|
143
153
|
capabilities << 'Personal VPN'
|
144
154
|
when 'com.apple.developer.healthkit',
|
145
|
-
|
155
|
+
'com.apple.developer.healthkit.access'
|
146
156
|
capabilities << 'HealthKit' unless capabilities.include?('HealthKit')
|
147
157
|
when 'com.apple.developer.icloud-services',
|
148
|
-
|
158
|
+
'com.apple.developer.icloud-container-identifiers'
|
149
159
|
capabilities << 'iCloud' unless capabilities.include?('iCloud')
|
150
160
|
when 'com.apple.developer.in-app-payments'
|
151
161
|
capabilities << 'Apple Pay'
|
@@ -201,9 +211,9 @@ module AppInfo
|
|
201
211
|
end
|
202
212
|
|
203
213
|
def mobileprovision
|
204
|
-
return @mobileprovision = nil unless File.exist?(@
|
214
|
+
return @mobileprovision = nil unless ::File.exist?(@file)
|
205
215
|
|
206
|
-
data = File.read(@
|
216
|
+
data = ::File.read(@file)
|
207
217
|
data = strip_plist_wrapper(data) unless bplist?(data)
|
208
218
|
list = CFPropertyList::List.new(data: data).value
|
209
219
|
@mobileprovision = CFPropertyList.native_types(list)
|
@@ -235,26 +245,5 @@ module AppInfo
|
|
235
245
|
end_point = raw.index(end_tag) + end_tag.size - 1
|
236
246
|
raw[start_point..end_point]
|
237
247
|
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
248
|
end
|
260
249
|
end
|