ipa_analyzer 0.1.0

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a7ff3fc2e0fd9c5325945dbc74f69529c05a6404
4
+ data.tar.gz: 3fcff8cfd50124ced9a7c2d8103a65a5e5d87068
5
+ SHA512:
6
+ metadata.gz: 0aefb110eaa03fac338ce0ac516b93effe282628b7a7381e66c498b273a8e8b3e9c609bdf1a5a13d45da07b5690a66c27f62c92902c2babd0559fbfd5924c80a
7
+ data.tar.gz: 1648eb696b7f9f7c84ed659d0c674c6aed868791cfcd72e5cd4a9b0b7b2d2b7f548d6ebb20e99e843f6ddb8d83da5a1d7ce1191e4fe158e189fbb00e27d6a470
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ipa_analyzer (0.1.0)
5
+ plist (~> 3.1, >= 3.1.0)
6
+ rubyzip (~> 1.1.7, >= 1.1.7)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ diff-lcs (1.2.5)
12
+ plist (3.1.0)
13
+ rake (10.4.2)
14
+ rspec (3.2.0)
15
+ rspec-core (~> 3.2.0)
16
+ rspec-expectations (~> 3.2.0)
17
+ rspec-mocks (~> 3.2.0)
18
+ rspec-core (3.2.0)
19
+ rspec-support (~> 3.2.0)
20
+ rspec-expectations (3.2.0)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.2.0)
23
+ rspec-mocks (3.2.0)
24
+ diff-lcs (>= 1.2.0, < 2.0)
25
+ rspec-support (~> 3.2.0)
26
+ rspec-support (3.2.1)
27
+ rubyzip (1.1.7)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ ipa_analyzer!
34
+ rake
35
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Bitrise
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 all
13
+ 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 THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,19 @@
1
+ # ipa_analyzer
2
+
3
+ iOS IPA file analyzer
4
+
5
+ **This code will be converted to a Ruby Gem
6
+ once the basic functionality is implemented**
7
+
8
+
9
+ ## Usage example
10
+
11
+ bundle exec ruby ipa_analyzer.rb -i /path/to/app.ipa -p --info-plist --prov
12
+
13
+ This will collect and print both the embedded mobileprovisioning
14
+ and the Info.plist, in a pretty printed JSON output.
15
+
16
+
17
+ ## Requirements
18
+
19
+ * OS: OS X (tested on 10.10 Yosemite)
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+
3
+ #
4
+ # Prints the given command, then executes it
5
+ # Example: print_and_do_command echo 'hi'
6
+ #
7
+ function print_and_do_command {
8
+ echo " -> $ $@"
9
+ $@
10
+ }
11
+
12
+ #
13
+ # Print the given command, execute it
14
+ # and exit if error happened
15
+ function print_and_do_command_exit_on_error {
16
+ print_and_do_command $@
17
+ if [ $? -ne 0 ]; then
18
+ echo " [!] Failed!"
19
+ exit 1
20
+ fi
21
+ }
22
+
23
+ # test
24
+ print_and_do_command_exit_on_error rspec
25
+ # build
26
+ print_and_do_command_exit_on_error gem build ipa_analyzer.gemspec
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'json'
5
+
6
+ $:.push File.expand_path("../../lib", __FILE__)
7
+ require 'ipa_analyzer'
8
+
9
+
10
+ # -------------------------
11
+ # --- Options
12
+
13
+ options = {
14
+ ipa_path: nil,
15
+ is_verbose: false,
16
+ is_pretty: false,
17
+ is_collect_provision_info: false,
18
+ is_collect_info_plist: false
19
+ }
20
+
21
+ opt_parser = OptionParser.new do |opt|
22
+ opt.banner = "Usage: ipa_analyzer.rb [OPTIONS]"
23
+ opt.separator ""
24
+ opt.separator "Options, the ones marked with * are required"
25
+
26
+ opt.on("-i","--ipa IPA_PATH", "*IPA file path") do |value|
27
+ options[:ipa_path] = value
28
+ end
29
+
30
+ opt.on("--prov","Collect Provisioning Profile (mobileprovision) information from the IPA") do
31
+ options[:is_collect_provision_info] = true
32
+ end
33
+
34
+ opt.on("--info-plist","Collect Info.plist information from the IPA") do
35
+ options[:is_collect_info_plist] = true
36
+ end
37
+
38
+ opt.on("-v","--verbose","Verbose output") do
39
+ options[:is_verbose] = true
40
+ end
41
+
42
+ opt.on("-p","--pretty","Pretty print output") do
43
+ options[:is_pretty] = true
44
+ end
45
+
46
+ opt.on("-h","--help","Shows this help message") do
47
+ puts opt_parser
48
+ exit 0
49
+ end
50
+ end
51
+
52
+ opt_parser.parse!
53
+ $options = options
54
+
55
+
56
+ # -------------------------
57
+ # --- Utils
58
+
59
+ def vputs(msg="")
60
+ if $options[:is_verbose]
61
+ puts msg
62
+ end
63
+ end
64
+
65
+
66
+ # -------------------------
67
+ # --- Main
68
+
69
+ vputs "options: #{options}"
70
+
71
+ raise "No IPA specified" unless options[:ipa_path]
72
+ raise "IPA specified but file does not exist at the provided path" unless File.exist? options[:ipa_path]
73
+
74
+ parsed_infos = {
75
+ mobileprovision: nil,
76
+ info_plist: nil
77
+ }
78
+
79
+ exit_code = 0
80
+
81
+ ipa_analyzer = IpaAnalyzer::Analyzer.new(options[:ipa_path])
82
+ begin
83
+ vputs " * Opening the IPA"
84
+ ipa_analyzer.open!
85
+
86
+ if options[:is_collect_provision_info]
87
+ vputs " * Collecting Provisioning Profile information"
88
+ parsed_infos[:mobileprovision] = ipa_analyzer.collect_provision_info()
89
+ raise "Failed to collect Provisioning Profile information" if parsed_infos[:mobileprovision].nil?
90
+ end
91
+ if options[:is_collect_info_plist]
92
+ vputs " * Collecting Info.plist information"
93
+ parsed_infos[:info_plist] = ipa_analyzer.collect_info_plist_info()
94
+ raise "Failed to collect Info.plist information" if parsed_infos[:info_plist].nil?
95
+ end
96
+ rescue => ex
97
+ puts
98
+ puts "Failed: #{ex}"
99
+ puts
100
+ exit_code = 1
101
+ raise ex
102
+ ensure
103
+ vputs " * Closing the IPA"
104
+ ipa_analyzer.close()
105
+ end
106
+
107
+ if options[:is_pretty]
108
+ puts JSON.pretty_generate(parsed_infos)
109
+ else
110
+ puts JSON.generate(parsed_infos)
111
+ end
112
+
113
+ exit exit_code
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "ipa_analyzer/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "ipa_analyzer"
8
+ s.authors = ["Bitrise", "Viktor Benei"]
9
+ s.email = 'letsconnect@bitrise.io'
10
+ s.license = "MIT"
11
+ s.homepage = "https://github.com/bitrise-io/ipa_analyzer"
12
+ s.version = IpaAnalyzer::VERSION
13
+ s.platform = Gem::Platform::RUBY
14
+ s.summary = "iOS .ipa analyzer"
15
+ s.description = "Analyze an iOS .ipa file. Can be used as a CLI and can print the information in JSON so it can be used by other tools."
16
+
17
+ s.add_runtime_dependency 'plist', '~> 3.1', '>= 3.1.0'
18
+ s.add_runtime_dependency 'rubyzip', '~> 1.1.7', '>= 1.1.7'
19
+
20
+ s.add_development_dependency "rspec"
21
+ s.add_development_dependency "rake"
22
+
23
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = ['ipa_analyzer']
26
+ s.require_paths = ["lib"]
27
+ end
@@ -0,0 +1,2 @@
1
+ require 'ipa_analyzer/version'
2
+ require 'ipa_analyzer/analyzer'
@@ -0,0 +1,154 @@
1
+ require 'tempfile'
2
+ require 'zip'
3
+ require 'zip/filesystem'
4
+ require 'plist'
5
+
6
+ module IpaAnalyzer
7
+ class Analyzer
8
+ def initialize(ipa_path)
9
+ @ipa_path = ipa_path
10
+ @ipa_zipfile = nil
11
+ @app_folder_path = nil
12
+ end
13
+
14
+ def open!
15
+ @ipa_zipfile = Zip::File.open(@ipa_path)
16
+ @app_folder_path = find_app_folder_in_ipa()
17
+ raise "No app folder found in the IPA" if @app_folder_path.nil?
18
+ end
19
+
20
+ def open?
21
+ return !@ipa_zipfile.nil?
22
+ end
23
+
24
+ def close
25
+ if self.open?
26
+ @ipa_zipfile.close()
27
+ end
28
+ end
29
+
30
+ def collect_provision_info
31
+ raise "IPA is not open" unless self.open?
32
+
33
+ result = {
34
+ path_in_ipa: nil,
35
+ content: {}
36
+ }
37
+ mobileprovision_path = "#{@app_folder_path}/embedded.mobileprovision"
38
+ mobileprovision_entry = @ipa_zipfile.find_entry(mobileprovision_path)
39
+
40
+ raise "Embedded mobile provisioning file not found in (#{@ipa_path}) at path (#{mobileprovision_path})" unless mobileprovision_entry
41
+ result[:path_in_ipa] = "#{mobileprovision_entry}"
42
+
43
+ tempfile = Tempfile.new(::File.basename(mobileprovision_entry.name))
44
+ begin
45
+ @ipa_zipfile.extract(mobileprovision_entry, tempfile.path){ override = true }
46
+ plist = Plist::parse_xml(`security cms -D -i #{tempfile.path}`)
47
+
48
+ plist.each do |key, value|
49
+ next if key == "DeveloperCertificates"
50
+
51
+ parse_value = nil
52
+ case value
53
+ when Hash
54
+ parse_value = value
55
+ when Array
56
+ parse_value = value
57
+ else
58
+ parse_value = value.to_s
59
+ end
60
+
61
+ result[:content][key] = parse_value
62
+ end
63
+
64
+ rescue => e
65
+ puts e.message
66
+ result = nil
67
+ ensure
68
+ tempfile.close and tempfile.unlink
69
+ end
70
+ return result
71
+ end
72
+
73
+ def collect_info_plist_info
74
+ raise "IPA is not open" unless self.open?
75
+
76
+ result = {
77
+ path_in_ipa: nil,
78
+ content: {}
79
+ }
80
+ info_plist_entry = @ipa_zipfile.find_entry("#{@app_folder_path}/Info.plist")
81
+
82
+ raise "File 'Info.plist' not found in #{@ipa_path}" unless info_plist_entry
83
+ result[:path_in_ipa] = "#{info_plist_entry}"
84
+
85
+ tempfile = Tempfile.new(::File.basename(info_plist_entry.name))
86
+ begin
87
+ @ipa_zipfile.extract(info_plist_entry, tempfile.path){ override = true }
88
+ # convert from binary Plist to XML Plist
89
+ unless system("plutil -convert xml1 '#{tempfile.path}'")
90
+ raise "Failed to convert binary Plist to XML"
91
+ end
92
+ plist = Plist::parse_xml(tempfile.path)
93
+
94
+ plist.each do |key, value|
95
+ parse_value = nil
96
+ case value
97
+ when Hash
98
+ parse_value = value
99
+ when Array
100
+ parse_value = value
101
+ else
102
+ parse_value = value.to_s
103
+ end
104
+
105
+ result[:content][key] = parse_value
106
+ end
107
+
108
+ rescue => e
109
+ puts e.message
110
+ result = nil
111
+ ensure
112
+ tempfile.close and tempfile.unlink
113
+ end
114
+ return result
115
+ end
116
+
117
+
118
+ private
119
+ #
120
+ # Find the .app folder which contains both the "embedded.mobileprovision"
121
+ # and "Info.plist" files.
122
+ def find_app_folder_in_ipa
123
+ raise "IPA is not open" unless self.open?
124
+
125
+ # Check the most common location
126
+ app_folder_in_ipa = "Payload/#{File.basename(@ipa_path, File.extname(@ipa_path))}.app"
127
+ #
128
+ mobileprovision_entry = @ipa_zipfile.find_entry("#{app_folder_in_ipa}/embedded.mobileprovision")
129
+ info_plist_entry = @ipa_zipfile.find_entry("#{app_folder_in_ipa}/Info.plist")
130
+ #
131
+ if !mobileprovision_entry.nil? and !info_plist_entry.nil?
132
+ return app_folder_in_ipa
133
+ end
134
+
135
+ # It's somewhere else - let's find it!
136
+ @ipa_zipfile.dir.entries("Payload").each do |dir_entry|
137
+ if dir_entry =~ /.app$/
138
+ app_folder_in_ipa = "Payload/#{dir_entry}"
139
+ mobileprovision_entry = @ipa_zipfile.find_entry("#{app_folder_in_ipa}/embedded.mobileprovision")
140
+ info_plist_entry = @ipa_zipfile.find_entry("#{app_folder_in_ipa}/Info.plist")
141
+
142
+ if !mobileprovision_entry.nil? and !info_plist_entry.nil?
143
+ break
144
+ end
145
+ end
146
+ end
147
+
148
+ if !mobileprovision_entry.nil? and !info_plist_entry.nil?
149
+ return app_folder_in_ipa
150
+ end
151
+ return nil
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,3 @@
1
+ module IpaAnalyzer
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ipa_analyzer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bitrise
8
+ - Viktor Benei
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: plist
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.1'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 3.1.0
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '3.1'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 3.1.0
34
+ - !ruby/object:Gem::Dependency
35
+ name: rubyzip
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.1.7
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.7
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: 1.1.7
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.1.7
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ description: Analyze an iOS .ipa file. Can be used as a CLI and can print the information
83
+ in JSON so it can be used by other tools.
84
+ email: letsconnect@bitrise.io
85
+ executables:
86
+ - ipa_analyzer
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - "./Gemfile"
91
+ - "./Gemfile.lock"
92
+ - "./LICENSE"
93
+ - "./README.md"
94
+ - "./_scripts/make.sh"
95
+ - "./ipa_analyzer.gemspec"
96
+ - "./lib/ipa_analyzer.rb"
97
+ - "./lib/ipa_analyzer/analyzer.rb"
98
+ - "./lib/ipa_analyzer/version.rb"
99
+ - bin/ipa_analyzer
100
+ homepage: https://github.com/bitrise-io/ipa_analyzer
101
+ licenses:
102
+ - MIT
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.2.2
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: iOS .ipa analyzer
124
+ test_files: []