app-info 2.8.5 → 3.0.0.beta2

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 (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,162 @@
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] {Platform}
27
+ def platform
28
+ Platform::GOOGLE
29
+ end
30
+
31
+ # @return [Symbol] {OperaSystem}
32
+ def opera_system
33
+ OperaSystem::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 v1sign
157
+ @v1sign ||= Android::Signature::V1.verify(self)
158
+ rescue Android::Signature::NotFoundError
159
+ nil
160
+ end
161
+ end
162
+ 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,50 @@ 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
 
67
+ # @return [Zip::File]
68
+ def zip
69
+ @zip ||= apk.instance_variable_get(:@zip)
70
+ end
71
+
72
+ # Full icons metadata
73
+ # @example
74
+ # apk.icons
75
+ # # => [
76
+ # # {
77
+ # # name: 'icon.png',
78
+ # # file: '/path/to/icon.png',
79
+ # # dimensions: [29, 29]
80
+ # # },
81
+ # # {
82
+ # # name: 'icon1.png',
83
+ # # file: '/path/to/icon1.png',
84
+ # # dimensions: [120, 120]
85
+ # # }
86
+ # # ]
87
+ # @return [Array<Hash{Symbol => String, Array<Integer>}>] icons paths of icons
121
88
  def icons
122
89
  @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)
90
+ icon_name = ::File.basename(path)
91
+ icon_path = ::File.join(contents, ::File.dirname(path))
92
+ icon_file = ::File.join(icon_path, icon_name)
126
93
  FileUtils.mkdir_p icon_path
127
- File.write(icon_file, data, encoding: Encoding::BINARY)
94
+ ::File.write(icon_file, data, encoding: Encoding::BINARY)
128
95
 
