app-info 3.0.0.beta1 → 3.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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