app-info 2.8.5 → 3.0.0.beta2
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 +33 -11
- data/CHANGELOG.md +34 -1
- data/Gemfile +7 -1
- data/README.md +68 -13
- data/Rakefile +11 -0
- data/app_info.gemspec +12 -3
- data/lib/app_info/aab.rb +48 -108
- 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 +162 -0
- data/lib/app_info/apk.rb +54 -111
- data/lib/app_info/apple.rb +192 -0
- data/lib/app_info/certificate.rb +175 -0
- data/lib/app_info/const.rb +75 -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 +31 -135
- data/lib/app_info/error.rb +2 -2
- 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 -126
- 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 +67 -49
- data/lib/app_info/pe.rb +260 -0
- data/lib/app_info/png_uncrush.rb +24 -4
- data/lib/app_info/proguard.rb +29 -16
- data/lib/app_info/protobuf/manifest.rb +6 -3
- 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 +90 -46
- metadata +48 -35
@@ -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] {Platform}
|
11
|
+
def platform
|
12
|
+
Platform::APPLE
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Symbol] {OperaSystem}
|
16
|
+
def opera_system
|
17
|
+
case opera_systems[0]
|
18
|
+
when :macos
|
19
|
+
OperaSystem::MACOS
|
20
|
+
when :ios
|
21
|
+
OperaSystem::IOS
|
22
|
+
else
|
23
|
+
raise NotImplementedError, "Unkonwn opera_system: #{opera_systems[0]}"
|
24
|
+
end
|
11
25
|
end
|
12
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,7 +42,8 @@ module AppInfo
|
|
25
42
|
return :enterprise if enterprise?
|
26
43
|
end
|
27
44
|
|
28
|
-
|
45
|
+
# @return [Array<Symbol>]
|
46
|
+
def opera_systems
|
29
47
|
return unless platforms = mobileprovision.try(:[], 'Platform')
|
30
48
|
|
31
49
|
platforms.map do |v|
|
@@ -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
|
83
|
-
when
|
114
|
+
case opera_system
|
115
|
+
when OperaSystem::IOS
|
84
116
|
entitlements['get-task-allow'] == true
|
85
|
-
when
|
117
|
+
when OperaSystem::MACOS
|
86
118
|
!devices.nil?
|
87
119
|
else
|
88
|
-
raise
|
120
|
+
raise NotImplementedError, "Unknown opera_system: #{opera_system}"
|
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
|
97
|
-
when
|
129
|
+
case opera_system
|
130
|
+
when OperaSystem::IOS
|
98
131
|
!development? && entitlements.key?('beta-reports-active')
|
99
|
-
when
|
132
|
+
when OperaSystem::MACOS
|
100
133
|
!development?
|
101
134
|
else
|
102
|
-
raise
|
135
|
+
raise NotImplementedError, "Unknown opera_system: #{opera_system}"
|
103
136
|
end
|
104
137
|
end
|
105
138
|
|
139
|
+
# @return [Boolea]
|
106
140
|
def adhoc?
|
107
|
-
return false if
|
141
|
+
return false if opera_system == OperaSystem::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
|
148
|
+
return false if opera_system == OperaSystem::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
|
data/lib/app_info/pe.rb
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pedump'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
module AppInfo
|
8
|
+
# Windows PE parser
|
9
|
+
#
|
10
|
+
# @see https://learn.microsoft.com/zh-cn/windows/win32/debug/pe-format Microsoft PE Format
|
11
|
+
class PE < File
|
12
|
+
include Helper::HumanFileSize
|
13
|
+
include Helper::Archive
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
ARCH = {
|
17
|
+
0x014c => 'x86',
|
18
|
+
0x0200 => 'Intel Itanium',
|
19
|
+
0x8664 => 'x64',
|
20
|
+
0x1c0 => 'arm',
|
21
|
+
0xaa64 => 'arm64',
|
22
|
+
0x5032 => 'RISC-v 32',
|
23
|
+
0x5064 => 'RISC-v 64',
|
24
|
+
0x5128 => 'RISC-v 128'
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
# @return [Symbol] {Platform}
|
28
|
+
def platform
|
29
|
+
Platform::WINDOWS
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Symbol] {OperaSystem}
|
33
|
+
def opera_system
|
34
|
+
OperaSystem::WINDOWS
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Symbol] {Device}
|
38
|
+
def device
|
39
|
+
Device::WINDOWS
|
40
|
+
end
|
41
|
+
|
42
|
+
# return file size
|
43
|
+
# @example Read file size in integer
|
44
|
+
# aab.size # => 3618865
|
45
|
+
#
|
46
|
+
# @example Read file size in human readabale
|
47
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
48
|
+
#
|
49
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
50
|
+
# @return [Integer, String]
|
51
|
+
def size(human_size: false)
|
52
|
+
file_to_human_size(@file, human_size: human_size)
|
53
|
+
end
|
54
|
+
|
55
|
+
def binary_size(human_size: false)
|
56
|
+
file_to_human_size(binary_file, human_size: human_size)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @!method product_name
|
60
|
+
# @see VersionInfo#product_name
|
61
|
+
# @return [String]
|
62
|
+
# @!method product_version
|
63
|
+
# @see VersionInfo#product_version
|
64
|
+
# @return [String]
|
65
|
+
# @!method company_name
|
66
|
+
# @see VersionInfo#company_name
|
67
|
+
# @return [String]
|
68
|
+
# @!method assembly_version
|
69
|
+
# @see VersionInfo#assembly_version
|
70
|
+
# @return [String]
|
71
|
+
def_delegators :version_info, :product_name, :product_version, :company_name, :assembly_version
|
72
|
+
|
73
|
+
alias name product_name
|
74
|
+
alias release_version product_version
|
75
|
+
alias build_version assembly_version
|
76
|
+
|
77
|
+
# @return [String]
|
78
|
+
def archs
|
79
|
+
ARCH[image_file_header.Machine] || 'unknown'
|
80
|
+
end
|
81
|
+
alias architectures archs
|
82
|
+
|
83
|
+
# @return [Hash{String => String}] imports imports of libraries
|
84
|
+
def imports
|
85
|
+
@imports ||= pe.imports.each_with_object({}) do |import, obj|
|
86
|
+
obj[import.module_name] = import.first_thunk.map(&:name).compact
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [Array{String}] icons paths of bmp image icons
|
91
|
+
def icons
|
92
|
+
@icons ||= lambda {
|
93
|
+
# Fetch the largest size icon
|
94
|
+
files = []
|
95
|
+
pe.resources&.find_all do |res|
|
96
|
+
next unless res.type == 'ICON'
|
97
|
+
|
98
|
+
filename = "#{::File.basename(file, '.*')}-#{res.type}-#{res.id}.bmp"
|
99
|
+
icon_file = tempdir(filename, prefix: 'pe', system: true)
|
100
|
+
mask_icon_file = icon_file.sub('.bmp', '.mask.bmp')
|
101
|
+
|
102
|
+
begin
|
103
|
+
::File.open(icon_file, 'wb') do |f|
|
104
|
+
f << res.restore_bitmap(io)
|
105
|
+
end
|
106
|
+
|
107
|
+
if res.bitmap_mask(io)
|
108
|
+
mask_icon_file = icon_file.sub('.bmp', '.mask.bmp')
|
109
|
+
::File.open(mask_icon_file, 'wb') do |f|
|
110
|
+
f << res.bitmap_mask(io)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
rescue StandardError => e
|
114
|
+
# ignore pedump throws any exception.
|
115
|
+
raise e unless e.backtrace.first.include?('pedump')
|
116
|
+
|
117
|
+
FileUtils.rm_f(icon_file)
|
118
|
+
ensure
|
119
|
+
next unless ::File.exist?(icon_file)
|
120
|
+
|
121
|
+
mask_file = ::File.exist?(mask_icon_file) ? mask_icon_file : nil
|
122
|
+
files << icon_metadata(icon_file, mask_file: mask_file)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
files
|
127
|
+
}.call
|
128
|
+
end
|
129
|
+
|
130
|
+
# @return [PEdump]
|
131
|
+
def pe
|
132
|
+
@pe ||= lambda {
|
133
|
+
pe = PEdump.new(io)
|
134
|
+
pe.logger.level = Logger::FATAL # ignore :warn logger output
|
135
|
+
pe
|
136
|
+
}.call
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [VersionInfo]
|
140
|
+
def version_info
|
141
|
+
@version_info ||= VersionInfo.new(pe.version_info)
|
142
|
+
end
|
143
|
+
|
144
|
+
def clear!
|
145
|
+
@io = nil
|
146
|
+
@pe = nil
|
147
|
+
@icons = nil
|
148
|
+
@imports = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
# @return [String] binary_file path
|
152
|
+
def binary_file
|
153
|
+
@binary_file ||= lambda {
|
154
|
+
file_io = ::File.open(@file, 'rb')
|
155
|
+
return @file unless file_io.read(100) =~ AppInfo::ZIP_RETGEX
|
156
|
+
|
157
|
+
zip_file = Zip::File.open(@file)
|
158
|
+
zip_entry = zip_file.glob('*.exe').first
|
159
|
+
raise NotFoundError, 'Not found .exe file in archive file' if zip_entry.nil?
|
160
|
+
|
161
|
+
exe_file = tempdir(zip_entry.name, prefix: 'pe-exe', system: true)
|
162
|
+
zip_entry.extract(exe_file)
|
163
|
+
zip_file.close
|
164
|
+
|
165
|
+
return exe_file
|
166
|
+
}.call
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def image_file_header
|
172
|
+
@image_file_header ||= pe.pe.image_file_header
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Hash{Symbol => String}]
|
176
|
+
def icon_metadata(file, mask_file: nil)
|
177
|
+
{
|
178
|
+
name: ::File.basename(file),
|
179
|
+
file: file,
|
180
|
+
mask: mask_file,
|
181
|
+
dimensions: ImageSize.path(file).size
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
# @return [File]
|
186
|
+
def io
|
187
|
+
@io ||= ::File.open(binary_file, 'rb')
|
188
|
+
end
|
189
|
+
|
190
|
+
# VersionInfo class
|
191
|
+
#
|
192
|
+
# Ref: https://learn.microsoft.com/zh-cn/windows/win32/menurc/versioninfo-resource
|
193
|
+
class VersionInfo
|
194
|
+
def initialize(raw)
|
195
|
+
@raw = raw
|
196
|
+
end
|
197
|
+
|
198
|
+
# @return [String]
|
199
|
+
def company_name
|
200
|
+
@company_name ||= value_of('CompanyName')
|
201
|
+
end
|
202
|
+
|
203
|
+
# @return [String]
|
204
|
+
def product_name
|
205
|
+
@product_name ||= value_of('ProductName')
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [String]
|
209
|
+
def product_version
|
210
|
+
@product_version ||= value_of('ProductVersion')
|
211
|
+
end
|
212
|
+
|
213
|
+
# @return [String]
|
214
|
+
def assembly_version
|
215
|
+
@assembly_version ||= value_of('Assembly Version')
|
216
|
+
end
|
217
|
+
|
218
|
+
# @return [String]
|
219
|
+
def file_version
|
220
|
+
@file_version ||= value_of('FileVersion')
|
221
|
+
end
|
222
|
+
|
223
|
+
# @return [String]
|
224
|
+
def file_description
|
225
|
+
@file_description ||= value_of('FileDescription')
|
226
|
+
end
|
227
|
+
|
228
|
+
# @return [String]
|
229
|
+
def copyright
|
230
|
+
@copyright ||= value_of('LegalCopyright')
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def value_of(key)
|
236
|
+
info.each do |v|
|
237
|
+
return v[:Value] if v[:szKey] == key.to_s
|
238
|
+
end
|
239
|
+
|
240
|
+
nil
|
241
|
+
end
|
242
|
+
|
243
|
+
def info
|
244
|
+
return @info if @info
|
245
|
+
|
246
|
+
@raw.each do |item|
|
247
|
+
next unless item.is_a?(PEdump::VS_VERSIONINFO)
|
248
|
+
|
249
|
+
versions = item[:Children].select { |v| v.is_a?(PEdump::StringFileInfo) }
|
250
|
+
next if versions.empty?
|
251
|
+
|
252
|
+
@info = versions[0][:Children][0][:Children]
|
253
|
+
return @info
|
254
|
+
end
|
255
|
+
|
256
|
+
@info = []
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
data/lib/app_info/png_uncrush.rb
CHANGED
@@ -7,9 +7,10 @@ require 'zlib'
|
|
7
7
|
require 'stringio'
|
8
8
|
|
9
9
|
module AppInfo
|
10
|
+
# Decompress iOS Png image file.
|
11
|
+
# @see https://github.com/swcai/iphone-png-normalizer swcai/iphone-png-normalizer
|
12
|
+
# @author Wenwei Cai
|
10
13
|
class PngUncrush
|
11
|
-
class Error < StandardError; end
|
12
|
-
|
13
14
|
class FormatError < Error; end
|
14
15
|
|
15
16
|
class PngReader # :nodoc:
|
@@ -31,18 +32,24 @@ module AppInfo
|
|
31
32
|
@data = String.new
|
32
33
|
end
|
33
34
|
|
35
|
+
# @return [Integer]
|
34
36
|
def size
|
35
37
|
@io.size
|
36
38
|
end
|
37
39
|
|
40
|
+
# @param [String] format
|
41
|
+
# @return [String]
|
42
|
+
# @see IO package data
|
38
43
|
def unpack(format)
|
39
44
|
@io.unpack(format)
|
40
45
|
end
|
41
46
|
|
47
|
+
# @return [String]
|
42
48
|
def header
|
43
49
|
@header ||= self[0, 8]
|
44
50
|
end
|
45
51
|
|
52
|
+
# @return [Boolean]
|
46
53
|
def png?
|
47
54
|
header.bytes == PNG_HEADER
|
48
55
|
end
|
@@ -60,23 +67,36 @@ module AppInfo
|
|
60
67
|
end
|
61
68
|
end
|
62
69
|
|
70
|
+
# Decompress crushed png file.
|
71
|
+
# @param [String] input path of png file
|
72
|
+
# @param [String] output path of output file
|
73
|
+
# @return [Boolean] result whether decompress successfully
|
63
74
|
def self.decompress(input, output)
|
64
75
|
new(input).decompress(output)
|
65
76
|
end
|
66
77
|
|
78
|
+
# get png dimensions
|
79
|
+
# @param [String] input path of png file
|
80
|
+
# @return [Array<Integer>] dimensions width, height value of png file
|
67
81
|
def self.dimensions(input)
|
68
82
|
new(input).dimensions
|
69
83
|
end
|
70
84
|
|
71
85
|
def initialize(filename)
|
72
|
-
@io = PngReader.new(File.open(filename))
|
86
|
+
@io = PngReader.new(::File.open(filename))
|
73
87
|
raise FormatError, 'not a png file' unless @io.png?
|
74
88
|
end
|
75
89
|
|
90
|
+
# get png dimensions
|
91
|
+
# @param [String] input path of png file
|
92
|
+
# @return [Array<Integer>] dimensions width, height value of png file
|
76
93
|
def dimensions
|
77
94
|
_dump_sections(dimensions: true)
|
78
95
|
end
|
79
96
|
|
97
|
+
# Decompress crushed png file.
|
98
|
+
# @param [String] output path of output file
|
99
|
+
# @return [Boolean] result whether decompress successfully
|
80
100
|
def decompress(output)
|
81
101
|
content = _remap(_dump_sections)
|
82
102
|
return false unless content
|
@@ -125,7 +145,7 @@ module AppInfo
|
|
125
145
|
end
|
126
146
|
|
127
147
|
def write_file(path, content)
|
128
|
-
File.write(path, content, encoding: Encoding::BINARY)
|
148
|
+
::File.write(path, content, encoding: Encoding::BINARY)
|
129
149
|
true
|
130
150
|
end
|
131
151
|
|
data/lib/app_info/proguard.rb
CHANGED
@@ -5,52 +5,59 @@ require 'rexml/document'
|
|
5
5
|
|
6
6
|
module AppInfo
|
7
7
|
# Proguard parser
|
8
|
-
class Proguard
|
8
|
+
class Proguard < File
|
9
9
|
include Helper::Archive
|
10
10
|
|
11
11
|
NAMESPACE = UUIDTools::UUID.sha1_create(UUIDTools::UUID_DNS_NAMESPACE, 'icyleaf.com')
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@file = file
|
13
|
+
# @return [Symbol] {Platform}
|
14
|
+
def platform
|
15
|
+
Platform::GOOGLE
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
# @return [Symbol] {OperaSystem}
|
19
|
+
def opera_system
|
20
|
+
OperaSystem::ANDROID
|
21
21
|
end
|
22
22
|
|
23
|
+
# @return [String]
|
23
24
|
def uuid
|
24
25
|
# Similar to https://docs.sentry.io/workflow/debug-files/#proguard-uuids
|
25
|
-
UUIDTools::UUID.sha1_create(NAMESPACE, File.read(mapping_path)).to_s
|
26
|
+
UUIDTools::UUID.sha1_create(NAMESPACE, ::File.read(mapping_path)).to_s
|
26
27
|
end
|
27
28
|
alias debug_id uuid
|
28
29
|
|
30
|
+
# @return [Boolean]
|
29
31
|
def mapping?
|
30
|
-
File.exist?(mapping_path)
|
32
|
+
::File.exist?(mapping_path)
|
31
33
|
end
|
32
34
|
|
35
|
+
# @return [Boolean]
|
33
36
|
def manifest?
|
34
|
-
File.exist?(manifest_path)
|
37
|
+
::File.exist?(manifest_path)
|
35
38
|
end
|
36
39
|
|
40
|
+
# @return [Boolean]
|
37
41
|
def symbol?
|
38
|
-
File.exist?(symbol_path)
|
42
|
+
::File.exist?(symbol_path)
|
39
43
|
end
|
40
44
|
alias resource? symbol?
|
41
45
|
|
46
|
+
# @return [String, nil]
|
42
47
|
def package_name
|
43
48
|
return unless manifest?
|
44
49
|
|
45
50
|
manifest.root.attributes['package']
|
46
51
|
end
|
47
52
|
|
53
|
+
# @return [String, nil]
|
48
54
|
def releasd_version
|
49
55
|
return unless manifest?
|
50
56
|
|
51
57
|
manifest.root.attributes['package']
|
52
58
|
end
|
53
59
|
|
60
|
+
# @return [String, nil]
|
54
61
|
def version_name
|
55
62
|
return unless manifest?
|
56
63
|
|
@@ -58,6 +65,7 @@ module AppInfo
|
|
58
65
|
end
|
59
66
|
alias release_version version_name
|
60
67
|
|
68
|
+
# @return [String, nil]
|
61
69
|
def version_code
|
62
70
|
return unless manifest?
|
63
71
|
|
@@ -65,27 +73,32 @@ module AppInfo
|
|
65
73
|
end
|
66
74
|
alias build_version version_code
|
67
75
|
|
76
|
+
# @return [REXML::Document]
|
68
77
|
def manifest
|
69
78
|
return unless manifest?
|
70
79
|
|
71
|
-
@manifest ||= REXML::Document.new(File.new(manifest_path))
|
80
|
+
@manifest ||= REXML::Document.new(::File.new(manifest_path))
|
72
81
|
end
|
73
82
|
|
83
|
+
# @return [String]
|
74
84
|
def mapping_path
|
75
|
-
@mapping_path ||= Dir.glob(File.join(contents, '*mapping*.txt')).first
|
85
|
+
@mapping_path ||= Dir.glob(::File.join(contents, '*mapping*.txt')).first
|
76
86
|
end
|
77
87
|
|
88
|
+
# @return [String]
|
78
89
|
def manifest_path
|
79
|
-
@manifest_path ||= File.join(contents, 'AndroidManifest.xml')
|
90
|
+
@manifest_path ||= ::File.join(contents, 'AndroidManifest.xml')
|
80
91
|
end
|
81
92
|
|
93
|
+
# @return [String]
|
82
94
|
def symbol_path
|
83
|
-
@symbol_path ||= File.join(contents, 'R.txt')
|
95
|
+
@symbol_path ||= ::File.join(contents, 'R.txt')
|
84
96
|
end
|
85
97
|
alias resource_path symbol_path
|
86
98
|
|
99
|
+
# @return [String] contents path of contents
|
87
100
|
def contents
|
88
|
-
@contents ||= unarchive(@file,
|
101
|
+
@contents ||= unarchive(@file, prefix: 'proguard')
|
89
102
|
end
|
90
103
|
|
91
104
|
def clear!
|