app-info 2.8.5 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +12 -7
  3. data/.github/workflows/ci.yml +3 -1
  4. data/.rubocop.yml +33 -11
  5. data/CHANGELOG.md +34 -1
  6. data/Gemfile +7 -1
  7. data/README.md +68 -13
  8. data/Rakefile +11 -0
  9. data/app_info.gemspec +12 -3
  10. data/lib/app_info/aab.rb +48 -108
  11. data/lib/app_info/android/signature.rb +114 -0
  12. data/lib/app_info/android/signatures/base.rb +53 -0
  13. data/lib/app_info/android/signatures/info.rb +158 -0
  14. data/lib/app_info/android/signatures/v1.rb +63 -0
  15. data/lib/app_info/android/signatures/v2.rb +121 -0
  16. data/lib/app_info/android/signatures/v3.rb +131 -0
  17. data/lib/app_info/android/signatures/v4.rb +18 -0
  18. data/lib/app_info/android.rb +162 -0
  19. data/lib/app_info/apk.rb +54 -111
  20. data/lib/app_info/apple.rb +192 -0
  21. data/lib/app_info/certificate.rb +175 -0
  22. data/lib/app_info/const.rb +75 -0
  23. data/lib/app_info/core_ext/object/try.rb +3 -1
  24. data/lib/app_info/core_ext/string/inflector.rb +2 -0
  25. data/lib/app_info/dsym/debug_info.rb +72 -0
  26. data/lib/app_info/dsym/macho.rb +55 -0
  27. data/lib/app_info/dsym.rb +31 -135
  28. data/lib/app_info/error.rb +2 -2
  29. data/lib/app_info/file.rb +49 -0
  30. data/lib/app_info/helper/archive.rb +37 -0
  31. data/lib/app_info/helper/file_size.rb +25 -0
  32. data/lib/app_info/helper/generate_class.rb +29 -0
  33. data/lib/app_info/helper/protobuf.rb +12 -0
  34. data/lib/app_info/helper/signatures.rb +229 -0
  35. data/lib/app_info/helper.rb +5 -126
  36. data/lib/app_info/info_plist.rb +66 -29
  37. data/lib/app_info/ipa/framework.rb +4 -4
  38. data/lib/app_info/ipa.rb +61 -135
  39. data/lib/app_info/macos.rb +54 -102
  40. data/lib/app_info/mobile_provision.rb +67 -49
  41. data/lib/app_info/pe.rb +260 -0
  42. data/lib/app_info/png_uncrush.rb +24 -4
  43. data/lib/app_info/proguard.rb +29 -16
  44. data/lib/app_info/protobuf/manifest.rb +6 -3
  45. data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
  46. data/lib/app_info/protobuf/models/README.md +7 -0
  47. data/lib/app_info/protobuf/models/Resources_pb.rb +2 -0
  48. data/lib/app_info/protobuf/resources.rb +5 -5
  49. data/lib/app_info/version.rb +1 -1
  50. data/lib/app_info.rb +90 -46
  51. metadata +48 -35
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo::Helper
4
+ module HumanFileSize
5
+ def file_to_human_size(file, human_size:)
6
+ number = ::File.size(file)
7
+ human_size ? number_to_human_size(number) : number
8
+ end
9
+
10
+ FILE_SIZE_UNITS = %w[B KB MB GB TB].freeze
11
+
12
+ def number_to_human_size(number)
13
+ if number.to_i < 1024
14
+ exponent = 0
15
+ else
16
+ max_exp = FILE_SIZE_UNITS.size - 1
17
+ exponent = (Math.log(number) / Math.log(1024)).to_i
18
+ exponent = max_exp if exponent > max_exp
19
+ number = Kernel.format('%<number>.2f', number: (number / (1024**exponent.to_f)))
20
+ end
21
+
22
+ "#{number} #{FILE_SIZE_UNITS[exponent]}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo::Helper
4
+ module GenerateClass
5
+ def create_class(klass_name, parent_class, namespace:)
6
+ klass = Class.new(parent_class) do
7
+ yield if block_given?
8
+ end
9
+
10
+ name = namespace.to_s.empty? ? klass_name : "#{namespace}::#{klass_name}"
11
+ if Object.const_get(namespace).const_defined?(klass_name)
12
+ Object.const_get(namespace).const_get(klass_name)
13
+ elsif Object.const_defined?(name)
14
+ Object.const_get(name)
15
+ else
16
+ Object.const_get(namespace).const_set(klass_name, klass)
17
+ end
18
+ end
19
+
20
+ def define_instance_method(key, value)
21
+ instance_variable_set("@#{key}", value)
22
+ self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
23
+ def #{key}
24
+ @#{key}
25
+ end
26
+ RUBY
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo::Helper
4
+ module Protobuf
5
+ def reference_segments(value)
6
+ new_value = value.is_a?(Aapt::Pb::Reference) ? value.name : value
7
+ return new_value.split('/', 2) if new_value.include?('/')
8
+
9
+ [nil, new_value]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppInfo::Helper
4
+ # Binary IO Block Helper
5
+ module IOBlock
6
+ def length_prefix_block(
7
+ io, size: AppInfo::Android::Signature::UINT32_SIZE,
8
+ raw: false, ignore_left_size_precheck: false
9
+ )
10
+ offset = io.size - io.pos
11
+ if offset < AppInfo::Android::Signature::UINT32_SIZE
12
+ raise SecurityError,
13
+ 'Remaining buffer too short to contain length of length-prefixed field.'
14
+ end
15
+
16
+ size = io.read(size).unpack1('I')
17
+ raise SecurityError, 'Negative length' if size.negative?
18
+
19
+ if !ignore_left_size_precheck && size > io.size
20
+ message = "Underflow while reading length-prefixed value. #{size} > #{io.size}"
21
+ raise SecurityError, message
22
+ end
23
+
24
+ raw_data = io.read(size)
25
+ raw ? raw_data : StringIO.new(raw_data)
26
+ end
27
+
28
+ # Only use for uint32 length-prefixed block
29
+ def loop_length_prefix_io(
30
+ io, name:, max_bytes: nil, logger: nil, raw: false,
31
+ ignore_left_size_precheck: false, &block
32
+ )
33
+ index = 0
34
+ until io.eof?
35
+ logger&.debug "#{name} count ##{index}"
36
+ buffer = length_prefix_block(
37
+ io,
38
+ raw: raw,
39
+ ignore_left_size_precheck: ignore_left_size_precheck
40
+ )
41
+
42
+ left_bytes_check(buffer, max_bytes, SecurityError) do |left_bytes|
43
+ "#{name} too short: #{left_bytes} < #{max_bytes}"
44
+ end
45
+
46
+ block.call(buffer)
47
+ index += 1
48
+ end
49
+ end
50
+
51
+ def left_bytes_check(io, max_bytes, exception, message = nil, &block)
52
+ return if max_bytes.nil?
53
+
54
+ left_bytes = io.size - io.pos
55
+ return left_bytes if left_bytes.zero?
56
+
57
+ message ||= if block_given?
58
+ block.call(left_bytes)
59
+ else
60
+ "IO too short: #{offset} < #{max_bytes}"
61
+ end
62
+
63
+ raise exception, message if left_bytes < max_bytes
64
+
65
+ left_bytes
66
+ end
67
+ end
68
+
69
+ # Signature Block helper
70
+ module Signatures
71
+ def singers_block(block_id)
72
+ info = AppInfo::Android::Signature::Info.new(@version, @parser, logger)
73
+ raise SecurityError, 'ZIP64 APK not supported' if info.zip64?
74
+
75
+ info.signers(block_id)
76
+ end
77
+
78
+ def signed_data_certs(io)
79
+ certificates = []
80
+ loop_length_prefix_io(io, name: 'Certificates', raw: true) do |cert_data|
81
+ certificates << AppInfo::Certificate.parse(cert_data)
82
+ end
83
+ certificates
84
+ end
85
+
86
+ def signed_data_digests(io)
87
+ content_digests = {}
88
+ loop_length_prefix_io(
89
+ io,
90
+ name: 'Digests',
91
+ max_bytes: AppInfo::Android::Signature::UINT64_SIZE
92
+ ) do |digest|
93
+ algorithm = digest.read(AppInfo::Android::Signature::UINT32_SIZE).unpack('C*')
94
+ digest_name = algorithm_match(algorithm)
95
+ next unless digest_name
96
+
97
+ content = length_prefix_block(digest)
98
+ content_digests[digest_name] = {
99
+ id: algorithm,
100
+ content: content
101
+ }
102
+ end
103
+
104
+ content_digests
105
+ end
106
+
107
+ # FIXME: this code not work, need fix.
108
+ def verify_additional_attrs(attrs, _certs)
109
+ loop_length_prefix_io(
110
+ attrs, name: 'Additional Attributes', ignore_left_size_precheck: true
111
+ ) do |attr|
112
+ id = attr.read(AppInfo::Android::Signature::UINT32_SIZE)
113
+ logger.debug "ID #{id} / #{id.size} / #{id.unpack('H*')} / #{id.unpack('C*')}"
114
+ if id.unpack('C*') == AppInfo::Helper::Algorithm::SIG_STRIPPING_PROTECTION_ATTR_ID
115
+ offset = attr.size - attr.pos
116
+ if offset < AppInfo::Android::Signature::UINT32_SIZE
117
+ raise SecurityError,
118
+ "V2 Signature Scheme Stripping Protection Attribute value too small. Expected #{UINT32_SIZE} bytes, but found #{offset}"
119
+ end
120
+
121
+ # value = attr.read(UINT32_SIZE).unpack1('I')
122
+ if @version == AppInfo::Android::Signature::Version::V3
123
+ raise SecurityError,
124
+ 'V2 signature indicates APK is signed using APK Signature Scheme v3, but none was found. Signature stripped?'
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def signature_algorithms(signatures)
131
+ algorithems = []
132
+ loop_length_prefix_io(
133
+ signatures,
134
+ name: 'Signature Algorithms',
135
+ max_bytes: AppInfo::Android::Signature::UINT64_SIZE,
136
+ logger: logger
137
+ ) do |signature|
138
+ algorithm = signature.read(AppInfo::Android::Signature::UINT32_SIZE).unpack('C*')
139
+ digest = algorithm_match(algorithm)
140
+ next unless digest
141
+
142
+ signature = length_prefix_block(signature, raw: true)
143
+ algorithems << {
144
+ id: algorithm,
145
+ digest: digest,
146
+ signature: signature
147
+ }
148
+ end
149
+
150
+ algorithems
151
+ end
152
+ end
153
+
154
+ # Signature Algorithm helper
155
+ module Algorithm
156
+ # Signature certificate identifiers
157
+ SIG_RSA_PSS_WITH_SHA256 = [0x01, 0x01, 0x00, 0x00].freeze # 0x0101
158
+ SIG_RSA_PSS_WITH_SHA512 = [0x02, 0x01, 0x00, 0x00].freeze # 0x0102
159
+ SIG_RSA_PKCS1_V1_5_WITH_SHA256 = [0x03, 0x01, 0x00, 0x00].freeze # 0x0103
160
+ SIG_RSA_PKCS1_V1_5_WITH_SHA512 = [0x04, 0x01, 0x00, 0x00].freeze # 0x0104
161
+ SIG_ECDSA_WITH_SHA256 = [0x01, 0x02, 0x00, 0x00].freeze # 0x0201
162
+ SIG_ECDSA_WITH_SHA512 = [0x02, 0x02, 0x00, 0x00].freeze # 0x0202
163
+ SIG_DSA_WITH_SHA256 = [0x01, 0x03, 0x00, 0x00].freeze # 0x0301
164
+ SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256 = [0x21, 0x04, 0x00, 0x00].freeze # 0x0421
165
+ SIG_VERITY_ECDSA_WITH_SHA256 = [0x23, 0x04, 0x00, 0x00].freeze # 0x0423
166
+ SIG_VERITY_DSA_WITH_SHA256 = [0x25, 0x04, 0x00, 0x00].freeze # 0x0425
167
+
168
+ SIG_STRIPPING_PROTECTION_ATTR_ID = [0x0d, 0xf0, 0xef, 0xbe].freeze # 0xbeeff00d
169
+
170
+ def best_algorithem(algorithems)
171
+ methods = algorithems.map { |algorithem| algorithem[:method] }
172
+ best_method = methods.max { |a, b| algorithem_priority(a) <=> algorithem_priority(b) }
173
+ best_method_index = methods.index(best_method)
174
+ algorithems[best_method_index]
175
+ end
176
+
177
+ def compare_algorithem(source, target)
178
+ case algorithem_priority(source) <=> algorithem_priority(target)
179
+ when -1
180
+ target
181
+ else
182
+ source
183
+ end
184
+ end
185
+
186
+ def algorithem_priority(algorithm)
187
+ case algorithm
188
+ when SIG_RSA_PSS_WITH_SHA256,
189
+ SIG_RSA_PKCS1_V1_5_WITH_SHA256,
190
+ SIG_ECDSA_WITH_SHA256,
191
+ SIG_DSA_WITH_SHA256
192
+ 1
193
+ when SIG_RSA_PSS_WITH_SHA512,
194
+ SIG_RSA_PKCS1_V1_5_WITH_SHA512,
195
+ SIG_ECDSA_WITH_SHA512
196
+ 2
197
+ when SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256,
198
+ SIG_VERITY_ECDSA_WITH_SHA256,
199
+ SIG_VERITY_DSA_WITH_SHA256
200
+ 3
201
+ end
202
+ end
203
+
204
+ def algorithm_method(algorithm)
205
+ case algorithm
206
+ when SIG_RSA_PSS_WITH_SHA256, SIG_RSA_PSS_WITH_SHA512,
207
+ SIG_RSA_PKCS1_V1_5_WITH_SHA256, SIG_RSA_PKCS1_V1_5_WITH_SHA512,
208
+ SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256
209
+ :rsa
210
+ when SIG_ECDSA_WITH_SHA256, SIG_ECDSA_WITH_SHA512, SIG_VERITY_ECDSA_WITH_SHA256
211
+ :ec
212
+ when SIG_DSA_WITH_SHA256, SIG_VERITY_DSA_WITH_SHA256
213
+ :dsa
214
+ end
215
+ end
216
+
217
+ def algorithm_match(algorithm)
218
+ case algorithm
219
+ when SIG_RSA_PSS_WITH_SHA256, SIG_RSA_PKCS1_V1_5_WITH_SHA256,
220
+ SIG_ECDSA_WITH_SHA256, SIG_DSA_WITH_SHA256,
221
+ SIG_VERITY_RSA_PKCS1_V1_5_WITH_SHA256, SIG_VERITY_ECDSA_WITH_SHA256,
222
+ SIG_VERITY_DSA_WITH_SHA256
223
+ 'SHA256'
224
+ when SIG_RSA_PSS_WITH_SHA512, SIG_RSA_PKCS1_V1_5_WITH_SHA512, SIG_ECDSA_WITH_SHA512
225
+ 'SHA512'
226
+ end
227
+ end
228
+ end
229
+ end
@@ -1,128 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module AppInfo
4
- # App Platform
5
- module Platform
6
- MACOS = 'macOS'
7
- IOS = 'iOS'
8
- ANDROID = 'Android'
9
- DSYM = 'dSYM'
10
- PROGUARD = 'Proguard'
11
- end
12
-
13
- # Device Type
14
- module Device
15
- MACOS = 'macOS'
16
- IPHONE = 'iPhone'
17
- IPAD = 'iPad'
18
- UNIVERSAL = 'Universal'
19
- end
20
-
21
- module AndroidDevice
22
- PHONE = 'Phone'
23
- TABLET = 'Tablet'
24
- WATCH = 'Watch'
25
- TV = 'Television'
26
- end
27
-
28
- # Icon Key
29
- ICON_KEYS = {
30
- Device::IPHONE => ['CFBundleIcons'],
31
- Device::IPAD => ['CFBundleIcons~ipad'],
32
- Device::UNIVERSAL => ['CFBundleIcons', 'CFBundleIcons~ipad'],
33
- Device::MACOS => %w[CFBundleIconFile CFBundleIconName]
34
- }.freeze
35
-
36
- module Helper
37
- module HumanFileSize
38
- def file_to_human_size(file, human_size:)
39
- number = File.size(file)
40
- human_size ? number_to_human_size(number) : number
41
- end
42
-
43
- FILE_SIZE_UNITS = %w[B KB MB GB TB].freeze
44
-
45
- def number_to_human_size(number)
46
- if number.to_i < 1024
47
- exponent = 0
48
- else
49
- max_exp = FILE_SIZE_UNITS.size - 1
50
- exponent = (Math.log(number) / Math.log(1024)).to_i
51
- exponent = max_exp if exponent > max_exp
52
- number = format('%<number>.2f', number: (number / (1024**exponent.to_f)))
53
- end
54
-
55
- "#{number} #{FILE_SIZE_UNITS[exponent]}"
56
- end
57
- end
58
-
59
- module Archive
60
- require 'zip'
61
- require 'fileutils'
62
- require 'securerandom'
63
-
64
- # Unarchive zip file
65
- #
66
- # source: https://github.com/soffes/lagunitas/blob/master/lib/lagunitas/ipa.rb
67
- def unarchive(file, path: nil)
68
- path = path ? "#{path}-" : ''
69
- root_path = "#{Dir.mktmpdir}/AppInfo-#{path}#{SecureRandom.hex}"
70
- Zip::File.open(file) do |zip_file|
71
- if block_given?
72
- yield root_path, zip_file
73
- else
74
- zip_file.each do |f|
75
- f_path = File.join(root_path, f.name)
76
- FileUtils.mkdir_p(File.dirname(f_path))
77
- zip_file.extract(f, f_path) unless File.exist?(f_path)
78
- end
79
- end
80
- end
81
-
82
- root_path
83
- end
84
-
85
- def tempdir(file, prefix:)
86
- dest_path ||= File.join(File.dirname(file), prefix)
87
- dest_file = File.join(dest_path, File.basename(file))
88
- FileUtils.mkdir_p(dest_path, mode: 0_700)
89
- dest_file
90
- end
91
- end
92
-
93
- module Defines
94
- def create_class(klass_name, parent_class, namespace:)
95
- klass = Class.new(parent_class) do
96
- yield if block_given?
97
- end
98
-
99
- name = namespace.to_s.empty? ? klass_name : "#{namespace}::#{klass_name}"
100
- if Object.const_get(namespace).const_defined?(klass_name)
101
- Object.const_get(namespace).const_get(klass_name)
102
- elsif Object.const_defined?(name)
103
- Object.const_get(name)
104
- else
105
- Object.const_get(namespace).const_set(klass_name, klass)
106
- end
107
- end
108
-
109
- def define_instance_method(key, value)
110
- instance_variable_set("@#{key}", value)
111
- self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
112
- def #{key}
113
- @#{key}
114
- end
115
- RUBY
116
- end
117
- end
118
-
119
- module ReferenceParser
120
- def reference_segments(value)
121
- new_value = value.is_a?(Aapt::Pb::Reference) ? value.name : value
122
- return new_value.split('/', 2) if new_value.include?('/')
123
-
124
- [nil, new_value]
125
- end
126
- end
127
- end
128
- end
3
+ require 'app_info/helper/archive'
4
+ require 'app_info/helper/file_size'
5
+ require 'app_info/helper/generate_class'
6
+ require 'app_info/helper/protobuf'
7
+ require 'app_info/helper/signatures'
@@ -6,97 +6,131 @@ require 'app_info/png_uncrush'
6
6
 
