app-info 2.8.5 → 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 +22 -0
- data/Gemfile +7 -1
- data/README.md +64 -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 +7 -1
- 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 +1 -2
- 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 +88 -45
- metadata +46 -35
@@ -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
|
data/lib/app_info/dsym.rb
CHANGED
@@ -1,78 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'app_info/dsym/debug_info'
|
4
4
|
|
5
5
|
module AppInfo
|
6
6
|
# DSYM parser
|
7
|
-
class DSYM
|
7
|
+
class DSYM < File
|
8
8
|
include Helper::Archive
|
9
9
|
|
10
|
-
attr_reader :file
|
11
|
-
|
12
|
-
def initialize(file)
|
13
|
-
@file = file
|
14
|
-
end
|
15
|
-
|
16
10
|
def file_type
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def object
|
21
|
-
@object ||= File.basename(app_path)
|
22
|
-
end
|
23
|
-
|
24
|
-
def macho_type
|
25
|
-
@macho_type ||= ::MachO.open(app_path)
|
26
|
-
end
|
27
|
-
|
28
|
-
def machos
|
29
|
-
@machos ||= case macho_type
|
30
|
-
when ::MachO::MachOFile
|
31
|
-
[MachO.new(macho_type, File.size(app_path))]
|
32
|
-
else
|
33
|
-
size = macho_type.fat_archs.each_with_object([]) do |arch, obj|
|
34
|
-
obj << arch.size
|
35
|
-
end
|
36
|
-
|
37
|
-
machos = []
|
38
|
-
macho_type.machos.each_with_index do |file, i|
|
39
|
-
machos << MachO.new(file, size[i])
|
40
|
-
end
|
41
|
-
machos
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def release_version
|
46
|
-
info.try(:[], 'CFBundleShortVersionString')
|
11
|
+
Format::DSYM
|
47
12
|
end
|
48
13
|
|
49
|
-
def
|
50
|
-
|
14
|
+
def each_file(&block)
|
15
|
+
files.each { |file| block.call(file) }
|
51
16
|
end
|
52
17
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
alias bundle_id identifier
|
57
|
-
|
58
|
-
def info
|
59
|
-
return nil unless File.exist?(info_path)
|
60
|
-
|
61
|
-
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: info_path).value)
|
62
|
-
end
|
63
|
-
|
64
|
-
def info_path
|
65
|
-
@info_path ||= File.join(contents, 'Contents', 'Info.plist')
|
66
|
-
end
|
67
|
-
|
68
|
-
def app_path
|
69
|
-
unless @app_path
|
70
|
-
path = File.join(contents, 'Contents', 'Resources', 'DWARF')
|
71
|
-
name = Dir.entries(path).reject { |f| ['.', '..'].include?(f) }.first
|
72
|
-
@app_path = File.join(path, name)
|
18
|
+
def files
|
19
|
+
@files ||= Dir.children(contents).each_with_object([]) do |file, obj|
|
20
|
+
obj << DebugInfo.new(::File.join(contents, file))
|
73
21
|
end
|
74
|
-
|
75
|
-
@app_path
|
76
22
|
end
|
77
23
|
|
78
24
|
def clear!
|
@@ -81,85 +27,32 @@ module AppInfo
|
|
81
27
|
FileUtils.rm_rf(@contents)
|
82
28
|
|
83
29
|
@contents = nil
|
84
|
-
@
|
85
|
-
@info = nil
|
86
|
-
@object = nil
|
87
|
-
@macho_type = nil
|
30
|
+
@files = nil
|
88
31
|
end
|
89
32
|
|
90
33
|
def contents
|
91
|
-
|
92
|
-
if File.directory?(@file)
|
93
|
-
@contents = @file
|
94
|
-
else
|
95
|
-
dsym_dir = nil
|
96
|
-
@contents = unarchive(@file, path: 'dsym') do |path, zip_file|
|
97
|
-
zip_file.each do |f|
|
98
|
-
unless dsym_dir
|
99
|
-
dsym_dir = f.name
|
100
|
-
# fix filename is xxx.app.dSYM/Contents
|
101
|
-
dsym_dir = dsym_dir.split('/')[0] if dsym_dir.include?('/')
|
102
|
-
end
|
34
|
+
@contents ||= lambda {
|
35
|
+
return @file if ::File.directory?(@file)
|
103
36
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
37
|
+
dsym_filenames = []
|
38
|
+
unarchive(@file, prefix: 'dsym') do |base_path, zip_file|
|
39
|
+
zip_file.each do |entry|
|
40
|
+
file_path = entry.name
|
41
|
+
next unless file_path.downcase.include?('.dsym/contents/')
|
42
|
+
next if ::File.basename(file_path).start_with?('.')
|
109
43
|
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
@contents
|
115
|
-
end
|
116
|
-
|
117
|
-
# DSYM Mach-O
|
118
|
-
class MachO
|
119
|
-
include Helper::HumanFileSize
|
120
|
-
|
121
|
-
def initialize(file, size = 0)
|
122
|
-
@file = file
|
123
|
-
@size = size
|
124
|
-
end
|
125
|
-
|
126
|
-
def cpu_name
|
127
|
-
@file.cpusubtype
|
128
|
-
end
|
129
|
-
|
130
|
-
def cpu_type
|
131
|
-
@file.cputype
|
132
|
-
end
|
133
|
-
|
134
|
-
def type
|
135
|
-
@file.filetype
|
136
|
-
end
|
137
|
-
|
138
|
-
def size(human_size: false)
|
139
|
-
return number_to_human_size(@size) if human_size
|
140
|
-
|
141
|
-
@size
|
142
|
-
end
|
143
|
-
|
144
|
-
def uuid
|
145
|
-
@file[:LC_UUID][0].uuid_string
|
146
|
-
end
|
147
|
-
alias debug_id uuid
|
44
|
+
dsym_filename = file_path.split('/').select { |f| f.downcase.end_with?('.dsym') }.last
|
45
|
+
dsym_filenames << dsym_filename unless dsym_filenames.include?(dsym_filename)
|
148
46
|
|
149
|
-
|
150
|
-
|
151
|
-
|
47
|
+
unless file_path.start_with?(dsym_filename)
|
48
|
+
file_path = file_path.split('/')[1..-1].join('/')
|
49
|
+
end
|
152
50
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
cpu_type: cpu_type,
|
159
|
-
size: size,
|
160
|
-
human_size: size(human_size: true)
|
161
|
-
}
|
162
|
-
end
|
51
|
+
dest_path = ::File.join(base_path, file_path)
|
52
|
+
entry.extract(dest_path) unless ::File.exist?(dest_path)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
}.call
|
163
56
|
end
|
164
57
|
end
|
165
58
|
end
|
data/lib/app_info/error.rb
CHANGED
@@ -9,7 +9,13 @@ module AppInfo
|
|
9
9
|
|
10
10
|
class NotFoundError < Error; end
|
11
11
|
|
12
|
-
class
|
12
|
+
class NotFoundWinBinraryError < NotFoundError; end
|
13
13
|
|
14
14
|
class ProtobufParseError < Error; end
|
15
|
+
|
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
|
15
21
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# AppInfo base file
|
4
|
+
module AppInfo
|
5
|
+
class File
|
6
|
+
attr_reader :file, :logger
|
7
|
+
|
8
|
+
def initialize(file, logger: AppInfo.logger)
|
9
|
+
@file = file
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
# @abstract Subclass and override {#file_type} to implement
|
14
|
+
def file_type
|
15
|
+
Platform::UNKNOWN
|
16
|
+
end
|
17
|
+
|
18
|
+
# @abstract Subclass and override {#size} to implement
|
19
|
+
def size(human_size: false)
|
20
|
+
raise NotImplementedError, ".#{__method__} method implantation required in #{self.class}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zip'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'securerandom'
|
7
|
+
|
8
|
+
module AppInfo::Helper
|
9
|
+
module Archive
|
10
|
+
# Unarchive zip file
|
11
|
+
#
|
12
|
+
# source: https://github.com/soffes/lagunitas/blob/master/lib/lagunitas/ipa.rb
|
13
|
+
def unarchive(file, prefix:, dest_path: '/tmp')
|
14
|
+
base_path = Dir.mktmpdir("appinfo-#{prefix}", dest_path)
|
15
|
+
Zip::File.open(file) do |zip_file|
|
16
|
+
if block_given?
|
17
|
+
yield base_path, zip_file
|
18
|
+
else
|
19
|
+
zip_file.each do |f|
|
20
|
+
f_path = ::File.join(base_path, f.name)
|
21
|
+
FileUtils.mkdir_p(::File.dirname(f_path))
|
22
|
+
zip_file.extract(f, f_path) unless ::File.exist?(f_path)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
base_path
|
28
|
+
end
|
29
|
+
|
30
|
+
def tempdir(file, prefix:, system: false)
|
31
|
+
base_path = system ? '/tmp' : ::File.dirname(file)
|
32
|
+
full_prefix = "appinfo-#{prefix}-#{::File.basename(file, '.*')}"
|
33
|
+
dest_path = Dir.mktmpdir(full_prefix, base_path)
|
34
|
+
::File.join(dest_path, ::File.basename(file))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppInfo::Helper
|
4
|
+
module HumanFileSize
|
5
|
+
def file_to_human_size(file, human_size:)
|
6
|
+
number = ::File.size(file)
|
7
|
+
human_size ? number_to_human_size(number) : number
|
8
|
+
end
|
9
|
+
|
10
|
+
FILE_SIZE_UNITS = %w[B KB MB GB TB].freeze
|
11
|
+
|
12
|
+
def number_to_human_size(number)
|
13
|
+
if number.to_i < 1024
|
14
|
+
exponent = 0
|
15
|
+
else
|
16
|
+
max_exp = FILE_SIZE_UNITS.size - 1
|
17
|
+
exponent = (Math.log(number) / Math.log(1024)).to_i
|
18
|
+
exponent = max_exp if exponent > max_exp
|
19
|
+
number = format('%<number>.2f', number: (number / (1024**exponent.to_f)))
|
20
|
+
end
|
21
|
+
|
22
|
+
"#{number} #{FILE_SIZE_UNITS[exponent]}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|