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.
Files changed (49) 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 +31 -11
  5. data/CHANGELOG.md +30 -2
  6. data/Gemfile +7 -1
  7. data/README.md +74 -9
  8. data/Rakefile +11 -0
  9. data/app_info.gemspec +12 -3
  10. data/lib/app_info/aab.rb +58 -39
  11. data/lib/app_info/android/signature.rb +114 -0
  12. data/lib/app_info/android/signatures/base.rb +49 -0
  13. data/lib/app_info/android/signatures/info.rb +152 -0
  14. data/lib/app_info/android/signatures/v1.rb +59 -0
  15. data/lib/app_info/android/signatures/v2.rb +117 -0
  16. data/lib/app_info/android/signatures/v3.rb +127 -0
  17. data/lib/app_info/android/signatures/v4.rb +14 -0
  18. data/lib/app_info/apk.rb +43 -46
  19. data/lib/app_info/certificate.rb +181 -0
  20. data/lib/app_info/const.rb +41 -0
  21. data/lib/app_info/core_ext/object/try.rb +3 -1
  22. data/lib/app_info/core_ext/string/inflector.rb +2 -0
  23. data/lib/app_info/dsym/debug_info.rb +72 -0
  24. data/lib/app_info/dsym/macho.rb +55 -0
  25. data/lib/app_info/dsym.rb +27 -134
  26. data/lib/app_info/error.rb +8 -0
  27. data/lib/app_info/file.rb +23 -0
  28. data/lib/app_info/helper/archive.rb +37 -0
  29. data/lib/app_info/helper/file_size.rb +25 -0
  30. data/lib/app_info/helper/generate_class.rb +29 -0
  31. data/lib/app_info/helper/protobuf.rb +12 -0
  32. data/lib/app_info/helper/signatures.rb +229 -0
  33. data/lib/app_info/helper.rb +5 -126
  34. data/lib/app_info/info_plist.rb +14 -6
  35. data/lib/app_info/ipa/framework.rb +4 -4
  36. data/lib/app_info/ipa.rb +41 -36
  37. data/lib/app_info/macos.rb +34 -26
  38. data/lib/app_info/mobile_provision.rb +19 -30
  39. data/lib/app_info/pe.rb +226 -0
  40. data/lib/app_info/png_uncrush.rb +5 -4
  41. data/lib/app_info/proguard.rb +11 -17
  42. data/lib/app_info/protobuf/manifest.rb +16 -9
  43. data/lib/app_info/protobuf/models/Configuration_pb.rb +1 -0
  44. data/lib/app_info/protobuf/models/README.md +7 -0
  45. data/lib/app_info/protobuf/models/Resources_pb.rb +2 -0
  46. data/lib/app_info/protobuf/resources.rb +5 -5
  47. data/lib/app_info/version.rb +1 -1
  48. data/lib/app_info.rb +91 -42
  49. 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
- def initialize(file)
25
- @file = file
26
- end
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 os
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
- 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'
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
- apk.signs.each_with_object([]) do |(path, sign), obj|
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
- apk.certificates.each_with_object([]) do |(path, certificate), obj|
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
- # Android Certificate
154
- class Certificate
155
- attr_reader :path, :certificate
162
+ private
156
163
 
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
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
- module Tryable # :nodoc:
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