7
7
  module AppInfo
8
8
  # iOS Info.plist parser
9
- class InfoPlist
9
+ class InfoPlist < File
10
10
  extend Forwardable
11
11
 
12
- def initialize(file)
13
- @file = file
12
+ # Icon Key
13
+ ICON_KEYS = {
14
+ Device::IPHONE => ['CFBundleIcons'],
15
+ Device::IPAD => ['CFBundleIcons~ipad'],
16
+ Device::UNIVERSAL => ['CFBundleIcons', 'CFBundleIcons~ipad'],
17
+ Device::MACOS => %w[CFBundleIconFile CFBundleIconName]
18
+ }.freeze
19
+
20
+ # @return [Symbol] {Platform}
21
+ def platform
22
+ Platform::APPLE
23
+ end
24
+
25
+ # @return [Symbol] {OperaSystem}
26
+ def opera_system
27
+ case device
28
+ when Device::MACOS
29
+ OperaSystem::MACOS
30
+ when Device::IPHONE, Device::IPAD, Device::UNIVERSAL
31
+ OperaSystem::IOS
32
+ end
33
+ end
34
+
35
+ # @return [Symbol] {Device}
36
+ def device
37
+ if device_family == [1]
38
+ Device::IPHONE
39
+ elsif device_family == [2]
40
+ Device::IPAD
41
+ elsif device_family == [1, 2]
42
+ Device::UNIVERSAL
43
+ elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
44
+ Device::MACOS
45
+ else
46
+ raise NotImplementedError, "Unkonwn device: #{device_family}"
47
+ end
14
48
  end
