app-info 1.0.1
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +9 -0
- data/app_info.gemspec +29 -0
- data/lib/app-info.rb +1 -0
- data/lib/app_info.rb +24 -0
- data/lib/app_info/core_ext/object/try.rb +25 -0
- data/lib/app_info/parser.rb +14 -0
- data/lib/app_info/parser/apk.rb +119 -0
- data/lib/app_info/parser/ipa.rb +218 -0
- data/lib/app_info/parser/ipa/info_plist.rb +123 -0
- data/lib/app_info/parser/ipa/mobile_provision.rb +69 -0
- data/lib/app_info/version.rb +3 -0
- metadata +176 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4133c8a5d6188b8b9d111b8c7f105b49f455a111
|
4
|
+
data.tar.gz: db50999794694f324ea5ca1ae34c77ab42e68916
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1fd826502a22607e9a6fea4004bee493e0413bca82f2e2e761fccdc2b2c4fbe6dd34ce49cc7b054321f377657a580eb679b156685338c0defe706b14501b3a49
|
7
|
+
data.tar.gz: b97a73a49da60aee6be611f463ada184446e2cfd557bb56336653b0ee47ea913ee51bf6dfae0c6061d828aaed79a8312bdfb736489e4cad3079d931596d95d10
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at icyleaf.cn@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 icyleaf
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# app_info
|
2
|
+
|
3
|
+
Teardown tool for mobile app(ipa/apk), analysis metedata like version, name, icon etc.
|
4
|
+
|
5
|
+
## Support
|
6
|
+
|
7
|
+
- Android apk file
|
8
|
+
- iOS ipa file
|
9
|
+
- Info.plist file
|
10
|
+
- .mobileprovision file
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'app_info'
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
$ bundle
|
24
|
+
```
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
$ gem install app_info
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
### Initialize
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'app_info'
|
38
|
+
|
39
|
+
# Automatic detect file extsion and parse
|
40
|
+
parser = AppInfo.parse('iphone.ipa')
|
41
|
+
parser = AppInfo.parse('ipad.ipa')
|
42
|
+
parser = AppInfo.parse('android.ipa')
|
43
|
+
parser = AppInfo.parse('App/Info.plist')
|
44
|
+
parser = AppInfo.parse('provisioning_profile/uuid.mobileprovision')
|
45
|
+
```
|
46
|
+
|
47
|
+
### iOS
|
48
|
+
|
49
|
+
Teardown suport iPhone/iPad/Universal.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
ipa = AppInfo.parse('iphone.ipa')
|
53
|
+
|
54
|
+
# get app release version
|
55
|
+
ipa.release_version
|
56
|
+
# => 1.0
|
57
|
+
|
58
|
+
# get app bundle id
|
59
|
+
ipa.bundle_id
|
60
|
+
# => com.icyleaf.AppInfoDemo
|
61
|
+
|
62
|
+
# get app icons
|
63
|
+
ipa.icons
|
64
|
+
# => [{:name=>"AppIcon29x29@2x~ipad.png", :file=>"/var/folders/mb/8cm0fz4d499968yss9y1j8bc0000gp/T/d20160728-69669-1xnub30/AppInfo-ios-a5369339399e62046d7d59c52254dac6/Payload/bundle.app/AppIcon29x29@2x~ipad.png", :dimensions=>[58, 58]}, ...]
|
65
|
+
|
66
|
+
# get provisioning profile devices
|
67
|
+
ipa.devices
|
68
|
+
# => ['18cf53cddee60c5af9c97b1521e7cbf8342628da']
|
69
|
+
|
70
|
+
# detect app type
|
71
|
+
ipa.ipad?
|
72
|
+
ipa.iphone?
|
73
|
+
ipa.universal?
|
74
|
+
|
75
|
+
# detect app release type
|
76
|
+
ipa.release_type
|
77
|
+
# => 'AdHoc'
|
78
|
+
|
79
|
+
# get more propety in Info.plist
|
80
|
+
ipa.info[:CFBundleDisplayName]
|
81
|
+
# => 'AppInfoDemo'
|
82
|
+
```
|
83
|
+
|
84
|
+
More to check [rspec test](spec/app_info).
|
85
|
+
|
86
|
+
### Mobile Provision
|
87
|
+
|
88
|
+
Extract from IPA parser, it could teardown any .mobileprovision file(Provisioning Profile).
|
89
|
+
you can download it from Apple Developer Portal.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
profile = AppInfo.parse('~/Library/MobileDevice/Provisioning\ Profiles/6e374bb8-a801-411f-ab28-96a4baa23814.mobileprovision')
|
93
|
+
|
94
|
+
# get app release version
|
95
|
+
profile.team_id
|
96
|
+
# => '3J9E73E9XS'
|
97
|
+
|
98
|
+
# get app package name
|
99
|
+
profile.team_name
|
100
|
+
# => 'Company/Team Name'
|
101
|
+
|
102
|
+
# get app icons
|
103
|
+
profile.devices
|
104
|
+
# => ['18cf53cddee60c5af9c97b1521e7cbf8342628da']
|
105
|
+
```
|
106
|
+
|
107
|
+
### Android
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
apk = AppInfo.parse('android.apk')
|
111
|
+
|
112
|
+
# get app release version
|
113
|
+
apk.release_version
|
114
|
+
# => 1.0
|
115
|
+
|
116
|
+
# get app package name
|
117
|
+
apk.package_namebundle_id
|
118
|
+
# => com.icyleaf.AppInfoDemo
|
119
|
+
|
120
|
+
# get app icons
|
121
|
+
apk.icons
|
122
|
+
# => [{:name=>"ic_launcher.png", :file=> "/var/folders/mb/8cm0fz4d499968yss9y1j8bc0000gp/T/d20160728-70163-10d47fl/AppInfo-android-cccbf89a889eb592c5c6f342d56b9a49/res/mipmap-mdpi-v4/ic_launcher.png/ic_launcher.png", :dimensions=>[48, 48]}, ...]
|
123
|
+
|
124
|
+
# get app support min sdk version
|
125
|
+
apk.min_sdk_version
|
126
|
+
# => 13
|
127
|
+
|
128
|
+
# detect app type (It's difficult to detect phone or tablet)
|
129
|
+
ipa.tv?
|
130
|
+
ipa.wear?
|
131
|
+
```
|
132
|
+
|
133
|
+
## Development
|
134
|
+
|
135
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
136
|
+
|
137
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
138
|
+
|
139
|
+
## Contributing
|
140
|
+
|
141
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/app-info. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
142
|
+
|
143
|
+
|
144
|
+
## License
|
145
|
+
|
146
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
147
|
+
|
data/Rakefile
ADDED
data/app_info.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'app_info/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'app-info'
|
8
|
+
spec.version = AppInfo::VERSION
|
9
|
+
spec.authors = ['icyleaf']
|
10
|
+
spec.email = ['icyleaf.cn@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'Teardown tool for mobile app(ipa/apk), analysis metedata like version, name, icon'
|
13
|
+
spec.description = 'Teardown tool for ipa/apk files, even support for info.plist and .mobileprovision files'
|
14
|
+
spec.homepage = 'http://github.com/icyleaf/app-info'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_dependency 'CFPropertyList', '~> 2.3.2'
|
21
|
+
spec.add_dependency 'pngdefry', '~> 0.1.2'
|
22
|
+
spec.add_dependency 'ruby_android', '~> 0.7.7'
|
23
|
+
spec.add_dependency 'image_size', '~> 1.4.2'
|
24
|
+
spec.add_dependency 'os', '~> 0.9.6'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
29
|
+
end
|
data/lib/app-info.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/app_info'
|
data/lib/app_info.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'app_info/version'
|
2
|
+
require 'app_info/parser'
|
3
|
+
|
4
|
+
#
|
5
|
+
# AppInfo Module
|
6
|
+
module AppInfo
|
7
|
+
#
|
8
|
+
# Get a new parser for automatic
|
9
|
+
def self.parse(file)
|
10
|
+
raise NotFoundError, file unless File.exist?(file)
|
11
|
+
|
12
|
+
case File.extname(file).downcase
|
13
|
+
when '.ipa' then Parser::IPA.new(file)
|
14
|
+
when '.apk' then Parser::APK.new(file)
|
15
|
+
when '.mobileprovision' then Parser::MobileProvision.new(file)
|
16
|
+
else
|
17
|
+
raise NotAppError, file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
singleton_class.send(:alias_method, :dump, :parse)
|
21
|
+
|
22
|
+
class NotFoundError < StandardError; end
|
23
|
+
class NotAppError < StandardError; end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module AppInfo
|
2
|
+
# Monkey Patch for Object
|
3
|
+
module Tryable
|
4
|
+
def try(*a, &b)
|
5
|
+
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
6
|
+
end
|
7
|
+
|
8
|
+
def try!(*a, &b)
|
9
|
+
if a.empty? && block_given?
|
10
|
+
if b.arity.zero?
|
11
|
+
instance_eval(&b)
|
12
|
+
else
|
13
|
+
yield self
|
14
|
+
end
|
15
|
+
else
|
16
|
+
public_send(*a, &b)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# :nodoc:
|
23
|
+
class Object
|
24
|
+
include AppInfo::Tryable
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'app_info/parser/ipa'
|
2
|
+
require 'app_info/parser/ipa/info_plist'
|
3
|
+
require 'app_info/parser/ipa/mobile_provision'
|
4
|
+
require 'app_info/parser/apk'
|
5
|
+
|
6
|
+
module AppInfo
|
7
|
+
module Parser
|
8
|
+
# App Platform
|
9
|
+
module Platform
|
10
|
+
IOS = 'iOS'.freeze
|
11
|
+
ANDROID = 'Android'.freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'ruby_apk'
|
2
|
+
require 'image_size'
|
3
|
+
|
4
|
+
module AppInfo
|
5
|
+
module Parser
|
6
|
+
# Parse APK file
|
7
|
+
class APK
|
8
|
+
attr_reader :file, :apk
|
9
|
+
|
10
|
+
# APK Devices
|
11
|
+
module Device
|
12
|
+
PHONE = 'Phone'.freeze
|
13
|
+
TABLET = 'Tablet'.freeze
|
14
|
+
WATCH = 'Watch'.freeze
|
15
|
+
TV = 'Television'.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(file)
|
19
|
+
@file = file
|
20
|
+
@apk = ::Android::Apk.new(file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def os
|
24
|
+
Parser::Platform::ANDROID
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_version
|
28
|
+
manifest.version_code.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
def release_version
|
32
|
+
manifest.version_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def name
|
36
|
+
resource.find('@string/app_name')
|
37
|
+
end
|
38
|
+
|
39
|
+
def bundle_id
|
40
|
+
manifest.package_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def device_type
|
44
|
+
if wear?
|
45
|
+
Device::WATCH
|
46
|
+
elsif tv?
|
47
|
+
Device::TV
|
48
|
+
else
|
49
|
+
Device::PHONE
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# TODO: find a way to detect
|
54
|
+
# def tablet?
|
55
|
+
# resource
|
56
|
+
# end
|
57
|
+
|
58
|
+
def wear?
|
59
|
+
uses_features.include?('android.hardware.type.watch')
|
60
|
+
end
|
61
|
+
|
62
|
+
def tv?
|
63
|
+
uses_features.include?('android.software.leanback')
|
64
|
+
end
|
65
|
+
|
66
|
+
def min_sdk_version
|
67
|
+
manifest.min_sdk_ver
|
68
|
+
end
|
69
|
+
|
70
|
+
def target_sdk_version
|
71
|
+
manifest.doc.elements['/manifest/uses-sdk'].attributes['targetSdkVersion'].to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
def uses_features
|
75
|
+
uses_features = []
|
76
|
+
manifest.doc.each_element('/manifest/uses-feature') do |elem|
|
77
|
+
uses_features << elem.attributes['name']
|
78
|
+
end
|
79
|
+
uses_features.uniq
|
80
|
+
end
|
81
|
+
|
82
|
+
def manifest
|
83
|
+
@apk.manifest
|
84
|
+
end
|
85
|
+
|
86
|
+
def resource
|
87
|
+
@apk.resource
|
88
|
+
end
|
89
|
+
|
90
|
+
def icons
|
91
|
+
unless @icons
|
92
|
+
tmp_path = File.join(Dir.mktmpdir, "AppInfo-android-#{SecureRandom.hex}")
|
93
|
+
|
94
|
+
@icons = @apk.icon.each_with_object([]) do |(path, data), obj|
|
95
|
+
icon_name = File.basename(path)
|
96
|
+
icon_path = File.join(tmp_path, File.path(path))
|
97
|
+
icon_file = File.join(icon_path, icon_name)
|
98
|
+
FileUtils.mkdir_p icon_path
|
99
|
+
File.open(icon_file, 'w') do |f|
|
100
|
+
f.write data
|
101
|
+
end
|
102
|
+
|
103
|
+
obj << {
|
104
|
+
name: icon_name,
|
105
|
+
file: icon_file,
|
106
|
+
dimensions: ImageSize.path(icon_file).size
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@icons
|
112
|
+
end
|
113
|
+
|
114
|
+
alias identifier bundle_id
|
115
|
+
alias package_name bundle_id
|
116
|
+
alias device_type os
|
117
|
+
end # /APK
|
118
|
+
end # /Parser
|
119
|
+
end # /AppInfo
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'os'
|
2
|
+
require 'pngdefry'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'cfpropertylist'
|
6
|
+
require 'app_info/core_ext/object/try'
|
7
|
+
|
8
|
+
module AppInfo
|
9
|
+
module Parser
|
10
|
+
# IPA parser
|
11
|
+
class IPA
|
12
|
+
attr_reader :file, :app_path
|
13
|
+
|
14
|
+
# iOS Export types
|
15
|
+
module ExportType
|
16
|
+
DEBUG = 'Debug'.freeze
|
17
|
+
ADHOC = 'AdHoc'.freeze
|
18
|
+
INHOUSE = 'inHouse'.freeze
|
19
|
+
RELEASE = 'Release'.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(file)
|
23
|
+
@file = file
|
24
|
+
@app_path = app_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def os
|
28
|
+
Parser::Platform::IOS
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_version
|
32
|
+
info.build_version
|
33
|
+
end
|
34
|
+
|
35
|
+
def release_version
|
36
|
+
info.release_version
|
37
|
+
end
|
38
|
+
|
39
|
+
def identifier
|
40
|
+
info.identifier
|
41
|
+
end
|
42
|
+
|
43
|
+
def name
|
44
|
+
display_name || bundle_name
|
45
|
+
end
|
46
|
+
|
47
|
+
def display_name
|
48
|
+
info.display_name
|
49
|
+
end
|
50
|
+
|
51
|
+
def bundle_name
|
52
|
+
info.bundle_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def icons
|
56
|
+
info.icons
|
57
|
+
end
|
58
|
+
|
59
|
+
def devices
|
60
|
+
mobileprovision.devices
|
61
|
+
end
|
62
|
+
|
63
|
+
def team_name
|
64
|
+
mobileprovision.team_name
|
65
|
+
end
|
66
|
+
|
67
|
+
def team_identifier
|
68
|
+
mobileprovision.team_identifier
|
69
|
+
end
|
70
|
+
|
71
|
+
def profile_name
|
72
|
+
mobileprovision.profile_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def expired_date
|
76
|
+
mobileprovision.expired_date
|
77
|
+
end
|
78
|
+
|
79
|
+
def distribution_name
|
80
|
+
"#{profile_name} - #{team_name}" if profile_name && team_name
|
81
|
+
end
|
82
|
+
|
83
|
+
def device_type
|
84
|
+
info.device_type
|
85
|
+
end
|
86
|
+
|
87
|
+
def iphone?
|
88
|
+
info.iphone?
|
89
|
+
end
|
90
|
+
|
91
|
+
def ipad?
|
92
|
+
info.ipad?
|
93
|
+
end
|
94
|
+
|
95
|
+
def universal?
|
96
|
+
info.universal?
|
97
|
+
end
|
98
|
+
|
99
|
+
def release_type
|
100
|
+
if stored?
|
101
|
+
ExportType::RELEASE
|
102
|
+
else
|
103
|
+
build_type
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_type
|
108
|
+
if mobileprovision?
|
109
|
+
if devices
|
110
|
+
ExportType::ADHOC
|
111
|
+
else
|
112
|
+
ExportType::INHOUSE
|
113
|
+
end
|
114
|
+
else
|
115
|
+
ExportType::DEBUG
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def stored?
|
120
|
+
metadata? ? true : false
|
121
|
+
end
|
122
|
+
|
123
|
+
def hide_developer_certificates
|
124
|
+
mobileprovision.delete('DeveloperCertificates') if mobileprovision?
|
125
|
+
end
|
126
|
+
|
127
|
+
def cleanup!
|
128
|
+
return unless @contents
|
129
|
+
FileUtils.rm_rf(@contents)
|
130
|
+
|
131
|
+
@contents = nil
|
132
|
+
@icons = nil
|
133
|
+
@app_path = nil
|
134
|
+
@metadata = nil
|
135
|
+
@metadata_path = nil
|
136
|
+
@info = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def mobileprovision
|
140
|
+
return unless mobileprovision?
|
141
|
+
return @mobileprovision if @mobileprovision
|
142
|
+
|
143
|
+
return MobileProvision.new(nil) unless OS.mac?
|
144
|
+
|
145
|
+
@mobileprovision = MobileProvision.new(mobileprovision_path)
|
146
|
+
end
|
147
|
+
|
148
|
+
def mobileprovision?
|
149
|
+
File.exist?mobileprovision_path
|
150
|
+
end
|
151
|
+
|
152
|
+
def mobileprovision_path
|
153
|
+
filename = 'embedded.mobileprovision'
|
154
|
+
@mobileprovision_path ||= File.join(@file, filename)
|
155
|
+
unless File.exist?@mobileprovision_path
|
156
|
+
@mobileprovision_path = File.join(app_path, filename)
|
157
|
+
end
|
158
|
+
|
159
|
+
@mobileprovision_path
|
160
|
+
end
|
161
|
+
|
162
|
+
def metadata
|
163
|
+
return unless metadata?
|
164
|
+
@metadata ||= CFPropertyList.native_types(CFPropertyList::List.new(file: metadata_path).value)
|
165
|
+
end
|
166
|
+
|
167
|
+
def metadata?
|
168
|
+
File.exist?(metadata_path)
|
169
|
+
end
|
170
|
+
|
171
|
+
def metadata_path
|
172
|
+
@metadata_path ||= File.join(@contents, 'iTunesMetadata.plist')
|
173
|
+
end
|
174
|
+
|
175
|
+
def info
|
176
|
+
@info ||= InfoPlist.new(@app_path)
|
177
|
+
end
|
178
|
+
|
179
|
+
def app_path
|
180
|
+
@app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
|
181
|
+
end
|
182
|
+
|
183
|
+
alias bundle_id identifier
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def contents
|
188
|
+
# source: https://github.com/soffes/lagunitas/blob/master/lib/lagunitas/ipa.rb
|
189
|
+
unless @contents
|
190
|
+
@contents = "#{Dir.mktmpdir}/AppInfo-ios-#{SecureRandom.hex}"
|
191
|
+
Zip::File.open(@file) do |zip_file|
|
192
|
+
zip_file.each do |f|
|
193
|
+
f_path = File.join(@contents, f.name)
|
194
|
+
FileUtils.mkdir_p(File.dirname(f_path))
|
195
|
+
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
@contents
|
201
|
+
end
|
202
|
+
|
203
|
+
def icons_root_path
|
204
|
+
iphone = 'CFBundleIcons'.freeze
|
205
|
+
ipad = 'CFBundleIcons~ipad'.freeze
|
206
|
+
|
207
|
+
case device_type
|
208
|
+
when 'iPhone'
|
209
|
+
[iphone]
|
210
|
+
when 'iPad'
|
211
|
+
[ipad]
|
212
|
+
when 'Universal'
|
213
|
+
[iphone, ipad]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end # /IPA
|
217
|
+
end # /Parser
|
218
|
+
end # /AppInfo
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'cfpropertylist'
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
module Parser
|
5
|
+
# iOS Info.plist parser
|
6
|
+
class InfoPlist
|
7
|
+
def initialize(app_path)
|
8
|
+
@app_path = app_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_version
|
12
|
+
info.try(:[], 'CFBundleVersion')
|
13
|
+
end
|
14
|
+
|
15
|
+
def release_version
|
16
|
+
info.try(:[], 'CFBundleShortVersionString')
|
17
|
+
end
|
18
|
+
|
19
|
+
def identifier
|
20
|
+
info.try(:[], 'CFBundleIdentifier')
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
display_name || bundle_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def display_name
|
28
|
+
info.try(:[], 'CFBundleDisplayName')
|
29
|
+
end
|
30
|
+
|
31
|
+
def bundle_name
|
32
|
+
info.try(:[], 'CFBundleName')
|
33
|
+
end
|
34
|
+
|
35
|
+
def icons
|
36
|
+
return @icons if @icons
|
37
|
+
|
38
|
+
@icons = []
|
39
|
+
icons_root_path.each do |name|
|
40
|
+
icon_array = info.try(:[], name)
|
41
|
+
.try(:[], 'CFBundlePrimaryIcon')
|
42
|
+
.try(:[], 'CFBundleIconFiles')
|
43
|
+
|
44
|
+
next if icon_array.nil? || icon_array.count == 0
|
45
|
+
|
46
|
+
icon_array.each do |items|
|
47
|
+
Dir.glob(File.join(@app_path, "#{items}*")).find_all.each do |file|
|
48
|
+
dict = {
|
49
|
+
name: File.basename(file),
|
50
|
+
file: file,
|
51
|
+
dimensions: Pngdefry.dimensions(file)
|
52
|
+
}
|
53
|
+
|
54
|
+
@icons.push(dict)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@icons
|
60
|
+
end
|
61
|
+
|
62
|
+
def device_type
|
63
|
+
device_family = info.try(:[], 'UIDeviceFamily')
|
64
|
+
if device_family.length == 1
|
65
|
+
case device_family
|
66
|
+
when [1]
|
67
|
+
'iPhone'
|
68
|
+
when [2]
|
69
|
+
'iPad'
|
70
|
+
end
|
71
|
+
elsif device_family.length == 2 && device_family == [1, 2]
|
72
|
+
'Universal'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def iphone?
|
77
|
+
device_type == 'iPhone'
|
78
|
+
end
|
79
|
+
|
80
|
+
def ipad?
|
81
|
+
device_type == 'iPad'
|
82
|
+
end
|
83
|
+
|
84
|
+
def universal?
|
85
|
+
device_type == 'Universal'
|
86
|
+
end
|
87
|
+
|
88
|
+
def release_type
|
89
|
+
if stored?
|
90
|
+
'Store'
|
91
|
+
else
|
92
|
+
build_type
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def info
|
97
|
+
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: info_path).value)
|
98
|
+
end
|
99
|
+
|
100
|
+
def info_path
|
101
|
+
File.join(@app_path, 'Info.plist')
|
102
|
+
end
|
103
|
+
|
104
|
+
alias bundle_id identifier
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def icons_root_path
|
109
|
+
iphone = 'CFBundleIcons'.freeze
|
110
|
+
ipad = 'CFBundleIcons~ipad'.freeze
|
111
|
+
|
112
|
+
case device_type
|
113
|
+
when 'iPhone'
|
114
|
+
[iphone]
|
115
|
+
when 'iPad'
|
116
|
+
[ipad]
|
117
|
+
when 'Universal'
|
118
|
+
[iphone, ipad]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'cfpropertylist'
|
2
|
+
|
3
|
+
module AppInfo
|
4
|
+
module Parser
|
5
|
+
# .mobileprovision file parser
|
6
|
+
class MobileProvision
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
mobileprovision.try(:[], 'Name')
|
13
|
+
end
|
14
|
+
|
15
|
+
def app_name
|
16
|
+
mobileprovision.try(:[], 'AppIDName')
|
17
|
+
end
|
18
|
+
|
19
|
+
def devices
|
20
|
+
mobileprovision.try(:[], 'ProvisionedDevices')
|
21
|
+
end
|
22
|
+
|
23
|
+
def team_identifier
|
24
|
+
mobileprovision.try(:[], 'TeamIdentifier')
|
25
|
+
end
|
26
|
+
|
27
|
+
def team_name
|
28
|
+
mobileprovision.try(:[], 'TeamName')
|
29
|
+
end
|
30
|
+
|
31
|
+
def profile_name
|
32
|
+
mobileprovision.try(:[], 'Name')
|
33
|
+
end
|
34
|
+
|
35
|
+
def created_date
|
36
|
+
mobileprovision.try(:[], 'CreationDate')
|
37
|
+
end
|
38
|
+
|
39
|
+
def expired_date
|
40
|
+
mobileprovision.try(:[], 'ExpirationDate')
|
41
|
+
end
|
42
|
+
|
43
|
+
def entitlements
|
44
|
+
mobileprovision.try(:[], 'Entitlements')
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(method_name, *args, &block)
|
48
|
+
key = if method_name.to_s.include?('_')
|
49
|
+
method_name.to_s.split('_').map {|k| k.capitalize }.join('')
|
50
|
+
else
|
51
|
+
method_name.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
mobileprovision.try(:[], key)
|
55
|
+
end
|
56
|
+
|
57
|
+
def empty?
|
58
|
+
mobileprovision.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def mobileprovision
|
62
|
+
data = `security cms -D -i "#{@path}"`
|
63
|
+
@mobileprovision = CFPropertyList.native_types(CFPropertyList::List.new(data: data).value)
|
64
|
+
rescue CFFormatError
|
65
|
+
@mobileprovision = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: app-info
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- icyleaf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: CFPropertyList
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.3.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.3.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pngdefry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby_android
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.7.7
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.7.7
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: image_size
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.4.2
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.4.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: os
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.9.6
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.9.6
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.12'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.12'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '10.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '10.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
description: Teardown tool for ipa/apk files, even support for info.plist and .mobileprovision
|
126
|
+
files
|
127
|
+
email:
|
128
|
+
- icyleaf.cn@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- ".rspec"
|
135
|
+
- ".travis.yml"
|
136
|
+
- CODE_OF_CONDUCT.md
|
137
|
+
- Gemfile
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- app_info.gemspec
|
142
|
+
- lib/app-info.rb
|
143
|
+
- lib/app_info.rb
|
144
|
+
- lib/app_info/core_ext/object/try.rb
|
145
|
+
- lib/app_info/parser.rb
|
146
|
+
- lib/app_info/parser/apk.rb
|
147
|
+
- lib/app_info/parser/ipa.rb
|
148
|
+
- lib/app_info/parser/ipa/info_plist.rb
|
149
|
+
- lib/app_info/parser/ipa/mobile_provision.rb
|
150
|
+
- lib/app_info/version.rb
|
151
|
+
homepage: http://github.com/icyleaf/app-info
|
152
|
+
licenses:
|
153
|
+
- MIT
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.6.6
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: Teardown tool for mobile app(ipa/apk), analysis metedata like version, name,
|
175
|
+
icon
|
176
|
+
test_files: []
|