app-info 3.0.0.beta1 → 3.0.0.beta3

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.
@@ -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] {Manufacturer}
30
+ def manufacturer
31
+ Manufacturer::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 platform
50
+ # @see InfoPlist#platform
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, :platform, :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
@@ -6,6 +6,7 @@ module AppInfo
6
6
  class Certificate
7
7
  # Parse Raw data into X509 cerificate wrapper
8
8
  # @param [String] certificate raw data
9
+ # @return [AppInfo::Certificate]
9
10
  def self.parse(data)
10
11
  cert = OpenSSL::X509::Certificate.new(data)
11
12
  new(cert)
@@ -54,18 +55,22 @@ module AppInfo
54
55
  convert_cert_name(raw.subject, format: format)
55
56
  end
56
57
 
58
+ # @return [Time]
57
59
  def created_at
58
60
  raw.not_before
59
61
  end
60
62
 
63
+ # @return [Time]
61
64
  def expired_at
62
65
  raw.not_after
63
66
  end
64
67
 
68
+ # @return [Boolean]
65
69
  def expired?
66
70
  expired_at < Time.now.utc
67
71
  end
68
72
 
73
+ # @return [String] format always be :x509.
69
74
  def format
70
75
  :x509
71
76
  end
@@ -113,36 +118,25 @@ module AppInfo
113
118
  end
114
119
  end
115
120
 
116
- # return size of public key
117
- def size
121
+ # return length of public key
122
+ # @return [Integer]
123
+ # @raise NotImplementedError
124
+ def length
118
125
  case public_key
119
126
  when OpenSSL::PKey::RSA
120
127
  public_key.n.num_bits
121
128
  when OpenSSL::PKey::DSA, OpenSSL::PKey::DH
122
129
  public_key.p.num_bits
123
130
  when OpenSSL::PKey::EC
124
- raise NotImplementedError, "key size for #{public_key.inspect} not implemented"
131
+ raise NotImplementedError, "key length for #{public_key.inspect} not implemented"
125
132
  end
126
133
  end
134
+ alias size length
127
135
 
128
136
  # return fingerprint of certificate
137
+ # @return [String]
129
138
  def fingerprint(name = :sha256, transform: :lower, delimiter: nil)
130
139
  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
140
  digest.update(raw.to_der)
147
141
  fingerprint = digest.to_s
148
142
  fingerprint = fingerprint.upcase if transform.to_sym == :upper
@@ -152,6 +146,7 @@ module AppInfo
152
146
  end
153
147
 
154
148
  # Orginal OpenSSL X509 certificate
149
+ # @return [OpenSSL::X509::Certificate]
155
150
  def raw
156
151
  @cert
157
152
  end
@@ -175,7 +170,7 @@ module AppInfo
175
170
  end
176
171
 
177
172
  def respond_to_missing?(method, *args)
178
- @cert.include?(method.to_sym) || super
173
+ @cert.respond_to?(method.to_sym) || super
179
174
  end
180
175
  end
181
176
  end
@@ -3,39 +3,74 @@
3
3
  module AppInfo
4
4
  # Full Format
5
5
  module Format
6
- # iOS
7
- IPA = :ipa
6
+ # Apple
7
+
8
8
  INFOPLIST = :infoplist
9
9
  MOBILEPROVISION = :mobileprovision
10
10
  DSYM = :dsym
11
11
 
12
+ # macOS
13
+
14
+ MACOS = :macos
15
+
16
+ # iOS
17
+
18
+ IPA = :ipa
19
+
12
20
  # Android
21
+
13
22
  APK = :apk
14
23
  AAB = :aab
15
24
  PROGUARD = :proguard
16
25
 
17
- # macOS
18
- MACOS = :macos
19
-
20
26
  # Windows
27
+
21
28
  PE = :pe
22
29
 
23
30
  UNKNOWN = :unknown
24
31
  end
25
32
 
33
+ # Manufacturer
34
+ module Manufacturer
35
+ APPLE = :apple
36
+ GOOGLE = :google
37
+ MICROSOFT = :microsoft
38
+ end
39
+
26
40
  # Platform
27
41
  module Platform
28
- WINDOWS = 'Windows'
29
- MACOS = 'macOS'
30
- IOS = 'iOS'
31
- ANDROID = 'Android'
42
+ MACOS = :macos
43
+ IOS = :ios
44
+ ANDROID = :android
45
+ WINDOWS = :windows
32
46
  end
33
47
 
34
48
  # Apple Device Type
35
49
  module Device