15
49
 
50
+ # @return [String, nil]
16
51
  def version
17
52
  release_version || build_version
18
53
  end
19
54
 
55
+ # @return [String, nil]
20
56
  def build_version
21
57
  info.try(:[], 'CFBundleVersion')
22
58
  end
23
59
 
60
+ # @return [String, nil]
24
61
  def release_version
25
62
  info.try(:[], 'CFBundleShortVersionString')
26
63
  end
27
64
 
65
+ # @return [String, nil]
28
66
  def identifier
29
67
  info.try(:[], 'CFBundleIdentifier')
30
68
  end
31
69
  alias bundle_id identifier
32
70
 
71
+ # @return [String, nil]
33
72
  def name
34
73
  display_name || bundle_name
35
74
  end
36
75
 
76
+ # @return [String, nil]
37
77
  def display_name
38
78
  info.try(:[], 'CFBundleDisplayName')
39
79
  end
40
80
 
81
+ # @return [String, nil]
41
82
  def bundle_name
42
83
  info.try(:[], 'CFBundleName')
43
84
  end
44
85
 
86
+ # @return [String, nil]
45
87
  def min_os_version
46
88
  min_sdk_version || min_system_version
47
89
  end
48
90
 
49
- #
50
91
  # Extract the Minimum OS Version from the Info.plist (iOS Only)
