app-info 3.0.0.beta1 → 3.0.0.beta3
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/.rubocop.yml +2 -0
- data/CHANGELOG.md +29 -3
- data/README.md +4 -4
- data/lib/app_info/aab.rb +57 -114
- data/lib/app_info/android/signature.rb +5 -5
- data/lib/app_info/android/signatures/base.rb +41 -37
- data/lib/app_info/android/signatures/info.rb +135 -129
- data/lib/app_info/android/signatures/v1.rb +56 -52
- data/lib/app_info/android/signatures/v2.rb +114 -110
- data/lib/app_info/android/signatures/v3.rb +124 -120
- data/lib/app_info/android/signatures/v4.rb +13 -9
- data/lib/app_info/android.rb +181 -0
- data/lib/app_info/apk.rb +68 -100
- data/lib/app_info/apple.rb +192 -0
- data/lib/app_info/certificate.rb +14 -19
- data/lib/app_info/const.rb +48 -13
- data/lib/app_info/dsym.rb +6 -3
- data/lib/app_info/error.rb +1 -7
- data/lib/app_info/file.rb +30 -4
- data/lib/app_info/helper/file_size.rb +1 -1
- data/lib/app_info/info_plist.rb +54 -25
- data/lib/app_info/ipa.rb +39 -118
- data/lib/app_info/macos.rb +38 -94
- data/lib/app_info/mobile_provision.rb +49 -20
- data/lib/app_info/pe.rb +103 -7
- data/lib/app_info/png_uncrush.rb +19 -0
- data/lib/app_info/proguard.rb +21 -2
- data/lib/app_info/protobuf/manifest.rb +5 -1
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +4 -3
- metadata +4 -2
@@ -1,127 +1,131 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module AppInfo
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
3
|
+
module AppInfo
|
4
|
+
class Android < File
|
5
|
+
module Signature
|
6
|
+
# Android v3 Signature
|
7
|
+
#
|
8
|
+
# FULL FORMAT:
|
9
|
+
# OFFSET DATA TYPE DESCRIPTION
|
10
|
+
# * @+0 bytes uint32: signer size in bytes
|
11
|
+
# * @+4 bytes payload signer block
|
12
|
+
# * @+0 bytes unit32: signed data size in bytes
|
13
|
+
# * @+4 bytes payload signed data block
|
14
|
+
# * @+0 bytes unit32: digests with size in bytes
|
15
|
+
# * @+0 bytes unit32: digests with size in bytes
|
16
|
+
# * @+W bytes unit32: minSDK
|
17
|
+
# * @+X+4 bytes unit32: maxSDK
|
18
|
+
# * @+Y+4 bytes unit32: signatures with size in bytes
|
19
|
+
# * @+Y+4 bytes payload signed data block
|
20
|
+
# * @+Z bytes unit32: public key with size in bytes
|
21
|
+
# * @+Z+4 bytes payload signed data block
|
22
|
+
class V3 < Base
|
23
|
+
include AppInfo::Helper::IOBlock
|
24
|
+
include AppInfo::Helper::Signatures
|
25
|
+
include AppInfo::Helper::Algorithm
|
26
|
+
|
27
|
+
# V3 Signature ID 0xf05368c0
|
28
|
+
V3_BLOCK_ID = [0xc0, 0x68, 0x53, 0xf0].freeze
|
29
|
+
|
30
|
+
# V3.1 Signature ID 0x1b93ad61
|
31
|
+
V3_1_BLOCK_ID = [0x61, 0xad, 0x93, 0x1b].freeze
|
32
|
+
|
33
|
+
attr_reader :certificates, :digests
|
34
|
+
|
35
|
+
def version
|
36
|
+
Version::V3
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify
|
40
|
+
begin
|
41
|
+
signers_block = singers_block(V3_1_BLOCK_ID)
|
42
|
+
rescue NotFoundError
|
43
|
+
signers_block = singers_block(V3_BLOCK_ID)
|
44
|
+
end
|
45
|
+
|
46
|
+
@certificates, @digests = verified_certs(signers_block)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def verified_certs(signers_block)
|
52
|
+
unless (signers = length_prefix_block(signers_block))
|
53
|
+
raise SecurityError, 'Not found signers'
|
54
|
+
end
|
55
|
+
|
56
|
+
certificates = []
|
57
|
+
content_digests = {}
|
58
|
+
loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
|
59
|
+
signer_certs, signer_digests = extract_signer_data(signer)
|
60
|
+
certificates.concat(signer_certs)
|
61
|
+
content_digests.merge!(signer_digests)
|
62
|
+
end
|
63
|
+
raise SecurityError, 'No signers found' if certificates.empty?
|
64
|
+
|
65
|
+
[certificates, content_digests]
|
66
|
+
end
|
67
|
+
|
68
|
+
def extract_signer_data(signer)
|
69
|
+
# raw data
|
70
|
+
signed_data = length_prefix_block(signer)
|
71
|
+
|
72
|
+
# TODO: verify min_sdk and max_sdk
|
73
|
+
min_sdk = signer.read(UINT32_SIZE)
|
74
|
+
max_sdk = signer.read(UINT32_SIZE)
|
75
|
+
|
76
|
+
signatures = length_prefix_block(signer)
|
77
|
+
public_key = length_prefix_block(signer, raw: true)
|
78
|
+
|
79
|
+
algorithems = signature_algorithms(signatures)
|
80
|
+
raise SecurityError, 'No signatures found' if algorithems.empty?
|
81
|
+
|
82
|
+
# find best algorithem to verify signed data with public key and signature
|
83
|
+
unless best_algorithem = best_algorithem(algorithems)
|
84
|
+
raise SecurityError, 'No supported signatures found'
|
85
|
+
end
|
86
|
+
|
87
|
+
algorithems_digest = best_algorithem[:digest]
|
88
|
+
signature = best_algorithem[:signature]
|
89
|
+
|
90
|
+
pkey = OpenSSL::PKey.read(public_key)
|
91
|
+
digest = OpenSSL::Digest.new(algorithems_digest)
|
92
|
+
verified = pkey.verify(digest, signature, signed_data.string)
|
93
|
+
raise SecurityError, "#{algorithems_digest} signature did not verify" unless verified
|
94
|
+
|
95
|
+
# verify algorithm ID full equal (and sort) between digests and signature
|
96
|
+
digests = length_prefix_block(signed_data)
|
97
|
+
content_digests = signed_data_digests(digests)
|
98
|
+
content_digest = content_digests[algorithems_digest]&.fetch(:content)
|
99
|
+
|
100
|
+
unless content_digest
|
101
|
+
raise SecurityError,
|
102
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
103
|
+
end
|
104
|
+
|
105
|
+
previous_digest = content_digests.fetch(algorithems_digest)
|
106
|
+
content_digests[algorithems_digest] = content_digest
|
107
|
+
if previous_digest && previous_digest[:content] != content_digest
|
108
|
+
raise SecurityError,
|
109
|
+
'Signature algorithms don\'t match between digests and signatures records'
|
110
|
+
end
|
111
|
+
|
112
|
+
certificates = length_prefix_block(signed_data)
|
113
|
+
certs = signed_data_certs(certificates)
|
114
|
+
raise SecurityError, 'No certificates listed' if certs.empty?
|
115
|
+
|
116
|
+
main_cert = certs[0]
|
117
|
+
if main_cert.public_key.to_der != pkey.to_der
|
118
|
+
raise SecurityError, 'Public key mismatch between certificate and signature record'
|
119
|
+
end
|
120
|
+
|
121
|
+
additional_attrs = length_prefix_block(signed_data)
|
122
|
+
verify_additional_attrs(additional_attrs, certs)
|
123
|
+
|
124
|
+
[certs, content_digests]
|
125
|
+
end
|
42
126
|
end
|
43
127
|
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def verified_certs(signers_block)
|
50
|
-
unless (signers = length_prefix_block(signers_block))
|
51
|
-
raise SecurityError, 'Not found signers'
|
52
|
-
end
|
53
|
-
|
54
|
-
certificates = []
|
55
|
-
content_digests = {}
|
56
|
-
loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
|
57
|
-
signer_certs, signer_digests = extract_signer_data(signer)
|
58
|
-
certificates.concat(signer_certs)
|
59
|
-
content_digests.merge!(signer_digests)
|
60
|
-
end
|
61
|
-
raise SecurityError, 'No signers found' if certificates.empty?
|
62
|
-
|
63
|
-
[certificates, content_digests]
|
64
|
-
end
|
65
|
-
|
66
|
-
def extract_signer_data(signer)
|
67
|
-
# raw data
|
68
|
-
signed_data = length_prefix_block(signer)
|
69
|
-
|
70
|
-
# TODO: verify min_sdk and max_sdk
|
71
|
-
min_sdk = signer.read(UINT32_SIZE)
|
72
|
-
max_sdk = signer.read(UINT32_SIZE)
|
73
|
-
|
74
|
-
signatures = length_prefix_block(signer)
|
75
|
-
public_key = length_prefix_block(signer, raw: true)
|
76
|
-
|
77
|
-
algorithems = signature_algorithms(signatures)
|
78
|
-
raise SecurityError, 'No signatures found' if algorithems.empty?
|
79
|
-
|
80
|
-
# find best algorithem to verify signed data with public key and signature
|
81
|
-
unless best_algorithem = best_algorithem(algorithems)
|
82
|
-
raise SecurityError, 'No supported signatures found'
|
83
|
-
end
|
84
|
-
|
85
|
-
algorithems_digest = best_algorithem[:digest]
|
86
|
-
signature = best_algorithem[:signature]
|
87
|
-
|
88
|
-
pkey = OpenSSL::PKey.read(public_key)
|
89
|
-
digest = OpenSSL::Digest.new(algorithems_digest)
|
90
|
-
verified = pkey.verify(digest, signature, signed_data.string)
|
91
|
-
raise SecurityError, "#{algorithems_digest} signature did not verify" unless verified
|
92
|
-
|
93
|
-
# verify algorithm ID full equal (and sort) between digests and signature
|
94
|
-
digests = length_prefix_block(signed_data)
|
95
|
-
content_digests = signed_data_digests(digests)
|
96
|
-
content_digest = content_digests[algorithems_digest]&.fetch(:content)
|
97
|
-
|
98
|
-
unless content_digest
|
99
|
-
raise SecurityError,
|
100
|
-
'Signature algorithms don\'t match between digests and signatures records'
|
101
|
-
end
|
102
|
-
|
103
|
-
previous_digest = content_digests.fetch(algorithems_digest)
|
104
|
-
content_digests[algorithems_digest] = content_digest
|
105
|
-
if previous_digest && previous_digest[:content] != content_digest
|
106
|
-
raise SecurityError,
|
107
|
-
'Signature algorithms don\'t match between digests and signatures records'
|
108
|
-
end
|
109
|
-
|
110
|
-
certificates = length_prefix_block(signed_data)
|
111
|
-
certs = signed_data_certs(certificates)
|
112
|
-
raise SecurityError, 'No certificates listed' if certs.empty?
|
113
|
-
|
114
|
-
main_cert = certs[0]
|
115
|
-
if main_cert.public_key.to_der != pkey.to_der
|
116
|
-
raise SecurityError, 'Public key mismatch between certificate and signature record'
|
117
|
-
end
|
118
|
-
|
119
|
-
additional_attrs = length_prefix_block(signed_data)
|
120
|
-
verify_additional_attrs(additional_attrs, certs)
|
121
|
-
|
122
|
-
[certs, content_digests]
|
128
|
+
register(Version::V3, V3)
|
123
129
|
end
|
124
130
|
end
|
125
|
-
|
126
|
-
register(Version::V3, V3)
|
127
131
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module AppInfo
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
module AppInfo
|
4
|
+
class Android < File
|
5
|
+
module Signature
|
6
|
+
# Android v4 Signature
|
7
|
+
#
|
8
|
+
# TODO: ApkSignatureSchemeV4Verifier.java
|
9
|
+
class V4 < Base
|
10
|
+
def version
|
11
|
+
Version::V4
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# register(Version::V4, V4)
|
10
16
|
end
|
11
17
|
end
|
12
|
-
|
13
|
-
# register(Version::V4, V4)
|
14
18
|
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_info/android/signature'
|
4
|
+
require 'image_size'
|
5
|
+
require 'forwardable'
|
6
|
+
|
7
|
+
module AppInfo
|
8
|
+
# Android base parser for apk and aab file
|
9
|
+
class Android < File
|
10
|
+
extend Forwardable
|
11
|
+
include Helper::HumanFileSize
|
12
|
+
|
13
|
+
# return file size
|
14
|
+
# @example Read file size in integer
|
15
|
+
# aab.size # => 3618865
|
16
|
+
#
|
17
|
+
# @example Read file size in human readabale
|
18
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
19
|
+
#
|
20
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
21
|
+
# @return [Integer, String]
|
22
|
+
def size(human_size: false)
|
23
|
+
file_to_human_size(@file, human_size: human_size)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Symbol] {Manufacturer}
|
27
|
+
def manufacturer
|
28
|
+
Manufacturer::GOOGLE
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Symbol] {Platform}
|
32
|
+
def platform
|
33
|
+
Platform::ANDROID
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Symbol] {Device}
|
37
|
+
def device
|
38
|
+
if watch?
|
39
|
+
Device::WATCH
|
40
|
+
elsif television?
|
41
|
+
Device::TELEVISION
|
42
|
+
elsif automotive?
|
43
|
+
Device::AUTOMOTIVE
|
44
|
+
elsif tablet?
|
45
|
+
Device::TABLET
|
46
|
+
else
|
47
|
+
Device::PHONE
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @abstract Subclass and override {#name} to implement.
|
52
|
+
def name
|
53
|
+
not_implemented_error!(__method__)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @todo find a way to detect, no way!
|
57
|
+
# @see https://stackoverflow.com/questions/9279111/determine-if-the-device-is-a-smartphone-or-tablet
|
58
|
+
# @return [Boolean] false always false
|
59
|
+
def tablet?
|
60
|
+
# Not works!
|
61
|
+
# resource.first_package
|
62
|
+
# .entries('bool')
|
63
|
+
# .select{|e| e.name == 'isTablet' }
|
64
|
+
# .size >= 1
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Boolean]
|
69
|
+
def watch?
|
70
|
+
!!use_features&.include?('android.hardware.type.watch')
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Boolean]
|
74
|
+
def television?
|
75
|
+
!!use_features&.include?('android.software.leanback')
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Boolean]
|
79
|
+
def automotive?
|
80
|
+
!!use_features&.include?('android.hardware.type.automotive')
|
81
|
+
end
|
82
|
+
|
83
|
+
# @abstract Subclass and override {#use_features} to implement.
|
84
|
+
def use_features
|
85
|
+
not_implemented_error!(__method__)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @abstract Subclass and override {#use_permissions} to implement.
|
89
|
+
def use_permissions
|
90
|
+
not_implemented_error!(__method__)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @abstract Subclass and override {#use_permissions} to implement.
|
94
|
+
def activities
|
95
|
+
not_implemented_error!(__method__)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @abstract Subclass and override {#use_permissions} to implement.
|
99
|
+
def services
|
100
|
+
not_implemented_error!(__method__)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @abstract Subclass and override {#use_permissions} to implement.
|
104
|
+
def components
|
105
|
+
not_implemented_error!(__method__)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return multi version certifiates of signatures
|
109
|
+
# @return [Array<Hash>] signatures
|
110
|
+
# @see AppInfo::Android::Signature.verify
|
111
|
+
def signatures
|
112
|
+
@signatures ||= Android::Signature.verify(self)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Legacy v1 scheme signatures, it will remove soon.
|
116
|
+
# @deprecated Use {#signatures}
|
117
|
+
# @return [Array<OpenSSL::PKCS7, nil>] signatures
|
118
|
+
def signs
|
119
|
+
@signs ||= v1sign&.signatures || []
|
120
|
+
end
|
121
|
+
|
122
|
+
# Legacy v1 scheme certificates, it will remove soon.
|
123
|
+
# @deprecated Use {#signatures}
|
124
|
+
# @return [Array<OpenSSL::PKCS7, nil>] certificates
|
125
|
+
def certificates
|
126
|
+
@certificates ||= v1sign&.certificates || []
|
127
|
+
end
|
128
|
+
|
129
|
+
# @abstract Subclass and override {#manifest} to implement.
|
130
|
+
def manifest
|
131
|
+
not_implemented_error!(__method__)
|
132
|
+
end
|
133
|
+
|
134
|
+
# @abstract Subclass and override {#resource} to implement.
|
135
|
+
def resource
|
136
|
+
not_implemented_error!(__method__)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @abstract Subclass and override {#zip} to implement.
|
140
|
+
def zip
|
141
|
+
not_implemented_error!(__method__)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @abstract Subclass and override {#clear!} to implement.
|
145
|
+
def clear!
|
146
|
+
not_implemented_error!(__method__)
|
147
|
+
end
|
148
|
+
|
149
|
+
# @return [String] contents path of contents
|
150
|
+
def contents
|
151
|
+
@contents ||= ::File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
def extract_icon(icons, exclude: nil)
|
157
|
+
excludes = exclude_icon_exts(exclude: exclude)
|
158
|
+
icons.reject { |icon| icon_ext_match?(icon[:name], excludes) }
|
159
|
+
end
|
160
|
+
|
161
|
+
def exclude_icon_exts(exclude:)
|
162
|
+
case exclude
|
163
|
+
when String then [exclude]
|
164
|
+
when Array then exclude.map(&:to_s)
|
165
|
+
when Symbol then [exclude.to_s]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def icon_ext_match?(file, excludes)
|
170
|
+
return false if file.nil? || excludes.nil?
|
171
|
+
|
172
|
+
excludes.include?(::File.extname(file)[1..-1])
|
173
|
+
end
|
174
|
+
|
175
|
+
def v1sign
|
176
|
+
@v1sign ||= Android::Signature::V1.verify(self)
|
177
|
+
rescue Android::Signature::NotFoundError
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/lib/app_info/apk.rb
CHANGED
@@ -1,49 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'ruby_apk'
|
4
|
-
require 'image_size'
|
5
|
-
require 'forwardable'
|
6
4
|
|
7
5
|
module AppInfo
|
8
6
|
# Parse APK file parser, wrapper for {https://github.com/icyleaf/android_parser android_parser}.
|
9
|
-
class APK <
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
module Device
|
17
|
-
PHONE = 'Phone'
|
18
|
-
TABLET = 'Tablet'
|
19
|
-
WATCH = 'Watch'
|
20
|
-
TV = 'Television'
|
21
|
-
AUTOMOTIVE = 'Automotive'
|
22
|
-
end
|
23
|
-
|
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]
|
33
|
-
def size(human_size: false)
|
34
|
-
file_to_human_size(@file, human_size: human_size)
|
35
|
-
end
|
36
|
-
|
37
|
-
def file_type
|
38
|
-
Format::APK
|
39
|
-
end
|
40
|
-
|
41
|
-
def platform
|
42
|
-
Platform::ANDROID
|
43
|
-
end
|
44
|
-
|
7
|
+
class APK < Android
|
8
|
+
# @!method manifest
|
9
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Apk#manifest-instance_method ::Android::Apk#manifest
|
10
|
+
# @!method resource
|
11
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Apk#resource-instance_method ::Android::Apk#resource
|
12
|
+
# @!method dex
|
13
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Apk#dex-instance_method ::Android::Apk#dex
|
45
14
|
def_delegators :apk, :manifest, :resource, :dex
|
46
15
|
|
16
|
+
# @!method version_name
|
17
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#version_name-instance_method ::Android::Manifest#version_name
|
18
|
+
# @!method package_name
|
19
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#package_name-instance_method ::Android::Manifest#package_name
|
20
|
+
# @!method target_sdk_versionx
|
21
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#target_sdk_versionx-instance_method ::Android::Manifest#target_sdk_version
|
22
|
+
# @!method components
|
23
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#components-instance_method ::Android::Manifest#components
|
24
|
+
# @!method services
|
25
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#services-instance_method ::Android::Manifest#services
|
26
|
+
# @!method use_permissions
|
27
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#use_permissions-instance_method ::Android::Manifest#use_permissions
|
28
|
+
# @!method use_features
|
29
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#use_features-instance_method ::Android::Manifest#use_features
|
30
|
+
# @!method deep_links
|
31
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#deep_links-instance_method ::Android::Manifest#deep_links
|
32
|
+
# @!method schemes
|
33
|
+
# @see https://rubydoc.info/gems/android_parser/Android/Manifest#schemes-instance_method ::Android::Manifest#schemes
|
47
34
|
def_delegators :manifest, :version_name, :package_name, :target_sdk_version,
|
48
35
|
:components, :services, :use_permissions, :use_features,
|
49
36
|
:deep_links, :schemes
|
@@ -61,73 +48,64 @@ module AppInfo
|
|
61
48
|
manifest.label || resource.find('@string/app_name')
|
62
49
|
end
|
63
50
|
|
64
|
-
|
65
|
-
if wear?
|
66
|
-
Device::WATCH
|
67
|
-
elsif tv?
|
68
|
-
Device::TV
|
69
|
-
elsif automotive?
|
70
|
-
Device::AUTOMOTIVE
|
71
|
-
else
|
72
|
-
Device::PHONE
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# TODO: find a way to detect, no way!
|
77
|
-
# def tablet?
|
78
|
-
# end
|
79
|
-
|
80
|
-
def wear?
|
81
|
-
use_features.include?('android.hardware.type.watch')
|
82
|
-
end
|
83
|
-
|
84
|
-
def tv?
|
85
|
-
use_features.include?('android.software.leanback')
|
86
|
-
end
|
87
|
-
|
88
|
-
def automotive?
|
89
|
-
use_features.include?('android.hardware.type.automotive')
|
90
|
-
end
|
91
|
-
|
51
|
+
# @return [String]
|
92
52
|
def min_sdk_version
|
93
53
|
manifest.min_sdk_ver
|
94
54
|
end
|
95
55
|
alias min_os_version min_sdk_version
|
96
56
|
|
97
|
-
#
|
98
|
-
# @return [Array<Hash>]
|
99
|
-
# @see AppInfo::Android::Signature.verify
|
100
|
-
def signatures
|
101
|
-
@signatures ||= Android::Signature.verify(self)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Legacy v1 scheme signatures, it will remove soon.
|
105
|
-
# @deprecated Use {#signatures}
|
106
|
-
# @return [Array<OpenSSL::PKCS7, nil>]
|
107
|
-
def signs
|
108
|
-
@signs ||= v1sign&.signatures || []
|
109
|
-
end
|
110
|
-
|
111
|
-
# Legacy v1 scheme certificates, it will remove soon.
|
112
|
-
# @deprecated Use {#signatures}
|
113
|
-
# @return [Array<OpenSSL::PKCS7, nil>]
|
114
|
-
def certificates
|
115
|
-
@certificates ||= v1sign&.certificates || []
|
116
|
-
end
|
117
|
-
|
57
|
+
# @return [String]
|
118
58
|
def activities
|
119
59
|
components.select { |c| c.type == 'activity' }
|
120
60
|
end
|
121
61
|
|
62
|
+
# @return [::Android::Apk]
|
122
63
|
def apk
|
123
64
|
@apk ||= ::Android::Apk.new(@file)
|
124
65
|
end
|
125
66
|
|
67
|
+
# @return [Zip::File]
|
126
68
|
def zip
|
127
69
|
@zip ||= apk.instance_variable_get(:@zip)
|
128
70
|
end
|
129
71
|
|
130
|
-
|
72
|
+
# Full icons metadata
|
73
|
+
# @example full icons
|
74
|
+
# apk.icons
|
75
|
+
# # => [
|
76
|
+
# # {
|
77
|
+
# # name: 'ic_launcher.png',
|
78
|
+
# # file: '/path/to/ic_launcher.png',
|
79
|
+
# # dimensions: [29, 29]
|
80
|
+
# # },
|
81
|
+
# # {
|
82
|
+
# # name: 'ic_launcher.png',
|
83
|
+
# # file: '/path/to/ic_launcher.png',
|
84
|
+
# # dimensions: [120, 120]
|
85
|
+
# # },
|
86
|
+
# # {
|
87
|
+
# # name: 'ic_launcher.xml',
|
88
|
+
# # file: '/path/to/ic_launcher.xml',
|
89
|
+
# # dimensions: [nil, nil]
|
90
|
+
# # },
|
91
|
+
# # ]
|
92
|
+
# @example exclude xml icons
|
93
|
+
# apk.icons(exclude: :xml)
|
94
|
+
# # => [
|
95
|
+
# # {
|
96
|
+
# # name: 'ic_launcher.png',
|
97
|
+
# # file: '/path/to/ic_launcher.png',
|
98
|
+
# # dimensions: [29, 29]
|
99
|
+
# # },
|
100
|
+
# # {
|
101
|
+
# # name: 'ic_launcher.png',
|
102
|
+
# # file: '/path/to/ic_launcher.png',
|
103
|
+
# # dimensions: [120, 120]
|
104
|
+
# # }
|
105
|
+
# # ]
|
106
|
+
# @param [Boolean] xml return xml icons
|
107
|
+
# @return [Array<Hash{Symbol => String, Array<Integer>}>] icons paths of icons
|
108
|
+
def icons(exclude: nil)
|
131
109
|
@icons ||= apk.icon.each_with_object([]) do |(path, data), obj|
|
132
110
|
icon_name = ::File.basename(path)
|
133
111
|
icon_path = ::File.join(contents, ::File.dirname(path))
|
@@ -141,6 +119,8 @@ module AppInfo
|
|
141
119
|
dimensions: ImageSize.path(icon_file).size
|
142
120
|
}
|
143
121
|
end
|
122
|
+
|
123
|
+
extract_icon(@icons, exclude: exclude)
|
144
124
|
end
|
145
125
|
|
146
126
|
def clear!
|
@@ -154,17 +134,5 @@ module AppInfo
|
|
154
134
|
@app_path = nil
|
155
135
|
@info = nil
|
156
136
|
end
|
157
|
-
|
158
|
-
def contents
|
159
|
-
@contents ||= ::File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
160
|
-
end
|
161
|
-
|
162
|
-
private
|
163
|
-
|
164
|
-
def v1sign
|
165
|
-
@v1sign ||= Android::Signature::V1.verify(self)
|
166
|
-
rescue Android::Signature::NotFoundError
|
167
|
-
nil
|
168
|
-
end
|
169
137
|
end
|
170
138
|
end
|