36
- MACOS = 'macOS'
37
- IPHONE = 'iPhone'
38
- IPAD = 'iPad'
39
- UNIVERSAL = 'Universal'
50
+ # macOS
51
+ MACOS = :macos
52
+
53
+ # Apple iPhone
54
+ IPHONE = :iphone
55
+ # Apple iPad
56
+ IPAD = :ipad
57
+ # Apple Watch
58
+ IWATCH = :iwatch # not implemented yet
59
+ # Apple Universal (iPhone and iPad)
60
+ UNIVERSAL = :universal
61
+
62
+ # Android Phone
63
+ PHONE = :phone
64
+ # Android Tablet (not implemented yet)
65
+ TABLET = :tablet
66
+ # Android Watch
67
+ WATCH = :watch
68
+ # Android TV
69
+ TELEVISION = :television
70
+ # Android Car Automotive
71
+ AUTOMOTIVE = :automotive
72
+
73
+ # Windows
74
+ WINDOWS = :windows
40
75
  end
41
76
  end
data/lib/app_info/dsym.rb CHANGED
@@ -7,16 +7,18 @@ module AppInfo
7
7
  class DSYM < File
8
8
  include Helper::Archive
9
9
 
10
- def file_type
11
- Format::DSYM
10
+ # @return [Symbol] {Manufacturer}
11
+ def manufacturer
12
+ Manufacturer::APPLE
12
13
  end
13
14
 
14
15
  def each_file(&block)
15
16
  files.each { |file| block.call(file) }
16
17
  end
17
18
 
19
+ # @return [Array<DebugInfo>] dsym_files files by alphabetical order
18
20
  def files
19
- @files ||= Dir.children(contents).each_with_object([]) do |file, obj|
21
+ @files ||= Dir.children(contents).sort.each_with_object([]) do |file, obj|
20
22
  obj << DebugInfo.new(::File.join(contents, file))
21
23
  end
22
24
  end
@@ -30,6 +32,7 @@ module AppInfo
30
32
  @files = nil
31
33
  end
32
34
 
35
+ # @return [String] contents path of dsym
33
36
  def contents
34
37
  @contents ||= lambda {
35
38
  return @file if ::File.directory?(@file)
@@ -9,13 +9,7 @@ module AppInfo
9
9
 
10
10
  class NotFoundError < Error; end
11
11
 
12
- class NotFoundWinBinraryError < NotFoundError; end
13
-
14
12
  class ProtobufParseError < Error; end
15
13
 
16
- class UnknownFileTypeError < Error; end
17
-
18
- # @deprecated Correct to the new {UnknownFileTypeError} class because typo.
19
- # It will remove since 2.7.0.
20
- class UnkownFileTypeError < Error; end
14
+ class UnknownFormatError < Error; end
21
15
  end
data/lib/app_info/file.rb CHANGED
@@ -10,14 +10,40 @@ module AppInfo
10
10
  @logger = logger
11
11
  end
12
12
 
13
- # @abstract Subclass and override {#file_type} to implement
14
- def file_type
15
- Platform::UNKNOWN
13
+ # @return [Symbol] {Format}
14
+ def format
15
+ @format ||= lambda {
16
+ if instance_of?(AppInfo::File) || instance_of?(AppInfo::Apple) ||
17
+ instance_of?(AppInfo::Android)
18
+ not_implemented_error!(__method__)
19
+ end
20
+
21
+ self.class.name.split('::')[-1].downcase.to_sym
22
+ }.call
23
+ end
24
+
25
+ # @abstract Subclass and override {#platform} to implement.
26
+ def platform
27
+ not_implemented_error!(__method__)
28
+ end
29
+
30
+ # @abstract Subclass and override {#manufacturer} to implement.
31
+ def manufacturer
32
+ not_implemented_error!(__method__)
33
+ end
34
+
35
+ # @abstract Subclass and override {#device} to implement.
36
+ def device
37
+ not_implemented_error!(__method__)
16
38
  end
17
39
 
18
40
  # @abstract Subclass and override {#size} to implement
19
41
  def size(human_size: false)
20
- raise NotImplementedError, ".#{__method__} method implantation required in #{self.class}"
42
+ not_implemented_error!(__method__)
43
+ end
44
+
45
+ def not_implemented_error!(method)
46
+ raise NotImplementedError, ".#{method} method implantation required in #{self.class}"
21
47
  end
22
48
  end
23
49
  end
@@ -16,7 +16,7 @@ module AppInfo::Helper
16
16
  max_exp = FILE_SIZE_UNITS.size - 1
17
17
  exponent = (Math.log(number) / Math.log(1024)).to_i
18
18
  exponent = max_exp if exponent > max_exp
19
- number = format('%<number>.2f', number: (number / (1024**exponent.to_f)))
19
+ number = Kernel.format('%<number>.2f', number: (number / (1024**exponent.to_f)))
20
20
  end
21
21
 
22
22
  "#{number} #{FILE_SIZE_UNITS[exponent]}"
@@ -17,94 +17,120 @@ module AppInfo
17
17
  Device::MACOS => %w[CFBundleIconFile CFBundleIconName]
18
18
  }.freeze