51
- #
92
+ # @return [String, nil]
52
93
  def min_sdk_version
53
94
  info.try(:[], 'MinimumOSVersion')
54
95
  end
55
96
 
56
- #
57
97
  # Extract the Minimum OS Version from the Info.plist (macOS Only)
58
- #
98
+ # @return [String, nil]
59
99
  def min_system_version
60
100
  info.try(:[], 'LSMinimumSystemVersion')
61
101
  end
62
102
 
103
+ # @return [Array<String>]
63
104
  def icons
64
- @icons ||= ICON_KEYS[device_type]
65
- end
66
-
67
- def device_type
68
- device_family = info.try(:[], 'UIDeviceFamily')
69
- if device_family == [1]
70
- Device::IPHONE
71
- elsif device_family == [2]
72
- Device::IPAD
73
- elsif device_family == [1, 2]
74
- Device::UNIVERSAL
75
- elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
76
- Device::MACOS
77
- end
105
+ @icons ||= ICON_KEYS[device]
78
106
  end
79
107
 
108
+ # @return [Boolean]
80
109
  def iphone?
81
- device_type == Device::IPHONE
110
+ device == Device::IPHONE
82
111
  end
83
112
 
113
+ # @return [Boolean]
84
114
  def ipad?
85
- device_type == Device::IPAD
115
+ device == Device::IPAD
86
116
  end
