app-info 2.8.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -7
  3. data/.github/workflows/ci.yml +7 -5
  4. data/.github/workflows/create_release.yml +15 -0
  5. data/.rubocop.yml +33 -11
  6. data/CHANGELOG.md +107 -1
  7. data/Gemfile +10 -5
  8. data/README.md +82 -15
  9. data/Rakefile +11 -0
  10. data/app_info.gemspec +14 -5
  11. data/lib/app_info/aab.rb +76 -110
  12. data/lib/app_info/android/signature.rb +114 -0
  13. data/lib/app_info/android/signatures/base.rb +53 -0
  14. data/lib/app_info/android/signatures/info.rb +158 -0
  15. data/lib/app_info/android/signatures/v1.rb +63 -0
  16. data/lib/app_info/android/signatures/v2.rb +121 -0
  17. data/lib/app_info/android/signatures/v3.rb +131 -0
  18. data/lib/app_info/android/signatures/v4.rb +18 -0
  19. data/lib/app_info/android.rb +181 -0
  20. data/lib/app_info/apk.rb +77 -112
  21. data/lib/app_info/apple.rb +192 -0
  22. data/lib/app_info/certificate.rb +176 -0
  23. data/lib/app_info/const.rb +76 -0
  24. data/lib/app_info/core_ext/object/try.rb +3 -1
  25. data/lib/app_info/core_ext/string/inflector.rb +2 -0
  26. data/lib/app_info/dsym/debug_info.rb +81 -0
  27. data/lib/app_info/dsym/macho.rb +62 -0
  28. data/lib/app_info/dsym.rb +35 -135
  29. data/lib/app_info/error.rb +3 -1
  30. data/lib/app_info/file.rb +49 -0
  31. data/lib/app_info/helper/archive.rb +37 -0
  32. data/lib/app_info/helper/file_size.rb +25 -0
  33. data/lib/app_info/helper/generate_class.rb +29 -0
  34. data/lib/app_info/helper/protobuf.rb +12 -0
  35. data/lib/app_info/helper/signatures.rb +229 -0
  36. data/lib/app_info/helper.rb +5 -128
  37. data/lib/app_info/info_plist.rb +66 -29
  38. data/lib/app_info/ipa/framework.rb +4 -4
  39. data/lib/app_info/ipa.rb +61 -135
  40. data/lib/app_info/macos.rb +54 -102
  41. data/lib/app_info/mobile_provision.rb +66 -48
  42. data/lib/app_info/pe.rb +322 -0
  43. data/lib/app_info/png_uncrush.rb +25 -5
  44. data/lib/app_info/proguard.rb +39 -22
  45. data/lib/app_info/protobuf/manifest.rb +22 -11
  46. data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
  47. data/lib/app_info/protobuf/models/README.md +8 -1
  48. data/lib/app_info/protobuf/models/Resources.proto +51 -0
  49. data/lib/app_info/protobuf/models/Resources_pb.rb +42 -0
  50. data/lib/app_info/protobuf/resources.rb +5 -5
  51. data/lib/app_info/version.rb +1 -1
  52. data/lib/app_info.rb +93 -43
  53. metadata +57 -37
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo
4
+ class Android < File
5
+ module Signature
6
+ # Android v2 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
+ # * @+X bytes unit32: signatures with size in bytes
17
+ # * @+X+4 bytes payload signed data block
18
+ # * @+Y bytes unit32: public key with size in bytes
19
+ # * @+Y+4 bytes payload signed data block
20
+ class V2 < Base
21
+ include AppInfo::Helper::IOBlock
22
+ include AppInfo::Helper::Signatures
23
+ include AppInfo::Helper::Algorithm
24
+
25
+ # V2 Signature ID 0x7109871a
26
+ BLOCK_ID = [0x1a, 0x87, 0x09, 0x71].freeze
27
+
28
+ attr_reader :certificates, :digests
29
+
30
+ def version
31
+ Version::V2
32
+ end
33
+
34
+ # Verify
35
+ # @todo verified signatures
36
+ def verify
37
+ signers_block = singers_block(BLOCK_ID)
38
+ @certificates, @digests = verified_certs(signers_block, verify: true)
39
+ # @verified = true
40
+ end
41
+
42
+ private
43
+
44
+ def verified_certs(signers_block, verify:)
45
+ unless (signers = length_prefix_block(signers_block))
46
+ raise SecurityError, 'Not found signers'
47
+ end
48
+
49
+ certificates = []
50
+ content_digests = {}
51
+ loop_length_prefix_io(signers, name: 'Singer', logger: logger) do |signer|
52
+ signer_certs, signer_digests = extract_signer_data(signer, verify: verify)
53
+ certificates.concat(signer_certs)
54
+ content_digests.merge!(signer_digests)
55
+ end
56
+ raise SecurityError, 'No signers found' if certificates.empty?
57
+
58
+ [certificates, content_digests]
59
+ end
60
+
61
+ def extract_signer_data(signer, verify:)
62
+ # raw data
63
+ signed_data = length_prefix_block(signer)
64
+ signatures = length_prefix_block(signer)
65
+ public_key = length_prefix_block(signer, raw: true)
66
+
67
+ # FIXME: extract code below and re-organize
68
+
69
+ algorithems = signature_algorithms(signatures)
70
+ raise SecurityError, 'No signatures found' if verify && algorithems.empty?
71
+
72
+ # find best algorithem to verify signed data with public key and signature
73
+ unless best_algorithem = best_algorithem(algorithems)
74
+ raise SecurityError, 'No supported signatures found'
75
+ end
76
+
77
+ algorithems_digest = best_algorithem[:digest]
78
+ signature = best_algorithem[:signature]
79
+
80
+ pkey = OpenSSL::PKey.read(public_key)
81
+ digest = OpenSSL::Digest.new(algorithems_digest)
82
+ verified = pkey.verify(digest, signature, signed_data.string)
83
+ raise SecurityError, "#{algorithems_digest} signature did not verify" unless verified
84
+
85
+ # verify algorithm ID full equal (and sort) between digests and signature
86
+ digests = length_prefix_block(signed_data)
87
+ content_digests = signed_data_digests(digests)
88
+ content_digest = content_digests[algorithems_digest]&.fetch(:content)
89
+
90
+ unless content_digest
91
+ raise SecurityError,
92
+ 'Signature algorithms don\'t match between digests and signatures records'
93
+ end
94
+
95
+ previous_digest = content_digests.fetch(algorithems_digest)
96
+ content_digests[algorithems_digest] = content_digest
97
+ if previous_digest && previous_digest[:content] != content_digest
98
+ raise SecurityError,
99
+ 'Signature algorithms don\'t match between digests and signatures records'
100
+ end
101
+
102
+ certificates = length_prefix_block(signed_data)
103
+ certs = signed_data_certs(certificates)
104
+ raise SecurityError, 'No certificates listed' if certs.empty?
105
+
106
+ main_cert = certs[0]
107
+ if main_cert.public_key.to_der != pkey.to_der
108
+ raise SecurityError, 'Public key mismatch between certificate and signature record'
109
+ end
110
+
111
+ additional_attrs = length_prefix_block(signed_data)
112
+ verify_additional_attrs(additional_attrs, certs)
113
+
114
+ [certs, content_digests]
115
+ end
116
+ end
117
+
118
+ register(Version::V2, V2)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
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
126
+ end
127
+
128
+ register(Version::V3, V3)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
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)
16
+ end
17
+ end
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,41 +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
- # Parse APK file
9
- class APK
10
- include Helper::HumanFileSize
11
- extend Forwardable
12
-
13
- attr_reader :file
14
-
15
- # APK Devices
16
- module Device
17
- PHONE = 'Phone'
18
- TABLET = 'Tablet'
19
- WATCH = 'Watch'
20
- TV = 'Television'
21
- AUTOMOTIVE = 'Automotive'
22
- end
23
-
24
- def initialize(file)
25
- @file = file
26
- end
27
-
28
- def size(human_size: false)
29
- file_to_human_size(@file, human_size: human_size)
30
- end
31
-
32
- def os
33
- Platform::ANDROID
34
- end
35
- alias file_type os
36
-
6
+ # Parse APK file parser, wrapper for {https://github.com/icyleaf/android_parser android_parser}.
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
37
14
  def_delegators :apk, :manifest, :resource, :dex