19
19
 
20
- def file_type
21
- Format::INFOPLIST
20
+ # @return [Symbol] {Manufacturer}
21
+ def manufacturer
22
+ Manufacturer::APPLE
23
+ end
24
+
25
+ # @return [Symbol] {Platform}
26
+ def platform
27
+ case device
28
+ when Device::MACOS
29
+ Platform::MACOS
30
+ when Device::IPHONE, Device::IPAD, Device::UNIVERSAL
31
+ Platform::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(:[], 'DTManufacturerName').nil?
44
+ Device::MACOS
45
+ else
46
+ raise NotImplementedError, "Unkonwn device: #{device_family}"
47
+ end
22
48
  end
23
49
 
50
+ # @return [String, nil]
24
51
  def version
25
52
  release_version || build_version
26
53
  end
27
54
 
55
+ # @return [String, nil]
28
56
  def build_version
29
57
  info.try(:[], 'CFBundleVersion')
30
58
  end
31
59
 
60
+ # @return [String, nil]
32
61
  def release_version
33
62
  info.try(:[], 'CFBundleShortVersionString')
34
63
  end
35
64
 
65
+ # @return [String, nil]
36
66
  def identifier
37
67
  info.try(:[], 'CFBundleIdentifier')
38
68
  end
39
69
  alias bundle_id identifier
40
70
 
71
+ # @return [String, nil]
41
72
  def name
42
73
  display_name || bundle_name
43
74
  end
44
75
 
76
+ # @return [String, nil]
45
77
  def display_name
46
78
  info.try(:[], 'CFBundleDisplayName')
47
79
  end
48
80
 
81
+ # @return [String, nil]
49
82
  def bundle_name
50
83
  info.try(:[], 'CFBundleName')
51
84
  end
52
85
 
86
+ # @return [String, nil]
53
87
  def min_os_version
54
88
  min_sdk_version || min_system_version
55
89
  end
56
90
 
57
- #
58
91
  # Extract the Minimum OS Version from the Info.plist (iOS Only)
59
- #
92
+ # @return [String, nil]
60
93
  def min_sdk_version
61
94
  info.try(:[], 'MinimumOSVersion')
62
95
  end
63
96
 
64
- #
65
97
  # Extract the Minimum OS Version from the Info.plist (macOS Only)
66
- #
98
+ # @return [String, nil]
67
99
  def min_system_version
68
100
  info.try(:[], 'LSMinimumSystemVersion')
69
101
  end
70
102
 
103
+ # @return [Array<String>]
71
104
  def icons
72
- @icons ||= ICON_KEYS[device_type]
73
- end
74
-
75
- def device_type
76
- device_family = info.try(:[], 'UIDeviceFamily')
77
- if device_family == [1]
78
- Device::IPHONE
79
- elsif device_family == [2]
80
- Device::IPAD
81
- elsif device_family == [1, 2]
82
- Device::UNIVERSAL
83
- elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
84
- Device::MACOS
85
- end
105
+ @icons ||= ICON_KEYS[device]
86
106
  end
87
107
 
108
+ # @return [Boolean]
88
109
  def iphone?
89
- device_type == Device::IPHONE
110
+ device == Device::IPHONE
90
111
  end
91
112
 
113
+ # @return [Boolean]
92
114
  def ipad?
93
- device_type == Device::IPAD
115
+ device == Device::IPAD
94
116
  end
95
117
 
118
+ # @return [Boolean]
96
119
  def universal?
97
- device_type == Device::UNIVERSAL
120
+ device == Device::UNIVERSAL
98
121
  end
99
122
 
123
+ # @return [Boolean]
100
124
  def macos?
101
- device_type == Device::MACOS
125
+ device == Device::MACOS
102
126
  end
103
127
 
128
+ # @return [Array<String>]
104
129
  def device_family
105
130
  info.try(:[], 'UIDeviceFamily') || []
106
131
  end
107
132
 
133
+ # @return [String]
108
134
  def release_type
109
135
  if stored?
110
136
  'Store'
@@ -113,10 +139,13 @@ module AppInfo
113
139
  end
114
140
  end
115
141
 
142
+ # @return [String, nil]
116
143
  def [](key)
117
144
  info.try(:[], key.to_s)
118
145
  end
119
146
 
147
+ # @!method to_h
148
+ # @see CFPropertyList#to_h
120
149
  def_delegators :info, :to_h
121
150
 
122
151
  def method_missing(method_name, *args, &block)
@@ -140,7 +169,7 @@ module AppInfo
140
169
  end
141
170
 
142
171
  def app_path
143
- @app_path ||= case device_type
172
+ @app_path ||= case device
144
173
  when Device::MACOS
145
174
  ::File.dirname(@file)
146
175
  else