app-info 2.8.5 → 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 +22 -0
- data/Gemfile +7 -1
- data/README.md +64 -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 +7 -1
- 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 +1 -2
- 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 +88 -45
- metadata +46 -35
@@ -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
|
data/lib/app_info/helper.rb
CHANGED
@@ -1,128 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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'
|
data/lib/app_info/info_plist.rb
CHANGED
@@ -6,11 +6,19 @@ 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
|
-
|
13
|
-
|
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
|
+
def file_type
|
21
|
+
Format::INFOPLIST
|
14
22
|
end
|
15
23
|
|
16
24
|
def version
|
@@ -126,7 +134,7 @@ module AppInfo
|
|
126
134
|
private
|
127
135
|
|
128
136
|
def info
|
129
|
-
return unless File.file?(@file)
|
137
|
+
return unless ::File.file?(@file)
|
130
138
|
|
131
139
|
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: @file).value)
|
132
140
|
end
|
@@ -134,9 +142,9 @@ module AppInfo
|
|
134
142
|
def app_path
|
135
143
|
@app_path ||= case device_type
|
136
144
|
when Device::MACOS
|
137
|
-
File.dirname(@file)
|
145
|
+
::File.dirname(@file)
|
138
146
|
else
|
139
|
-
File.expand_path('../', @file)
|
147
|
+
::File.expand_path('../', @file)
|
140
148
|
end
|
141
149
|
end
|
142
150
|
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
|
data/lib/app_info/ipa.rb
CHANGED
@@ -7,7 +7,7 @@ require 'cfpropertylist'
|
|
7
7
|
|
8
8
|
module AppInfo
|
9
9
|
# IPA parser
|
10
|
-
class IPA
|
10
|
+
class IPA < File
|
11
11
|
include Helper::HumanFileSize
|
12
12
|
include Helper::Archive
|
13
13
|
extend Forwardable
|
@@ -25,18 +25,26 @@ module AppInfo
|
|
25
25
|
INHOUSE = 'Enterprise' # Rename and Alias to enterprise
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
# return file size
|
29
|
+
# @example Read file size in integer
|
30
|
+
# aab.size # => 3618865
|
31
|
+
#
|
32
|
+
# @example Read file size in human readabale
|
33
|
+
# aab.size(human_size: true) # => '3.45 MB'
|
34
|
+
#
|
35
|
+
# @param [Boolean] human_size Convert integer value to human readable.
|
36
|
+
# @return [Integer, String]
|
32
37
|
def size(human_size: false)
|
33
38
|
file_to_human_size(@file, human_size: human_size)
|
34
39
|
end
|
35
40
|
|
36
|
-
def
|
41
|
+
def file_type
|
42
|
+
Format::IPA
|
43
|
+
end
|
44
|
+
|
45
|
+
def platform
|
37
46
|
Platform::IOS
|
38
47
|
end
|
39
|
-
alias file_type os
|
40
48
|
|
41
49
|
def_delegators :info, :iphone?, :ipad?, :universal?, :build_version, :name,
|
42
50
|
:release_version, :identifier, :bundle_id, :display_name,
|
@@ -70,7 +78,7 @@ module AppInfo
|
|
70
78
|
end
|
71
79
|
|
72
80
|
def archs
|
73
|
-
return unless File.exist?(bundle_path)
|
81
|
+
return unless ::File.exist?(bundle_path)
|
74
82
|
|
75
83
|
file = MachO.open(bundle_path)
|
76
84
|
case file
|
@@ -114,14 +122,14 @@ module AppInfo
|
|
114
122
|
end
|
115
123
|
|
116
124
|
def mobileprovision?
|
117
|
-
File.exist?(mobileprovision_path)
|
125
|
+
::File.exist?(mobileprovision_path)
|
118
126
|
end
|
119
127
|
|
120
128
|
def mobileprovision_path
|
121
129
|
filename = 'embedded.mobileprovision'
|
122
|
-
@mobileprovision_path ||= File.join(@file, filename)
|
123
|
-
unless File.exist?(@mobileprovision_path)
|
124
|
-
@mobileprovision_path = File.join(app_path, filename)
|
130
|
+
@mobileprovision_path ||= ::File.join(@file, filename)
|
131
|
+
unless ::File.exist?(@mobileprovision_path)
|
132
|
+
@mobileprovision_path = ::File.join(app_path, filename)
|
125
133
|
end
|
126
134
|
|
127
135
|
@mobileprovision_path
|
@@ -134,15 +142,15 @@ module AppInfo
|
|
134
142
|
end
|
135
143
|
|
136
144
|
def metadata?
|
137
|
-
File.exist?(metadata_path)
|
145
|
+
::File.exist?(metadata_path)
|
138
146
|
end
|
139
147
|
|
140
148
|
def metadata_path
|
141
|
-
@metadata_path ||= File.join(contents, 'iTunesMetadata.plist')
|
149
|
+
@metadata_path ||= ::File.join(contents, 'iTunesMetadata.plist')
|
142
150
|
end
|
143
151
|
|
144
152
|
def bundle_path
|
145
|
-
@bundle_path ||= File.join(app_path, info.bundle_name)
|
153
|
+
@bundle_path ||= ::File.join(app_path, info.bundle_name)
|
146
154
|
end
|
147
155
|
|
148
156
|
def info
|
@@ -150,35 +158,32 @@ module AppInfo
|
|
150
158
|
end
|
151
159
|
|
152
160
|
def info_path
|
153
|
-
@info_path ||= File.join(app_path, 'Info.plist')
|
161
|
+
@info_path ||= ::File.join(app_path, 'Info.plist')
|
154
162
|
end
|
155
163
|
|
156
164
|
def app_path
|
157
|
-
@app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
|
165
|
+
@app_path ||= Dir.glob(::File.join(contents, 'Payload', '*.app')).first
|
158
166
|
end
|
159
167
|
|
160
168
|
IPHONE_KEY = 'CFBundleIcons'
|
161
169
|
IPAD_KEY = 'CFBundleIcons~ipad'
|
162
170
|
|
163
171
|
def icons_path
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
@icons_path << file
|
172
|
+
@icons_path ||= lambda {
|
173
|
+
icon_keys.each_with_object([]) do |name, icons|
|
174
|
+
filenames = info.try(:[], name)
|
175
|
+
.try(:[], 'CFBundlePrimaryIcon')
|
176
|
+
.try(:[], 'CFBundleIconFiles')
|
177
|
+
|
178
|
+
next if filenames.nil? || filenames.empty?
|
179
|
+
|
180
|
+
filenames.each do |filename|
|
181
|
+
Dir.glob(::File.join(app_path, "#{filename}*")).find_all.each do |file|
|
182
|
+
icons << file
|
183
|
+
end
|
177
184
|
end
|
178
185
|
end
|
179
|
-
|
180
|
-
|
181
|
-
@icons_path
|
186
|
+
}.call
|
182
187
|
end
|
183
188
|
|
184
189
|
def clear!
|
@@ -197,7 +202,7 @@ module AppInfo
|
|
197
202
|
end
|
198
203
|
|
199
204
|
def contents
|
200
|
-
@contents ||= unarchive(@file,
|
205
|
+
@contents ||= unarchive(@file, prefix: 'ios')
|
201
206
|
end
|
202
207
|
|
203
208
|
private
|
@@ -206,7 +211,7 @@ module AppInfo
|
|
206
211
|
uncrushed_file = uncrush ? uncrush_png(file) : nil
|
207
212
|
|
208
213
|
{
|
209
|
-
name: File.basename(file),
|
214
|
+
name: ::File.basename(file),
|
210
215
|
file: file,
|
211
216
|
uncrushed_file: uncrushed_file,
|
212
217
|
dimensions: PngUncrush.dimensions(file)
|
@@ -217,7 +222,7 @@ module AppInfo
|
|
217
222
|
def uncrush_png(src_file)
|
218
223
|
dest_file = tempdir(src_file, prefix: 'uncrushed')
|
219
224
|
PngUncrush.decompress(src_file, dest_file)
|
220
|
-
File.exist?(dest_file) ? dest_file : nil
|
225
|
+
::File.exist?(dest_file) ? dest_file : nil
|
221
226
|
end
|
222
227
|
|
223
228
|
def icon_keys
|