87
117
 
118
+ # @return [Boolean]
88
119
  def universal?
89
- device_type == Device::UNIVERSAL
120
+ device == Device::UNIVERSAL
90
121
  end
91
122
 
123
+ # @return [Boolean]
92
124
  def macos?
93
- device_type == Device::MACOS
125
+ device == Device::MACOS
94
126
  end
95
127
 
128
+ # @return [Array<String>]
96
129
  def device_family
97
130
  info.try(:[], 'UIDeviceFamily') || []
98
131
  end
99
132
 
133
+ # @return [String]
100
134
  def release_type
101
135
  if stored?
102
136
  'Store'
@@ -105,10 +139,13 @@ module AppInfo
105
139
  end
106
140
  end
107
141
 
142
+ # @return [String, nil]
108
143
  def [](key)
109
144
  info.try(:[], key.to_s)
110
145
  end
111
146
 
147
+ # @!method to_h
148
+ # @see CFPropertyList#to_h
112
149
  def_delegators :info, :to_h
113
150
 
114
151
  def method_missing(method_name, *args, &block)
@@ -126,17 +163,17 @@ module AppInfo
126
163
  private
127
164
 
128
165
  def info
129
- return unless File.file?(@file)
166
+ return unless ::File.file?(@file)
130
167
 
