app-info 2.8.5 → 3.0.0.beta2
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/.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!
|