38
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
39
34
  def_delegators :manifest, :version_name, :package_name, :target_sdk_version,
40
35
  :components, :services, :use_permissions, :use_features,
41
36
  :deep_links, :schemes
@@ -53,78 +48,70 @@ module AppInfo
53
48
  manifest.label || resource.find('@string/app_name')
54
49
  end
55
50
 
56
- def device_type
57
- if wear?
58
- Device::WATCH
59
- elsif tv?
60
- Device::TV
61
- elsif automotive?
62
- Device::AUTOMOTIVE
63
- else
64
- Device::PHONE
65
- end
66
- end
67
-
68
- # TODO: find a way to detect, no way!
69
- # def tablet?
70
- # end
71
-
72
- def wear?
73
- use_features.include?('android.hardware.type.watch')
74
- end
75
-
76
- def tv?
77
- use_features.include?('android.software.leanback')
78
- end
79
-
80
- def automotive?
81
- use_features.include?('android.hardware.type.automotive')
82
- end
83
-
51
+ # @return [String]
84
52
  def min_sdk_version
85
53
  manifest.min_sdk_ver
86
54
  end
87
55
  alias min_os_version min_sdk_version
88
56
 
89
- def sign_version
90
- return 'v1' unless signs.empty?
91
-
92
- # when ?
93
- # https://source.android.com/security/apksigning/v2?hl=zh-cn
94
- # 'v2'
95
- # when ?
96
- # https://source.android.com/security/apksigning/v3?hl=zh-cn
97
- # 'v3'
98
- 'unknown'
99
- end
100
-
101
- def signs
102
- apk.signs.each_with_object([]) do |(path, sign), obj|
103
- obj << Sign.new(path, sign)
104
- end
105
- end
106
-
107
- def certificates
108
- apk.certificates.each_with_object([]) do |(path, certificate), obj|
109
- obj << Certificate.new(path, certificate)
110
- end
111
- end
112
-
57
+ # @return [String]
113
58
  def activities
