ruby_android_apk 0.7.7.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.
@@ -0,0 +1,55 @@
1
+
2
+ module Android
3
+ # Utility methods
4
+ module Utils
5
+ # path is apk file or not.
6
+ # @param [String] path target file path
7
+ # @return [Boolean]
8
+ def self.apk?(path)
9
+ begin
10
+ apk = Apk.new(path)
11
+ return true
12
+ rescue => e
13
+ return false
14
+ end
15
+ end
16
+
17
+ # data is elf file or not.
18
+ # @param [String] data target data
19
+ # @return [Boolean]
20
+ def self.elf?(data)
21
+ data[0..3] == "\x7f\x45\x4c\x46"
22
+ rescue => e
23
+ false
24
+ end
25
+
26
+ # data is cert file or not.
27
+ # @param [String] data target data
28
+ # @return [Boolean]
29
+ def self.cert?(data)
30
+ data[0..1] == "\x30\x82"
31
+ rescue => e
32
+ false
33
+ end
34
+
35
+ # data is dex file or not.
36
+ # @param [String] data target data
37
+ # @return [Boolean]
38
+ def self.dex?(data)
39
+ data[0..7] == "\x64\x65\x78\x0a\x30\x33\x35\x00" # "dex\n035\0"
40
+ rescue => e
41
+ false
42
+ end
43
+
44
+ # data is valid dex file or not.
45
+ # @param [String] data target data
46
+ # @return [Boolean]
47
+ def self.valid_dex?(data)
48
+ Android::Dex.new(data)
49
+ true
50
+ rescue => e
51
+ false
52
+ end
53
+ end
54
+ end
55
+
data/lib/ruby_apk.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'android/apk'
2
+ require_relative 'android/manifest'
3
+ require_relative 'android/axml_parser'
4
+ require_relative 'android/dex'
5
+ require_relative 'android/resource'
6
+ require_relative 'android/utils'
7
+ require_relative 'android/layout'
data/spec/apk_spec.rb ADDED
@@ -0,0 +1,332 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'tempfile'
3
+ require 'zip'
4
+ require 'digest/sha1'
5
+ require 'digest/sha2'
6
+ require 'digest/md5'
7
+
8
+ class TempApk
9
+ attr_reader :path
10
+ def initialize
11
+ @tmp = Tempfile.open('apk_spec')
12
+ @path = @tmp.path
13
+ @tmp.close! # delete file
14
+ append("AndroidManifest.xml", "hogehoge")
15
+ append("resources.arsc", "hogehoge")
16
+ end
17
+ def destroy!
18
+ File.unlink(@path) if File.exist? @path
19
+ end
20
+ def append(entry_name, data)
21
+ Zip::File.open(@path, Zip::File::CREATE) { |zip|
22
+ zip.get_output_stream(entry_name) {|f| f.write data }
23
+ }
24
+ end
25
+ def remove(entry_name)
26
+ Zip::File.open(@path, Zip::File::CREATE) { |zip|
27
+ zip.remove(entry_name)
28
+ }
29
+ end
30
+ end
31
+
32
+ describe Android::Apk do
33
+ before do
34
+ $stderr.reopen('/dev/null','w')
35
+ end
36
+ let(:tmp_apk) { TempApk.new }
37
+ let(:tmp_path) { tmp_apk.path }
38
+ let(:apk) { Android::Apk.new(tmp_path) }
39
+ subject { apk }
40
+
41
+ after do
42
+ tmp_apk.destroy!
43
+ end
44
+
45
+ describe "#initialize" do
46
+ let(:path) { tmp_path }
47
+ subject { Android::Apk.new(path) }
48
+ context "with not exist path" do
49
+ let(:path) { "not exist path" }
50
+ it { expect{ subject }.to raise_error Android::NotFoundError }
51
+ end
52
+ context "with not zip file path" do
53
+ let(:path) { __FILE__ } # not zip file
54
+ it { expect{ subject }.to raise_error Android::NotApkFileError }
55
+ end
56
+ context "with zip(and non apk) file" do
57
+ before do
58
+ tmp_apk.append('hoge.txt', 'hogehoge')
59
+ tmp_apk.remove('AndroidManifest.xml')
60
+ end
61
+ it { expect{ subject }.to raise_error Android::NotApkFileError }
62
+ end
63
+ context "with zip includes AndroidManifest.xml" do
64
+ it { should be_a_instance_of Android::Apk }
65
+ end
66
+ end
67
+
68
+ describe "#path" do
69
+ subject { apk.path }
70
+ it "should equals initialized path" do
71
+ should == tmp_path
72
+ end
73
+ end
74
+
75
+ describe "#manifest" do
76
+ subject { apk.manifest }
77
+
78
+ context "when Manifest parse is succeeded." do
79
+ let(:mock_mani) { mock(Android::Manifest) }
80
+
81
+ before do
82
+ end
83
+ it "should return manifest object" do
84
+ Android::Manifest.should_receive(:new).and_return(mock_mani)
85
+ subject.should == mock_mani
86
+ end
87
+ end
88
+
89
+ context "when Manifest parse is failed" do
90
+ it 'should return nil' do
91
+ Android::Manifest.should_receive(:new).and_raise(Android::AXMLParser::ReadError)
92
+ subject.should be_nil
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "#dex" do
98
+ let(:mock_dex) { mock(Android::Dex) }
99
+ subject { apk.dex }
100
+ context "when there is no dex file" do
101
+ it { should be_nil }
102
+ end
103
+ context "when invalid dex file" do
104
+ before do
105
+ tmp_apk.append("classes.dex", "invalid dex")
106
+ end
107
+ it { should be_nil }
108
+ end
109
+ context "with mock classes.dex file" do
110
+ before do
111
+ tmp_apk.append("classes.dex", "mock data")
112
+ end
113
+ it "should return mock value" do
114
+ Android::Dex.should_receive(:new).with("mock data").and_return(mock_dex)
115
+ subject.should == mock_dex
116
+ end
117
+ end
118
+ context "with real classes.dex file" do
119
+ before do
120
+ dex_path = File.expand_path(File.dirname(__FILE__) + '/data/sample_classes.dex')
121
+ tmp_apk.append("classes.dex", File.open(dex_path, "rb") {|f| f.read })
122
+ end
123
+ it { should be_instance_of Android::Dex }
124
+ end
125
+ end
126
+
127
+ its(:bindata) { should be_instance_of String }
128
+ describe '#bindata' do
129
+ specify 'encoding should be ASCII-8BIT' do
130
+ subject.bindata.encoding.should eq Encoding::ASCII_8BIT
131
+ end
132
+ end
133
+
134
+ describe '#resource' do
135
+ let(:mock_rsc) { mock(Android::Resource) }
136
+ subject { apk.resource }
137
+ it "should return manifest object" do
138
+ Android::Resource.should_receive(:new).and_return(mock_rsc)
139
+ subject.should == mock_rsc
140
+ end
141
+ end
142
+
143
+ describe "#size" do
144
+ subject { apk.size }
145
+ it "should return apk file size" do
146
+ should == File.size(tmp_path)
147
+ end
148
+ end
149
+
150
+ describe "#digest" do
151
+ let(:data) { File.open(tmp_apk.path, 'rb'){|f| f.read } }
152
+ subject { apk.digest(type) }
153
+ context "when type is sha1" do
154
+ let(:type) { :sha1 }
155
+ it "should return sha1 digest" do
156
+ should eq Digest::SHA1.hexdigest(data)
157
+ end
158
+ end
159
+ context "when type is sha256" do
160
+ let(:type) { :sha256 }
161
+ it "should return sha256 digest" do
162
+ should == Digest::SHA256.hexdigest(data)
163
+ end
164
+ end
165
+ context "when type is md5" do
166
+ let(:type) { :md5 }
167
+ it "should return md5 digest" do
168
+ should == Digest::MD5.hexdigest(data)
169
+ end
170
+ end
171
+ context "when type is unkown symbol" do
172
+ let(:type) { :unknown }
173
+ it do
174
+ expect { subject }.to raise_error(ArgumentError)
175
+ end
176
+ end
177
+ context "when type is not symbol(String: 'sha1')" do
178
+ let(:type) { 'sha1' }
179
+ it { expect { subject }.to raise_error(ArgumentError) }
180
+ end
181
+ end
182
+
183
+ describe '#time' do
184
+ subject { apk.time }
185
+ it { should be_kind_of Time }
186
+ end
187
+
188
+ describe "#each_file" do
189
+ before do
190
+ tmp_apk.append("hoge.txt", "aaaaaaa")
191
+ end
192
+ it { expect { |b| apk.each_file(&b) }.to yield_successive_args(Array, Array, Array) }
193
+ let(:each_file_result ) {
194
+ result = []
195
+ apk.each_file do |name, data|
196
+ result << [name, data]
197
+ end
198
+ result
199
+ }
200
+
201
+ it "should invoke block with all file" do
202
+ each_file_result.should have(3).items
203
+ each_file_result.should include(['AndroidManifest.xml', 'hogehoge'])
204
+ each_file_result.should include(['hoge.txt','aaaaaaa'])
205
+ end
206
+ end
207
+
208
+ describe '#file' do
209
+ let(:data) { 'aaaaaaaaaaaaaaaaaaaaaaaaaaa' }
210
+ let(:path) { 'hoge.txt' }
211
+ subject { apk.file(path) }
212
+
213
+ before do
214
+ tmp_apk.append('hoge.txt', data)
215
+ end
216
+ context 'assigns exist path' do
217
+ it 'should equal file data' do
218
+ should eq data
219
+ end
220
+ end
221
+ context 'assigns not exist path' do
222
+ let(:path) { 'not_exist_path.txt' }
223
+ it { expect { subject }.to raise_error(Android::NotFoundError) }
224
+ end
225
+ end
226
+
227
+ describe '#each_entry' do
228
+ before do
229
+ tmp_apk.append("hoge.txt", "aaaaaaa")
230
+ end
231
+ it { expect { |b| apk.each_entry(&b) }.to yield_successive_args(Zip::Entry, Zip::Entry, Zip::Entry) }
232
+ end
233
+
234
+ describe '#entry' do
235
+ subject { apk.entry(entry_name) }
236
+ context 'assigns exist entry' do
237
+ let(:entry_name) { 'AndroidManifest.xml' }
238
+ it { should be_instance_of Zip::Entry }
239
+ end
240
+ context 'assigns not exist entry name' do
241
+ let(:entry_name) { 'not_exist_path' }
242
+ it { expect{ subject }.to raise_error(Android::NotFoundError) }
243
+ end
244
+ end
245
+
246
+ describe "#find" do
247
+ before do
248
+ tmp_apk.append("hoge.txt", "aaaaaaa")
249
+ end
250
+ it "should return matched array" do
251
+ array = apk.find do |name, data|
252
+ name == "hoge.txt"
253
+ end
254
+ array.should be_instance_of Array
255
+ array.should have(1).item
256
+ array[0] == "hoge.txt" # returns filename
257
+ end
258
+ context "when no entry is matched" do
259
+ it "should return emtpy array" do
260
+ array = apk.find do |name, dota|
261
+ false # nothing matched
262
+ end
263
+ array.should be_instance_of Array
264
+ array.should be_empty
265
+ end
266
+ end
267
+ end
268
+
269
+ describe "#icon" do
270
+ context "with real apk file" do
271
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
272
+ subject { apk.icon }
273
+ it { should be_a Hash }
274
+ it { should have(3).items }
275
+ it { subject.keys.should =~ ["res/drawable-hdpi/ic_launcher.png", "res/drawable-ldpi/ic_launcher.png", "res/drawable-mdpi/ic_launcher.png"]}
276
+ end
277
+ end
278
+
279
+ describe "#icon" do
280
+ context "with real new apk file" do
281
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_new.apk') }
282
+ subject { apk.icon }
283
+ it { should be_a Hash }
284
+ it { should have(4).items }
285
+ it { subject.keys.should =~ ["res/mipmap-xxhdpi-v4/ic_launcher.png", "res/mipmap-hdpi-v4/ic_launcher.png", "res/mipmap-mdpi-v4/ic_launcher.png", "res/mipmap-xhdpi-v4/ic_launcher.png"]}
286
+ end
287
+ end
288
+
289
+ describe '#signs' do
290
+ context 'with sample apk file' do
291
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
292
+ subject { apk.signs }
293
+ it { should be_a Hash }
294
+ it { should have(1).item }
295
+ it { should have_key('META-INF/CERT.RSA') }
296
+ it { subject['META-INF/CERT.RSA'].should be_a OpenSSL::PKCS7 }
297
+ end
298
+ end
299
+
300
+ describe '#signs' do
301
+ context 'with new sample apk file' do
302
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_new.apk') }
303
+ subject { apk.signs }
304
+ it { should be_a Hash }
305
+ it { should have(1).item }
306
+ it { should have_key('META-INF/CERT.RSA') }
307
+ it { subject['META-INF/CERT.RSA'].should be_a OpenSSL::PKCS7 }
308
+ end
309
+ end
310
+
311
+ describe '#certficates' do
312
+ context 'with sample apk file' do
313
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
314
+ subject { apk.certificates }
315
+ it { should be_a Hash }
316
+ it { should have(1).item }
317
+ it { should have_key('META-INF/CERT.RSA') }
318
+ it { subject['META-INF/CERT.RSA'].should be_a OpenSSL::X509::Certificate }
319
+ end
320
+ end
321
+
322
+ describe '#certficates' do
323
+ context 'with new sample apk file' do
324
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_new.apk') }
325
+ subject { apk.certificates }
326
+ it { should be_a Hash }
327
+ it { should have(1).item }
328
+ it { should have_key('META-INF/CERT.RSA') }
329
+ it { subject['META-INF/CERT.RSA'].should be_a OpenSSL::X509::Certificate }
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ describe Android::AXMLParser do
5
+ let(:bin_xml_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_AndroidManifest.xml') }
6
+ let(:bin_xml){ File.open(bin_xml_path, 'rb') {|f| f.read } }
7
+ let(:axmlparser){ Android::AXMLParser.new(bin_xml) }
8
+
9
+ describe "#parse" do
10
+
11
+ subject { axmlparser.parse }
12
+ context 'with sample_AndroidManifest.xml' do
13
+ it { should be_instance_of(REXML::Document) }
14
+ specify 'root element should be <manifest> element' do
15
+ subject.root.name.should eq 'manifest'
16
+ end
17
+ specify 'should have 2 <uses-permission> elements' do
18
+ subject.get_elements('/manifest/uses-permission').should have(2).items
19
+ end
20
+ end
21
+
22
+ context 'with nil data as binary xml' do
23
+ let(:bin_xml) { nil }
24
+ specify { expect{ subject }.to raise_error }
25
+ end
26
+
27
+ end
28
+
29
+ describe "#strings" do
30
+ context 'with sample_AndroidManifest.xml' do
31
+ subject { axmlparser.strings }
32
+ before do
33
+ axmlparser.parse
34
+ end
35
+ it { should be_instance_of(Array) }
36
+
37
+ # ugh!! the below test cases depend on sample_AndroidManifest.xml
38
+ it { should have(26).items} # in sample manifest.
39
+ it { should include("versionCode") }
40
+ it { should include("versionName") }
41
+ it { should include("minSdkVersion") }
42
+ it { should include("package") }
43
+ it { should include("manifest") }
44
+ end
45
+ end
46
+
47
+ describe '#convert_value' do
48
+ let(:axmlparser){ Android::AXMLParser.new('') }
49
+ subject { axmlparser.convert_value(str_id, flags, val) }
50
+ context 'when parsing boolean attribute' do
51
+ let(:str_id) { 0xFFFFFFFF }
52
+ let(:flags) { 0x12000008 }
53
+ context 'and value is 0x01' do
54
+ let(:val) { 0x01 }
55
+ it { should be_true }
56
+ end
57
+ context 'and value is 0xFFFFFFF' do
58
+ let(:val) { 0xFFFFFFFF }
59
+ it { should be_true }
60
+ end
61
+ context 'and value is 0x00' do
62
+ let(:val) { 0x00 }
63
+ it { should be_false }
64
+ end
65
+ end
66
+ end
67
+ end