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/apk.rb
CHANGED
@@ -5,8 +5,8 @@ require 'image_size'
|
|
5
5
|
require 'forwardable'
|
6
6
|
|
7
7
|
module AppInfo
|
8
|
-
# Parse APK file
|
9
|
-
class APK
|
8
|
+
# Parse APK file parser, wrapper for {https://github.com/icyleaf/android_parser android_parser}.
|
9
|
+
class APK < File
|
10
10
|
include Helper::HumanFileSize
|
11
11
|
extend Forwardable
|
12
12
|
|
@@ -21,18 +21,26 @@ module AppInfo
|
|
21
21
|
AUTOMOTIVE = 'Automotive'
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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]
|
28
33
|
def size(human_size: false)
|
29
34
|
file_to_human_size(@file, human_size: human_size)
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
37
|
+
def file_type
|
38
|
+
Format::APK
|
39
|
+
end
|
40
|
+
|
41
|
+
def platform
|
33
42
|
Platform::ANDROID
|
34
43
|
end
|
35
|
-
alias file_type os
|
36
44
|
|
37
45
|
def_delegators :apk, :manifest, :resource, :dex
|
38
46
|
|
@@ -86,28 +94,25 @@ module AppInfo
|
|
86
94
|
end
|
87
95
|
alias min_os_version min_sdk_version
|
88
96
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# 'v2'
|
95
|
-
# when ?
|
96
|
-
# https://source.android.com/security/apksigning/v3?hl=zh-cn
|
97
|
-
# 'v3'
|
98
|
-
'unknown'
|
97
|
+
# Return multi version certifiates of signatures
|
98
|
+
# @return [Array<Hash>]
|
99
|
+
# @see AppInfo::Android::Signature.verify
|
100
|
+
def signatures
|
101
|
+
@signatures ||= Android::Signature.verify(self)
|
99
102
|
end
|
100
103
|
|
104
|
+
# Legacy v1 scheme signatures, it will remove soon.
|
105
|
+
# @deprecated Use {#signatures}
|
106
|
+
# @return [Array<OpenSSL::PKCS7, nil>]
|
101
107
|
def signs
|
102
|
-
|
103
|
-
obj << Sign.new(path, sign)
|
104
|
-
end
|
108
|
+
@signs ||= v1sign&.signatures || []
|
105
109
|
end
|
106
110
|
|
111
|
+
# Legacy v1 scheme certificates, it will remove soon.
|
112
|
+
# @deprecated Use {#signatures}
|
113
|
+
# @return [Array<OpenSSL::PKCS7, nil>]
|
107
114
|
def certificates
|
108
|
-
|
109
|
-
obj << Certificate.new(path, certificate)
|
110
|
-
end
|
115
|
+
@certificates ||= v1sign&.certificates || []
|
111
116
|
end
|
112
117
|
|
113
118
|
def activities
|
@@ -118,13 +123,17 @@ module AppInfo
|
|
118
123
|
@apk ||= ::Android::Apk.new(@file)
|
119
124
|
end
|
120
125
|
|
126
|
+
def zip
|
127
|
+
@zip ||= apk.instance_variable_get(:@zip)
|
128
|
+
end
|
129
|
+
|
121
130
|
def icons
|
122
131
|
@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)
|
132
|
+
icon_name = ::File.basename(path)
|
133
|
+
icon_path = ::File.join(contents, ::File.dirname(path))
|
134
|
+
icon_file = ::File.join(icon_path, icon_name)
|
126
135
|
FileUtils.mkdir_p icon_path
|
127
|
-
File.write(icon_file, data, encoding: Encoding::BINARY)
|
136
|
+
::File.write(icon_file, data, encoding: Encoding::BINARY)
|
128
137
|
|
129
138
|
obj << {
|
130
139
|
name: icon_name,
|
@@ -147,27 +156,15 @@ module AppInfo
|
|
147
156
|
end
|
148
157
|
|
149
158
|
def contents
|
150
|
-
@contents ||= File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
159
|
+
@contents ||= ::File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
151
160
|
end
|
152
161
|
|
153
|
-
|
154
|
-
class Certificate
|
155
|
-
attr_reader :path, :certificate
|
162
|
+
private
|
156
163
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
164
|
+
def v1sign
|
165
|
+
@v1sign ||= Android::Signature::V1.verify(self)
|
166
|
+
rescue Android::Signature::NotFoundError
|
167
|
+
nil
|
171
168
|
end
|
172
169
|
end
|
173
170
|
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
# Certificate wrapper for
|
5
|
+
# {https://docs.ruby-lang.org/en/3.0/OpenSSL/X509/Certificate.html OpenSSL::X509::Certifiate}.
|
6
|
+
class Certificate
|
7
|
+
# Parse Raw data into X509 cerificate wrapper
|
8
|
+
# @param [String] certificate raw data
|
9
|
+
def self.parse(data)
|
10
|
+
cert = OpenSSL::X509::Certificate.new(data)
|
11
|
+
new(cert)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [OpenSSL::X509::Certificate] certificate
|
15
|
+
def initialize(cert)
|
16
|
+
@cert = cert
|
17
|
+
end
|
18
|
+
|
19
|
+
# return version of certificate
|
20
|
+
# @param [String] prefix
|
21
|
+
# @param [Integer] base
|
22
|
+
# @return [String] version
|
23
|
+
def version(prefix: 'v', base: 1)
|
24
|
+
"#{prefix}#{raw.version + base}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# return serial of certificate
|
28
|
+
# @param [Integer] base
|
29
|
+
# @param [Symbol] transform avaiables in :lower, :upper
|
30
|
+
# @param [String] prefix
|
31
|
+
# @return [String] serial
|
32
|
+
def serial(base = 10, transform: :lower, prefix: nil)
|
33
|
+
serial = raw.serial.to_s(base)
|
34
|
+
serial = transform == :lower ? serial.downcase : serial.upcase
|
35
|
+
return serial unless prefix
|
36
|
+
|
37
|
+
"#{prefix}#{serial}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# return issuer from DN, similar to {#subject}.
|
41
|
+
#
|
42
|
+
# Example:
|
43
|
+
#
|
44
|
+
# @param [Symbol] format avaiables in `:to_a`, `:to_s` and `:raw`
|
45
|
+
# @return [Array, String, OpenSSL::X509::Name] the object converted into the expected format.
|
46
|
+
def issuer(format: :raw)
|
47
|
+
convert_cert_name(raw.issuer, format: format)
|
48
|
+
end
|
49
|
+
|
50
|
+
# return subject from DN, similar to {#issuer}.
|
51
|
+
# @param [Symbol] format avaiables in `:to_a`, `:to_s` and `:raw`
|
52
|
+
# @return [Array, String, OpenSSL::X509::Name] the object converted into the expected format.
|
53
|
+
def subject(format: :raw)
|
54
|
+
convert_cert_name(raw.subject, format: format)
|
55
|
+
end
|
56
|
+
|
57
|
+
def created_at
|
58
|
+
raw.not_before
|
59
|
+
end
|
60
|
+
|
61
|
+
def expired_at
|
62
|
+
raw.not_after
|
63
|
+
end
|
64
|
+
|
65
|
+
def expired?
|
66
|
+
expired_at < Time.now.utc
|
67
|
+
end
|
68
|
+
|
69
|
+
def format
|
70
|
+
:x509
|
71
|
+
end
|
72
|
+
|
73
|
+
# return algorithm digest
|
74
|
+
#
|
75
|
+
# OpenSSL supported digests:
|
76
|
+
#
|
77
|
+
# -blake2b512 -blake2s256 -md4
|
78
|
+
# -md5 -md5-sha1 -mdc2
|
79
|
+
# -ripemd -ripemd160 -rmd160
|
80
|
+
# -sha1 -sha224 -sha256
|
81
|
+
# -sha3-224 -sha3-256 -sha3-384
|
82
|
+
# -sha3-512 -sha384 -sha512
|
83
|
+
# -sha512-224 -sha512-256 -shake128
|
84
|
+
# -shake256 -sm3 -ssl3-md5
|
85
|
+
# -ssl3-sha1 -whirlpool
|
86
|
+
def digest
|
87
|
+
signature_algorithm = raw.signature_algorithm
|
88
|
+
|
89
|
+
case signature_algorithm
|
90
|
+
when /md5/
|
91
|
+
:md5
|
92
|
+
when /sha1/
|
93
|
+
:sha1
|
94
|
+
when /sha224/
|
95
|
+
:sha224
|
96
|
+
when /sha256/
|
97
|
+
:sha256
|
98
|
+
when /sha512/
|
99
|
+
:sha512
|
100
|
+
else
|
101
|
+
# Android signature no need the others
|
102
|
+
signature_algorithm.to_sym
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# return algorithm name of public key
|
107
|
+
def algorithm
|
108
|
+
case public_key
|
109
|
+
when OpenSSL::PKey::RSA then :rsa
|
110
|
+
when OpenSSL::PKey::DSA then :dsa
|
111
|
+
when OpenSSL::PKey::DH then :dh
|
112
|
+
when OpenSSL::PKey::EC then :ec
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# return size of public key
|
117
|
+
def size
|
118
|
+
case public_key
|
119
|
+
when OpenSSL::PKey::RSA
|
120
|
+
public_key.n.num_bits
|
121
|
+
when OpenSSL::PKey::DSA, OpenSSL::PKey::DH
|
122
|
+
public_key.p.num_bits
|
123
|
+
when OpenSSL::PKey::EC
|
124
|
+
raise NotImplementedError, "key size for #{public_key.inspect} not implemented"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# return fingerprint of certificate
|
129
|
+
def fingerprint(name = :sha256, transform: :lower, delimiter: nil)
|
130
|
+
digest = OpenSSL::Digest.new(name.to_s.upcase)
|
131
|
+
# digest = case name.to_sym
|
132
|
+
# when :sha1
|
133
|
+
# OpenSSL::Digest::SHA1.new
|
134
|
+
# when :sha224
|
135
|
+
# OpenSSL::Digest::SHA224.new
|
136
|
+
# when :sha384
|
137
|
+
# OpenSSL::Digest::SHA384.new
|
138
|
+
# when :sha512
|
139
|
+
# OpenSSL::Digest::SHA512.new
|
140
|
+
# when :md5
|
141
|
+
# OpenSSL::Digest::MD5.new
|
142
|
+
# else
|
143
|
+
# OpenSSL::Digest::SHA256.new
|
144
|
+
# end
|
145
|
+
|
146
|
+
digest.update(raw.to_der)
|
147
|
+
fingerprint = digest.to_s
|
148
|
+
fingerprint = fingerprint.upcase if transform.to_sym == :upper
|
149
|
+
return fingerprint unless delimiter
|
150
|
+
|
151
|
+
fingerprint.scan(/../).join(delimiter)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Orginal OpenSSL X509 certificate
|
155
|
+
def raw
|
156
|
+
@cert
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def convert_cert_name(name, format:)
|
162
|
+
data = name.to_a
|
163
|
+
case format
|
164
|
+
when :to_a
|
165
|
+
data.map { |k, v, _| [k, v] }
|
166
|
+
when :to_s
|
167
|
+
data.map { |k, v, _| "#{k}=#{v}" }.join(' ')
|
168
|
+
else
|
169
|
+
name
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def method_missing(method, *args, &block)
|
174
|
+
@cert.send(method.to_sym, *args, &block) || super
|
175
|
+
end
|
176
|
+
|
177
|
+
def respond_to_missing?(method, *args)
|
178
|
+
@cert.include?(method.to_sym) || super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
# Full Format
|
5
|
+
module Format
|
6
|
+
# iOS
|
7
|
+
IPA = :ipa
|
8
|
+
INFOPLIST = :infoplist
|
9
|
+
MOBILEPROVISION = :mobileprovision
|
10
|
+
DSYM = :dsym
|
11
|
+
|
12
|
+
# Android
|
13
|
+
APK = :apk
|
14
|
+
AAB = :aab
|
15
|
+
PROGUARD = :proguard
|
16
|
+
|
17
|
+
# macOS
|
18
|
+
MACOS = :macos
|
19
|
+
|
20
|
+
# Windows
|
21
|
+
PE = :pe
|
22
|
+
|
23
|
+
UNKNOWN = :unknown
|
24
|
+
end
|
25
|
+
|
26
|
+
# Platform
|
27
|
+
module Platform
|
28
|
+
WINDOWS = 'Windows'
|
29
|
+
MACOS = 'macOS'
|
30
|
+
IOS = 'iOS'
|
31
|
+
ANDROID = 'Android'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Apple Device Type
|
35
|
+
module Device
|
36
|
+
MACOS = 'macOS'
|
37
|
+
IPHONE = 'iPhone'
|
38
|
+
IPAD = 'iPad'
|
39
|
+
UNIVERSAL = 'Universal'
|
40
|
+
end
|
41
|
+
end
|
@@ -4,7 +4,8 @@
|
|
4
4
|
# Copy from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb
|
5
5
|
|
6
6
|
module AppInfo
|
7
|
-
|
7
|
+
# @!visibility private
|
8
|
+
module Tryable
|
8
9
|
##
|
9
10
|
# :method: try
|
10
11
|
#
|
@@ -107,6 +108,7 @@ module AppInfo
|
|
107
108
|
end
|
108
109
|
end
|
109
110
|
|
111
|
+
# @!visibility private
|
110
112
|
class Object
|
111
113
|
include AppInfo::Tryable
|
112
114
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AppInfo
|
4
|
+
# @!visibility private
|
4
5
|
module Inflector
|
5
6
|
def ai_snakecase
|
6
7
|
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
@@ -30,6 +31,7 @@ module AppInfo
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
34
|
+
# @!visibility private
|
33
35
|
class String
|
34
36
|
include AppInfo::Inflector
|
35
37
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'app_info/dsym/macho'
|
4
|
+
|
5
|
+
module AppInfo
|
6
|
+
class DSYM < File
|
7
|
+
# DSYM Debug Information Format Struct
|
8
|
+
class DebugInfo
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def object
|
16
|
+
@object ||= ::File.basename(bin_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def macho_type
|
20
|
+
@macho_type ||= ::MachO.open(bin_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def machos
|
24
|
+
@machos ||= case macho_type
|
25
|
+
when ::MachO::MachOFile
|
26
|
+
[MachO.new(macho_type, ::File.size(bin_path))]
|
27
|
+
else
|
28
|
+
size = macho_type.fat_archs.each_with_object([]) do |arch, obj|
|
29
|
+
obj << arch.size
|
30
|
+
end
|
31
|
+
|
32
|
+
machos = []
|
33
|
+
macho_type.machos.each_with_index do |file, i|
|
34
|
+
machos << MachO.new(file, size[i])
|
35
|
+
end
|
36
|
+
machos
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def release_version
|
41
|
+
info.try(:[], 'CFBundleShortVersionString')
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_version
|
45
|
+
info.try(:[], 'CFBundleVersion')
|
46
|
+
end
|
47
|
+
|
48
|
+
def identifier
|
49
|
+
info.try(:[], 'CFBundleIdentifier').sub('com.apple.xcode.dsym.', '')
|
50
|
+
end
|
51
|
+
alias bundle_id identifier
|
52
|
+
|
53
|
+
def info
|
54
|
+
return nil unless ::File.exist?(info_path)
|
55
|
+
|
56
|
+
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: info_path).value)
|
57
|
+
end
|
58
|
+
|
59
|
+
def info_path
|
60
|
+
@info_path ||= ::File.join(path, 'Contents', 'Info.plist')
|
61
|
+
end
|
62
|
+
|
63
|
+
def bin_path
|
64
|
+
@bin_path ||= lambda {
|
65
|
+
dwarf_path = ::File.join(path, 'Contents', 'Resources', 'DWARF')
|
66
|
+
name = Dir.children(dwarf_path)[0]
|
67
|
+
::File.join(dwarf_path, name)
|
68
|
+
}.call
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'macho'
|
4
|
+
|
5
|
+
module AppInfo
|
6
|
+
class DSYM < File
|
7
|
+
# Mach-O Struct
|
8
|
+
class MachO
|
9
|
+
include Helper::HumanFileSize
|
10
|
+
|
11
|
+
def initialize(file, size = 0)
|
12
|
+
@file = file
|
13
|
+
@size = size
|
14
|
+
end
|
15
|
+
|
16
|
+
def cpu_name
|
17
|
+
@file.cpusubtype
|
18
|
+
end
|
19
|
+
|
20
|
+
def cpu_type
|
21
|
+
@file.cputype
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
@file.filetype
|
26
|
+
end
|
27
|
+
|
28
|
+
def size(human_size: false)
|
29
|
+
return number_to_human_size(@size) if human_size
|
30
|
+
|
31
|
+
@size
|
32
|
+
end
|
33
|
+
|
34
|
+
def uuid
|
35
|
+
@file[:LC_UUID][0].uuid_string
|
36
|
+
end
|
37
|
+
alias debug_id uuid
|
38
|
+
|
39
|
+
def header
|
40
|
+
@header ||= @file.header
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
{
|
45
|
+
uuid: uuid,
|
46
|
+
type: type,
|
47
|
+
cpu_name: cpu_name,
|
48
|
+
cpu_type: cpu_type,
|
49
|
+
size: size,
|
50
|
+
human_size: size(human_size: true)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|