131
168
  @info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: @file).value)
132
169
  end
133
170
 
134
171
  def app_path
135
- @app_path ||= case device_type
172
+ @app_path ||= case device
136
173
  when Device::MACOS
137
- File.dirname(@file)
174
+ ::File.dirname(@file)
138
175
  else
139
- File.expand_path('../', @file)
176
+ ::File.expand_path('../', @file)
140
177
  end
141
178
  end
142
179
  end
@@ -8,7 +8,7 @@ module AppInfo
8
8
  extend Forwardable
9
9
 
10
10
  def self.parse(path, name = 'Frameworks')
11
- files = Dir.glob(File.join(path, name.to_s, '*'))
11
+ files = Dir.glob(::File.join(path, name.to_s, '*'))
12
12
  return [] if files.empty?
13
13
 
14
14
  files.sort.each_with_object([]) do |file, obj|
@@ -26,7 +26,7 @@ module AppInfo
26
26
  end
27
27
 
28
28
  def name
29
- File.basename(file)
29
+ ::File.basename(file)
30
30
  end
31
31
 
32
32
  def macho
@@ -37,11 +37,11 @@ module AppInfo
37
37
  end
38
38
 
39
39
  def lib?
40
- File.file?(file)
40
+ ::File.file?(file)
41
41
  end
42
42
 
43
43
  def info
44
- @info ||= InfoPlist.new(File.join(file, 'Info.plist'))
44
+ @info ||= InfoPlist.new(::File.join(file, 'Info.plist'))
45
45
  end
46
46
 
47
47
  def to_s