129
96
  obj << {
130
97
  name: icon_name,
@@ -145,29 +112,5 @@ module AppInfo
145
112
  @app_path = nil
146
113
  @info = nil
147
114
  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
115
  end
173
116
  end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'macho'
4
+ require 'fileutils'
5
+ require 'forwardable'
6
+ require 'cfpropertylist'
7
+
8
+ module AppInfo
9
+ # Apple base parser for ipa and macos file
10
+ class Apple < File
11
+ extend Forwardable
12
+ include Helper::HumanFileSize
13
+ include Helper::Archive
14
+
15
+ # Export types
16
+ module ExportType
17
+ # debug configuration
18
+ DEBUG = :debug
19
+ # adhoc configuration (iOS only)
20
+ ADHOC = :adhoc
21
+ # enterprise configuration (iOS only)
22
+ ENTERPRISE = :enterprise
23
+ # release configuration
24
+ RELEASE = :release
25
+ # release configuration but download from App Store
26
+ APPSTORE = :appstore
27
+ end
28
+
29
+ # @return [Symbol] {Platform}
30
+ def platform
31
+ Platform::APPLE
32
+ end
33
+
34
+ # return file size
35
+ # @example Read file size in integer
36
+ # aab.size # => 3618865
37
+ #
38
+ # @example Read file size in human readabale
39
+ # aab.size(human_size: true) # => '3.45 MB'
40
+ #
41
+ # @param [Boolean] human_size Convert integer value to human readable.
42
+ # @return [Integer, String]
43
+ def size(human_size: false)
44
+ file_to_human_size(@file, human_size: human_size)
45
+ end
46
+
47
+ # @!method device
48
+ # @see InfoPlist#device
49
+ # @!method opera_system
50
+ # @see InfoPlist#opera_system
51
+ # @!method iphone?
52
+ # @see InfoPlist#iphone?
53
+ # @!method ipad?
54
+ # @see InfoPlist#ipad?
55
+ # @!method universal?
56
+ # @see InfoPlist#universal?
57
+ # @!method build_version
58
+ # @see InfoPlist#build_version
59
+ # @!method name
60
+ # @see InfoPlist#name
61
+ # @!method release_version
62
+ # @see InfoPlist#release_version
63
+ # @!method identifier
64
+ # @see InfoPlist#identifier
65
+ # @!method bundle_id
66
+ # @see InfoPlist#bundle_id
67
+ # @!method display_name
68
+ # @see InfoPlist#display_name
69
+ # @!method bundle_name
70
+ # @see InfoPlist#bundle_name
71
+ # @!method min_sdk_version
72
+ # @see InfoPlist#min_sdk_version
73
+ # @!method min_os_version
74
+ # @see InfoPlist#min_os_version
75
+ def_delegators :info, :device, :opera_system, :iphone?, :ipad?, :universal?, :macos?,
76
+ :build_version, :name, :release_version, :identifier, :bundle_id,
77
+ :display_name, :bundle_name, :min_sdk_version, :min_os_version
78
+
79
+ # @!method devices
80
+ # @see MobileProvision#devices
81
+ # @!method team_name
82
+ # @see MobileProvision#team_name
83
+ # @!method team_identifier
84
+ # @see MobileProvision#team_identifier
85
+ # @!method profile_name
86
+ # @see MobileProvision#profile_name
87
+ # @!method expired_date
88
+ # @see MobileProvision#expired_date
89
+ def_delegators :mobileprovision, :devices, :team_name, :team_identifier,
90
+ :profile_name, :expired_date
91
+
92
+ # @return [String, nil]
93
+ def distribution_name
94
+ "#{profile_name} - #{team_name}" if profile_name && team_name
95
+ end
96
+
97
+ # @return [String]
98
+ def release_type
99
+ if stored?
100
+ ExportType::APPSTORE
101
+ else
102
+ build_type
103
+ end
104
+ end
105
+
106
+ # return iOS build configuration, not correct in macOS app.
107
+ # @return [String]
108
+ def build_type
109
+ if mobileprovision?
110
+ return ExportType::RELEASE if macos?
111
+
112
+ devices ? ExportType::ADHOC : ExportType::ENTERPRISE
113
+ else
114
+ ExportType::DEBUG
115
+ end
116
+ end
117
+
118
+ # @return [Array<MachO>, nil]
119
+ def archs
120
+ return unless ::File.exist?(binary_path)
121
+
122
+ file = MachO.open(binary_path)
123
+ case file
124
+ when MachO::MachOFile
125
+ [file.cpusubtype]
126
+ else
127
+ file.machos.each_with_object([]) do |arch, obj|
128
+ obj << arch.cpusubtype
129
+ end
130
+ end
131
+ end
132
+ alias architectures archs
133
+
134
+ # @abstract Subclass and override {#icons} to implement.
135
+ def icons(_uncrush: true)
136
+ not_implemented_error!(__method__)
137
+ end
138
+
139
+ # @abstract Subclass and override {#stored?} to implement.
140
+ def stored?
141
+ not_implemented_error!(__method__)
142
+ end
143
+
144
+ # force remove developer certificate data from {#mobileprovision} method
145
+ # @return [nil]
146
+ def hide_developer_certificates
147
+ mobileprovision.delete('DeveloperCertificates') if mobileprovision?
148
+ end
149
+
150
+ # @return [MobileProvision]
151
+ def mobileprovision
152
+ return unless mobileprovision?
153
+
154
+ @mobileprovision ||= MobileProvision.new(mobileprovision_path)
155
+ end
156
+
157
+ # @return [Boolean]
158
+ def mobileprovision?
159
+ ::File.exist?(mobileprovision_path)
160
+ end
161
+
162
+ # @abstract Subclass and override {#mobileprovision_path} to implement.
163
+ def mobileprovision_path
164
+ not_implemented_error!(__method__)
165
+ end
166
+
167
+ # @return [InfoPlist]
168
+ def info
169
+ @info ||= InfoPlist.new(info_path)
170
+ end
171
+
172
+ # @abstract Subclass and override {#info_path} to implement.
173
+ def info_path
174
+ not_implemented_error!(__method__)
175
+ end
176
+
177
+ # @abstract Subclass and override {#app_path} to implement.
178
+ def app_path
179
+ not_implemented_error!(__method__)
180
+ end
181
+
182
+ # @abstract Subclass and override {#clear!} to implement.
183
+ def clear!
184
+ not_implemented_error!(__method__)
185
+ end
186
+
187
+ # @return [String] contents path of contents
188
+ def contents
189
+ @contents ||= unarchive(@file, prefix: format.to_s)
190
+ end
191
+ end
192
+ end