app-info 2.8.4 → 3.0.0.beta1
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 +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/pe.rb
ADDED
@@ -0,0 +1,226 @@
|
|
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
|
+
def file_type
|
28
|
+
Format::PE
|
29
|
+
end
|
30
|
+
|
31
|
+
def platform
|
32
|
+
Platform::WINDOWS
|
33
|
+
end
|
34
|
+
|
35
|
+
# return file size
|
36
|
+
# @example Read file size in integer
|
37
|
+
# aab.size # => 3618865
|
38
|
+
#
|
39
|
+
# @example Read file size in human readabale
|
40
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
41
|
+
#
|
42
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
43
|
+
# @return [Integer, String]
|
44
|
+
def size(human_size: false)
|
45
|
+
file_to_human_size(@file, human_size: human_size)
|
46
|
+
end
|
47
|
+
|
48
|
+
def binary_size(human_size: false)
|
49
|
+
file_to_human_size(binary_file, human_size: human_size)
|
50
|
+
end
|
51
|
+
|
52
|
+
def_delegators :version_info, :product_name, :product_version, :company_name, :assembly_version
|
53
|
+
|
54
|
+
alias name product_name
|
55
|
+
alias release_version product_version
|
56
|
+
alias build_version assembly_version
|
57
|
+
|
58
|
+
def archs
|
59
|
+
ARCH[image_file_header.Machine] || 'unknown'
|
60
|
+
end
|
61
|
+
alias architectures archs
|
62
|
+
|
63
|
+
def imports
|
64
|
+
@imports ||= pe.imports.each_with_object({}) do |import, obj|
|
65
|
+
obj[import.module_name] = import.first_thunk.map(&:name).compact
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def icons
|
70
|
+
@icons ||= lambda {
|
71
|
+
# Fetch the largest size icon
|
72
|
+
files = []
|
73
|
+
pe.resources&.find_all do |res|
|
74
|
+
next unless res.type == 'ICON'
|
75
|
+
|
76
|
+
filename = "#{::File.basename(file, '.*')}-#{res.type}-#{res.id}.bmp"
|
77
|
+
icon_file = tempdir(filename, prefix: 'pe', system: true)
|
78
|
+
mask_icon_file = icon_file.sub('.bmp', '.mask.bmp')
|
79
|
+
|
80
|
+
begin
|
81
|
+
::File.open(icon_file, 'wb') do |f|
|
82
|
+
f << res.restore_bitmap(io)
|
83
|
+
end
|
84
|
+
|
85
|
+
if res.bitmap_mask(io)
|
86
|
+
mask_icon_file = icon_file.sub('.bmp', '.mask.bmp')
|
87
|
+
::File.open(mask_icon_file, 'wb') do |f|
|
88
|
+
f << res.bitmap_mask(io)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue StandardError => e
|
92
|
+
# ignore pedump throws any exception.
|
93
|
+
raise e unless e.backtrace.first.include?('pedump')
|
94
|
+
|
95
|
+
FileUtils.rm_f(icon_file)
|
96
|
+
ensure
|
97
|
+
next unless ::File.exist?(icon_file)
|
98
|
+
|
99
|
+
mask_file = ::File.exist?(mask_icon_file) ? mask_icon_file : nil
|
100
|
+
files << icon_metadata(icon_file, mask_file: mask_file)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
files
|
105
|
+
}.call
|
106
|
+
end
|
107
|
+
|
108
|
+
def pe
|
109
|
+
@pe ||= lambda {
|
110
|
+
pe = PEdump.new(io)
|
111
|
+
pe.logger.level = Logger::FATAL # ignore :warn logger output
|
112
|
+
pe
|
113
|
+
}.call
|
114
|
+
end
|
115
|
+
|
116
|
+
def version_info
|
117
|
+
@version_info ||= VersionInfo.new(pe.version_info)
|
118
|
+
end
|
119
|
+
|
120
|
+
def clear!
|
121
|
+
@io = nil
|
122
|
+
@pe = nil
|
123
|
+
@icons = nil
|
124
|
+
@imports = nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def binary_file
|
128
|
+
@binary_file ||= lambda {
|
129
|
+
file_io = ::File.open(@file, 'rb')
|
130
|
+
return @file unless file_io.read(100) =~ AppInfo::ZIP_RETGEX
|
131
|
+
|
132
|
+
zip_file = Zip::File.open(@file)
|
133
|
+
zip_entry = zip_file.glob('*.exe').first
|
134
|
+
raise NotFoundWinBinraryError, 'Not found .exe file in archive file' if zip_entry.nil?
|
135
|
+
|
136
|
+
exe_file = tempdir(zip_entry.name, prefix: 'pe-exe', system: true)
|
137
|
+
zip_entry.extract(exe_file)
|
138
|
+
zip_file.close
|
139
|
+
|
140
|
+
return exe_file
|
141
|
+
}.call
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def image_file_header
|
147
|
+
@image_file_header ||= pe.pe.image_file_header
|
148
|
+
end
|
149
|
+
|
150
|
+
def icon_metadata(file, mask_file: nil)
|
151
|
+
{
|
152
|
+
name: ::File.basename(file),
|
153
|
+
file: file,
|
154
|
+
mask: mask_file,
|
155
|
+
dimensions: ImageSize.path(file).size
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def io
|
160
|
+
@io ||= ::File.open(binary_file, 'rb')
|
161
|
+
end
|
162
|
+
|
163
|
+
# VersionInfo class
|
164
|
+
#
|
165
|
+
# Ref: https://learn.microsoft.com/zh-cn/windows/win32/menurc/versioninfo-resource
|
166
|
+
class VersionInfo
|
167
|
+
def initialize(raw)
|
168
|
+
@raw = raw
|
169
|
+
end
|
170
|
+
|
171
|
+
def company_name
|
172
|
+
@company_name ||= value_of('CompanyName')
|
173
|
+
end
|
174
|
+
|
175
|
+
def product_name
|
176
|
+
@product_name ||= value_of('ProductName')
|
177
|
+
end
|
178
|
+
|
179
|
+
def product_version
|
180
|
+
@product_version ||= value_of('ProductVersion')
|
181
|
+
end
|
182
|
+
|
183
|
+
def assembly_version
|
184
|
+
@assembly_version ||= value_of('Assembly Version')
|
185
|
+
end
|
186
|
+
|
187
|
+
def file_version
|
188
|
+
@file_version ||= value_of('FileVersion')
|
189
|
+
end
|
190
|
+
|
191
|
+
def file_description
|
192
|
+
@file_description ||= value_of('FileDescription')
|
193
|
+
end
|
194
|
+
|
195
|
+
def copyright
|
196
|
+
@copyright ||= value_of('LegalCopyright')
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def value_of(key)
|
202
|
+
info.each do |v|
|
203
|
+
return v[:Value] if v[:szKey] == key.to_s
|
204
|
+
end
|
205
|
+
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
def info
|
210
|
+
return @info if @info
|
211
|
+
|
212
|
+
@raw.each do |item|
|
213
|
+
next unless item.is_a?(PEdump::VS_VERSIONINFO)
|
214
|
+
|
215
|
+
versions = item[:Children].select { |v| v.is_a?(PEdump::StringFileInfo) }
|
216
|
+
next if versions.empty?
|
217
|
+
|
218
|
+
@info = versions[0][:Children][0][:Children]
|
219
|
+
return @info
|
220
|
+
end
|
221
|
+
|
222
|
+
@info = []
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
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:
|
@@ -69,7 +70,7 @@ module AppInfo
|
|
69
70
|
end
|
70
71
|
|
71
72
|
def initialize(filename)
|
72
|
-
@io = PngReader.new(File.open(filename))
|
73
|
+
@io = PngReader.new(::File.open(filename))
|
73
74
|
raise FormatError, 'not a png file' unless @io.png?
|
74
75
|
end
|
75
76
|
|
@@ -125,7 +126,7 @@ module AppInfo
|
|
125
126
|
end
|
126
127
|
|
127
128
|
def write_file(path, content)
|
128
|
-
File.write(path, content, encoding: Encoding::BINARY)
|
129
|
+
::File.write(path, content, encoding: Encoding::BINARY)
|
129
130
|
true
|
130
131
|
end
|
131
132
|
|
data/lib/app_info/proguard.rb
CHANGED
@@ -5,37 +5,31 @@ 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
|
-
attr_reader :file
|
14
|
-
|
15
|
-
def initialize(file)
|
16
|
-
@file = file
|
17
|
-
end
|
18
|
-
|
19
13
|
def file_type
|
20
|
-
|
14
|
+
Format::PROGUARD
|
21
15
|
end
|
22
16
|
|
23
17
|
def uuid
|
24
18
|
# Similar to https://docs.sentry.io/workflow/debug-files/#proguard-uuids
|
25
|
-
UUIDTools::UUID.sha1_create(NAMESPACE, File.read(mapping_path)).to_s
|
19
|
+
UUIDTools::UUID.sha1_create(NAMESPACE, ::File.read(mapping_path)).to_s
|
26
20
|
end
|
27
21
|
alias debug_id uuid
|
28
22
|
|
29
23
|
def mapping?
|
30
|
-
File.exist?(mapping_path)
|
24
|
+
::File.exist?(mapping_path)
|
31
25
|
end
|
32
26
|
|
33
27
|
def manifest?
|
34
|
-
File.exist?(manifest_path)
|
28
|
+
::File.exist?(manifest_path)
|
35
29
|
end
|
36
30
|
|
37
31
|
def symbol?
|
38
|
-
File.exist?(symbol_path)
|
32
|
+
::File.exist?(symbol_path)
|
39
33
|
end
|
40
34
|
alias resource? symbol?
|
41
35
|
|
@@ -68,24 +62,24 @@ module AppInfo
|
|
68
62
|
def manifest
|
69
63
|
return unless manifest?
|
70
64
|
|
71
|
-
@manifest ||= REXML::Document.new(File.new(manifest_path))
|
65
|
+
@manifest ||= REXML::Document.new(::File.new(manifest_path))
|
72
66
|
end
|
73
67
|
|
74
68
|
def mapping_path
|
75
|
-
@mapping_path ||= Dir.glob(File.join(contents, '*mapping*.txt')).first
|
69
|
+
@mapping_path ||= Dir.glob(::File.join(contents, '*mapping*.txt')).first
|
76
70
|
end
|
77
71
|
|
78
72
|
def manifest_path
|
79
|
-
@manifest_path ||= File.join(contents, 'AndroidManifest.xml')
|
73
|
+
@manifest_path ||= ::File.join(contents, 'AndroidManifest.xml')
|
80
74
|
end
|
81
75
|
|
82
76
|
def symbol_path
|
83
|
-
@symbol_path ||= File.join(contents, 'R.txt')
|
77
|
+
@symbol_path ||= ::File.join(contents, 'R.txt')
|
84
78
|
end
|
85
79
|
alias resource_path symbol_path
|
86
80
|
|
87
81
|
def contents
|
88
|
-
@contents ||= unarchive(@file,
|
82
|
+
@contents ||= unarchive(@file, prefix: 'proguard')
|
89
83
|
end
|
90
84
|
|
91
85
|
def clear!
|
@@ -7,7 +7,7 @@ require 'app_info/core_ext'
|
|
7
7
|
module AppInfo
|
8
8
|
module Protobuf
|
9
9
|
class Base
|
10
|
-
include Helper::
|
10
|
+
include Helper::GenerateClass
|
11
11
|
|
12
12
|
def initialize(doc, resources = nil)
|
13
13
|
@resources = resources
|
@@ -17,7 +17,7 @@ module AppInfo
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def parse(_)
|
20
|
-
raise 'not implemented'
|
20
|
+
raise ProtobufParseError, 'not implemented'
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -152,6 +152,8 @@ module AppInfo
|
|
152
152
|
|
153
153
|
def intent_filters(search: nil)
|
154
154
|
activities.each_with_object([]) do |activity, obj|
|
155
|
+
next unless activity.respond_to?(:intent_filter)
|
156
|
+
|
155
157
|
intent_filters = activity.intent_filter
|
156
158
|
next if intent_filters.nil? || intent_filters&.empty?
|
157
159
|
|
@@ -182,6 +184,8 @@ module AppInfo
|
|
182
184
|
CATEGORY_BROWSABLE = 'android.intent.category.BROWSABLE'
|
183
185
|
|
184
186
|
def deep_links?
|
187
|
+
return unless respond_to?(:data)
|
188
|
+
|
185
189
|
browsable? && data.any? { |d| DEEP_LINK_SCHEMES.include?(d.scheme) }
|
186
190
|
end
|
187
191
|
|
@@ -193,6 +197,12 @@ module AppInfo
|
|
193
197
|
.uniq
|
194
198
|
end
|
195
199
|
|
200
|
+
def schemes?
|
201
|
+
return unless respond_to?(:data)
|
202
|
+
|
203
|
+
browsable? && data.any? { |d| !DEEP_LINK_SCHEMES.include?(d.scheme) }
|
204
|
+
end
|
205
|
+
|
196
206
|
def schemes
|
197
207
|
return unless schemes?
|
198
208
|
|
@@ -201,23 +211,20 @@ module AppInfo
|
|
201
211
|
.uniq
|
202
212
|
end
|
203
213
|
|
204
|
-
def schemes?
|
205
|
-
browsable? && data.any? { |d| !DEEP_LINK_SCHEMES.include?(d.scheme) }
|
206
|
-
end
|
207
|
-
|
208
214
|
def browsable?
|
209
215
|
exist?(CATEGORY_BROWSABLE)
|
210
216
|
end
|
211
217
|
|
212
218
|
def exist?(name, type: nil)
|
213
219
|
if type.to_s.empty? && !name.start_with?('android.intent.')
|
214
|
-
raise '
|
220
|
+
raise ProtobufParseError, 'Not found intent type'
|
215
221
|
end
|
216
222
|
|
217
223
|
type ||= name.split('.')[2]
|
218
|
-
raise 'Not found type' unless TYPES.include?(type)
|
224
|
+
raise ProtobufParseError, 'Not found intent type' unless TYPES.include?(type)
|
225
|
+
return false unless intent = send(type.to_sym)
|
219
226
|
|
220
|
-
values =
|
227
|
+
values = intent.select { |e| e.name == name }
|
221
228
|
values.empty? ? false : values
|
222
229
|
end
|
223
230
|
end
|
@@ -9,6 +9,13 @@ protoc --ruby_out=. Resources.proto
|
|
9
9
|
protoc --ruby_out=. Configuration.proto
|
10
10
|
```
|
11
11
|
|
12
|
+
## Decode
|
13
|
+
|
14
|
+
```bash
|
15
|
+
protoc --decode=aapt.pb.ResourceTable Resources.proto < aab/base/BundleConfig.pb > BundleConfig.txt
|
16
|
+
protoc --decode=aapt.pb.ResourceTable Resources.proto < aab/base/native.pb > native.txt
|
17
|
+
```
|
18
|
+
|
12
19
|
## Resouces
|
13
20
|
|
14
21
|
`Configuration.proto` and `Resources.proto` can be found in aapt2's github:
|
@@ -322,7 +322,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
|
|
322
322
|
end
|
323
323
|
end
|
324
324
|
|
325
|
+
# @!visibility private
|
325
326
|
module Aapt
|
327
|
+
|
326
328
|
module Pb
|
327
329
|
StringPool = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("aapt.pb.StringPool").msgclass
|
328
330
|
SourcePosition = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("aapt.pb.SourcePosition").msgclass
|
@@ -12,7 +12,7 @@ module AppInfo
|
|
12
12
|
new(doc)
|
13
13
|
end
|
14
14
|
|
15
|
-
include Helper::
|
15
|
+
include Helper::Protobuf
|
16
16
|
|
17
17
|
attr_reader :packages, :tool_fingerprint
|
18
18
|
|
@@ -53,7 +53,7 @@ module AppInfo
|
|
53
53
|
end
|
54
54
|
|
55
55
|
class Package
|
56
|
-
include Helper::
|
56
|
+
include Helper::GenerateClass
|
57
57
|
|
58
58
|
attr_reader :name, :types
|
59
59
|
|
@@ -113,14 +113,14 @@ module AppInfo
|
|
113
113
|
end
|
114
114
|
|
115
115
|
class Entry
|
116
|
+
include Helper::GenerateClass
|
117
|
+
|
116
118
|
def self.parse_from(type, package)
|
117
119
|
type.entry.each_with_object([]) do |entry, obj|
|
118
120
|
obj << Entry.new(entry, package)
|
119
121
|
end
|
120
122
|
end
|
121
123
|
|
122
|
-
include Helper::Defines
|
123
|
-
|
124
124
|
attr_reader :name, :values
|
125
125
|
|
126
126
|
def initialize(doc, package)
|
@@ -155,7 +155,7 @@ module AppInfo
|
|
155
155
|
end
|
156
156
|
|
157
157
|
class Value
|
158
|
-
include Helper::
|
158
|
+
include Helper::Protobuf
|
159
159
|
extend Forwardable
|
160
160
|
|
161
161
|
attr_reader :locale, :config, :original_value, :value, :type
|
data/lib/app_info/version.rb
CHANGED
data/lib/app_info.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'app_info/version'
|
4
|
-
require 'app_info/error'
|
5
4
|
require 'app_info/core_ext'
|
5
|
+
require 'app_info/const'
|
6
|
+
require 'app_info/certificate'
|
6
7
|
require 'app_info/helper'
|
8
|
+
require 'app_info/error'
|
7
9
|
|
10
|
+
require 'app_info/file'
|
8
11
|
require 'app_info/info_plist'
|
9
12
|
require 'app_info/mobile_provision'
|
10
13
|
|
@@ -12,6 +15,7 @@ require 'app_info/ipa'
|
|
12
15
|
require 'app_info/ipa/plugin'
|
13
16
|
require 'app_info/ipa/framework'
|
14
17
|
|
18
|
+
require 'app_info/android/signature'
|
15
19
|
require 'app_info/apk'
|
16
20
|
require 'app_info/aab'
|
17
21
|
|
@@ -19,6 +23,7 @@ require 'app_info/proguard'
|
|
19
23
|
require 'app_info/dsym'
|
20
24
|
|
21
25
|
require 'app_info/macos'
|
26
|
+
require 'app_info/pe'
|
22
27
|
|
23
28
|
# fix invaild date format warnings
|
24
29
|
Zip.warn_invalid_date = false
|
@@ -28,36 +33,56 @@ module AppInfo
|
|
28
33
|
class << self
|
29
34
|
# Get a new parser for automatic
|
30
35
|
def parse(file)
|
31
|
-
raise NotFoundError, file unless File.exist?(file)
|
32
|
-
|
33
|
-
case file_type(file)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
raise NotFoundError, file unless ::File.exist?(file)
|
37
|
+
|
38
|
+
parser = case file_type(file)
|
39
|
+
when Format::IPA then IPA.new(file)
|
40
|
+
when Format::APK then APK.new(file)
|
41
|
+
when Format::AAB then AAB.new(file)
|
42
|
+
when Format::MOBILEPROVISION then MobileProvision.new(file)
|
43
|
+
when Format::DSYM then DSYM.new(file)
|
44
|
+
when Format::PROGUARD then Proguard.new(file)
|
45
|
+
when Format::MACOS then Macos.new(file)
|
46
|
+
when Format::PE then PE.new(file)
|
47
|
+
else
|
48
|
+
raise UnknownFileTypeError, "Do not detect file type: #{file}"
|
49
|
+
end
|
50
|
+
|
51
|
+
return parser unless block_given?
|
52
|
+
|
53
|
+
# call block and clear!
|
54
|
+
yield parser
|
55
|
+
parser.clear!
|
44
56
|
end
|
45
57
|
alias dump parse
|
46
58
|
|
59
|
+
def parse?(file)
|
60
|
+
file_type(file) != Format::UNKNOWN
|
61
|
+
end
|
62
|
+
|
47
63
|
# Detect file type by read file header
|
48
64
|
#
|
49
65
|
# TODO: This can be better solution, if anyone knows, tell me please.
|
50
66
|
def file_type(file)
|
51
|
-
header_hex = File.read(file, 100)
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
67
|
+
header_hex = ::File.read(file, 100)
|
68
|
+
case header_hex
|
69
|
+
when ZIP_RETGEX
|
70
|
+
detect_zip_file(file)
|
71
|
+
when PE_REGEX
|
72
|
+
Format::PE
|
73
|
+
when PLIST_REGEX, BPLIST_REGEX
|
74
|
+
Format::MOBILEPROVISION
|
75
|
+
else
|
76
|
+
Format::UNKNOWN
|
77
|
+
end
|
59
78
|
end
|
60
79
|
|
80
|
+
def logger
|
81
|
+
@logger ||= Logger.new($stdout, level: :warn)
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_writer :logger
|
85
|
+
|
61
86
|
private
|
62
87
|
|
63
88
|
# :nodoc:
|
@@ -65,35 +90,59 @@ module AppInfo
|
|
65
90
|
Zip.warn_invalid_date = false
|
66
91
|
zip_file = Zip::File.open(file)
|
67
92
|
|
68
|
-
return
|
69
|
-
return
|
70
|
-
|
93
|
+
return Format::PROGUARD if proguard_clues?(zip_file)
|
94
|
+
return Format::APK if apk_clues?(zip_file)
|
95
|
+
return Format::AAB if aab_clues?(zip_file)
|
96
|
+
return Format::MACOS if macos_clues?(zip_file)
|
97
|
+
return Format::PE if pe_clues?(zip_file)
|
98
|
+
return Format::UNKNOWN unless clue = other_clues?(zip_file)
|
71
99
|
|
72
|
-
|
73
|
-
|
100
|
+
clue
|
101
|
+
ensure
|
102
|
+
zip_file.close
|
103
|
+
end
|
74
104
|
|
75
|
-
|
76
|
-
|
105
|
+
# :nodoc:
|
106
|
+
def proguard_clues?(zip_file)
|
107
|
+
!zip_file.glob('*mapping*.txt').empty?
|
108
|
+
end
|
77
109
|
|
78
|
-
|
79
|
-
|
110
|
+
# :nodoc:
|
111
|
+
def apk_clues?(zip_file)
|
112
|
+
!zip_file.find_entry('AndroidManifest.xml').nil? &&
|
113
|
+
!zip_file.find_entry('classes.dex').nil?
|
114
|
+
end
|
80
115
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
zip_file.close
|
116
|
+
# :nodoc:
|
117
|
+
def aab_clues?(zip_file)
|
118
|
+
!zip_file.find_entry('base/manifest/AndroidManifest.xml').nil? &&
|
119
|
+
!zip_file.find_entry('BundleConfig.pb').nil?
|
86
120
|
end
|
87
121
|
|
88
|
-
|
89
|
-
|
122
|
+
# :nodoc:
|
123
|
+
def macos_clues?(zip_file)
|
124
|
+
!zip_file.glob('*/Contents/MacOS/*').empty? &&
|
125
|
+
!zip_file.glob('*/Contents/Info.plist').empty?
|
126
|
+
end
|
90
127
|
|
91
128
|
# :nodoc:
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
129
|
+
def pe_clues?(zip_file)
|
130
|
+
!zip_file.glob('*.exe').empty?
|
131
|
+
end
|
132
|
+
|
133
|
+
# :nodoc:
|
134
|
+
def other_clues?(zip_file)
|
135
|
+
zip_file.each do |f|
|
136
|
+
path = f.name
|
137
|
+
|
138
|
+
return Format::IPA if path.include?('Payload/') && path.end_with?('Info.plist')
|
139
|
+
return Format::DSYM if path.include?('Contents/Resources/DWARF/')
|
96
140
|
end
|
97
141
|
end
|
98
142
|
end
|
143
|
+
|
144
|
+
ZIP_RETGEX = /^\x50\x4b\x03\x04/.freeze
|
145
|
+
PE_REGEX = /^MZ/.freeze
|
146
|
+
PLIST_REGEX = /\x3C\x3F\x78\x6D\x6C/.freeze
|
147
|
+
BPLIST_REGEX = /^\x62\x70\x6C\x69\x73\x74/.freeze
|
99
148
|
end
|