114
59
  components.select { |c| c.type == 'activity' }
115
60
  end
116
61
 
62
+ # @return [::Android::Apk]
117
63
  def apk
118
64
  @apk ||= ::Android::Apk.new(@file)
119
65
  end
120
66
 
121
- def icons
67
+ # @return [Zip::File]
68
+ def zip
69
+ @zip ||= apk.instance_variable_get(:@zip)
70
+ end
71
+
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)
122
109
  @icons ||= apk.icon.each_with_object([]) do |(path, data), obj|
123
- icon_name = File.basename(path)
124
- icon_path = File.join(contents, File.dirname(path))
125
- icon_file = File.join(icon_path, icon_name)
110
+ icon_name = ::File.basename(path)
111
+ icon_path = ::File.join(contents, ::File.dirname(path))
112
+ icon_file = ::File.join(icon_path, icon_name)
126
113
  FileUtils.mkdir_p icon_path
127
- File.write(icon_file, data, encoding: Encoding::BINARY)
114
+ ::File.write(icon_file, data, encoding: Encoding::BINARY)
128
115
 
129
116
  obj << {
130
117
  name: icon_name,
@@ -132,6 +119,8 @@ module AppInfo
132
119
  dimensions: ImageSize.path(icon_file).size
133
120
  }
134
121
  end
122
+
123
+ extract_icon(@icons, exclude: exclude)
135
124
  end
136
125
 
137
126
  def clear!
@@ -145,29 +134,5 @@ module AppInfo
145
134
  @app_path = nil
146
135
  @info = nil
147
136
  end
148
-
149
- def contents
150
- @contents ||= File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
151
- end
152
-
153
- # Android Certificate
154
- class Certificate
155
- attr_reader :path, :certificate
156
-
157
- def initialize(path, certificate)
158
- @path = path
159
- @certificate = certificate
160
- end
161
- end
162
-
163
- # Android Sign
164
- class Sign
165
- attr_reader :path, :sign
166
-
167
- def initialize(path, sign)
168
- @path = path
169
- @sign = sign
170
- end
171
- end
172
137
  end
173
138
  end