ruby_android 0.0.2 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/ruby_apk.iml +51 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +508 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +51 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +2 -2
- data/Rakefile +42 -1
- data/VERSION +1 -0
- data/lib/android/apk.rb +207 -0
- data/lib/android/axml_parser.rb +173 -0
- data/lib/android/dex/access_flag.rb +74 -0
- data/lib/android/dex/dex_object.rb +475 -0
- data/lib/android/dex/info.rb +151 -0
- data/lib/android/dex/utils.rb +45 -0
- data/lib/android/dex.rb +92 -0
- data/lib/android/layout.rb +44 -0
- data/lib/android/manifest.rb +249 -0
- data/lib/android/resource.rb +529 -0
- data/lib/android/utils.rb +55 -0
- data/lib/ruby_apk.rb +7 -0
- data/ruby_android.gemspec +103 -17
- data/spec/apk_spec.rb +301 -0
- data/spec/axml_parser_spec.rb +67 -0
- data/spec/data/sample.apk +0 -0
- data/spec/data/sample_AndroidManifest.xml +0 -0
- data/spec/data/sample_classes.dex +0 -0
- data/spec/data/sample_resources.arsc +0 -0
- data/spec/data/sample_resources_utf16.arsc +0 -0
- data/spec/data/str_resources.arsc +0 -0
- data/spec/dex/access_flag_spec.rb +42 -0
- data/spec/dex/dex_object_spec.rb +118 -0
- data/spec/dex/info_spec.rb +121 -0
- data/spec/dex/utils_spec.rb +56 -0
- data/spec/dex_spec.rb +59 -0
- data/spec/layout_spec.rb +27 -0
- data/spec/manifest_spec.rb +221 -0
- data/spec/resource_spec.rb +170 -0
- data/spec/ruby_apk_spec.rb +4 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/utils_spec.rb +90 -0
- metadata +112 -27
- data/.gitignore +0 -14
- data/lib/ruby_android/version.rb +0 -3
- data/lib/ruby_android.rb +0 -7
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
shared_context 'with sample_classes.dex', with: :sample_dex do
|
4
|
+
let(:dex_path){ File.expand_path(File.dirname(__FILE__) + '/../data/sample_classes.dex') }
|
5
|
+
let(:dex_bin){ File.open(dex_path, 'rb') {|f| f.read } }
|
6
|
+
let(:dex){ Android::Dex.new(dex_bin) }
|
7
|
+
let(:last_class) { dex.classes.last }
|
8
|
+
end
|
9
|
+
describe Android::Dex::ClassInfo do
|
10
|
+
include_context 'with sample_classes.dex'
|
11
|
+
context 'about the last class in Dex#classes with sample_classes.dex' do
|
12
|
+
let(:last_class) { dex.classes.last }
|
13
|
+
its(:name){ should eq 'Lexample/app/sample/SampleCode;' }
|
14
|
+
its(:access_flags){ should be_instance_of Android::Dex::ClassAccessFlag }
|
15
|
+
its(:super_class){ should eq 'Ljava/lang/Object;' }
|
16
|
+
its(:class_data){ should be_instance_of Android::Dex::DexObject::ClassDataItem }
|
17
|
+
its(:class_def){ should be_instance_of Android::Dex::DexObject::ClassDefItem }
|
18
|
+
its(:definition) { should eq 'public class Lexample/app/sample/SampleCode; extends Ljava/lang/Object;' }
|
19
|
+
|
20
|
+
subject { last_class }
|
21
|
+
describe '#static_fields' do
|
22
|
+
subject { last_class.static_fields }
|
23
|
+
it { should have(1).item }
|
24
|
+
specify { subject[0].should be_instance_of Android::Dex::FieldInfo }
|
25
|
+
end
|
26
|
+
describe '#instance_fields' do
|
27
|
+
subject { last_class.instance_fields }
|
28
|
+
it { should have(1).item }
|
29
|
+
specify { subject[0].should be_instance_of Android::Dex::FieldInfo }
|
30
|
+
end
|
31
|
+
describe '#direct_methods' do
|
32
|
+
subject { last_class.direct_methods }
|
33
|
+
it { should have(3).items }
|
34
|
+
specify { subject[0].should be_instance_of Android::Dex::MethodInfo }
|
35
|
+
end
|
36
|
+
describe '#virtual_methods' do
|
37
|
+
subject { last_class.virtual_methods }
|
38
|
+
it { should have(18).items }
|
39
|
+
specify { subject[0].should be_instance_of Android::Dex::MethodInfo }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context 'when class_data_item is nil' do
|
43
|
+
let(:mock_cls_def) {
|
44
|
+
s = double(Android::Dex::DexObject::ClassDefItem)
|
45
|
+
s.stub(:'[]').with(anything()).and_return(0)
|
46
|
+
s.stub(:class_data_item).and_return(nil)
|
47
|
+
s
|
48
|
+
}
|
49
|
+
let(:class_info) { Android::Dex::ClassInfo.new(mock_cls_def, nil) }
|
50
|
+
describe '#static_fields' do
|
51
|
+
subject { class_info.static_fields }
|
52
|
+
it { should be_kind_of Array }
|
53
|
+
it { should be_empty }
|
54
|
+
end
|
55
|
+
describe '#instance_fields' do
|
56
|
+
subject { class_info.instance_fields }
|
57
|
+
it { should be_kind_of Array }
|
58
|
+
it { should be_empty }
|
59
|
+
end
|
60
|
+
describe '#direct_methods' do
|
61
|
+
subject { class_info.direct_methods }
|
62
|
+
it { should be_kind_of Array }
|
63
|
+
it { should be_empty }
|
64
|
+
end
|
65
|
+
describe '#virtual_methods' do
|
66
|
+
subject { class_info.virtual_methods }
|
67
|
+
it { should be_kind_of Array }
|
68
|
+
it { should be_empty }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe Android::Dex::FieldInfo do
|
74
|
+
include_context 'with sample_classes.dex'
|
75
|
+
context 'about the first static field of the last class with sample_classes.dex'do
|
76
|
+
let(:first_static_field) { last_class.static_fields.first }
|
77
|
+
subject { first_static_field }
|
78
|
+
its(:name) { should eq 'TAG' }
|
79
|
+
its(:type) { should eq 'Ljava/lang/String;' }
|
80
|
+
describe '#access_flags' do
|
81
|
+
subject { first_static_field.access_flags }
|
82
|
+
it { should be_instance_of Android::Dex::ClassAccessFlag }
|
83
|
+
specify { subject.to_s.should eq 'private static final' }
|
84
|
+
end
|
85
|
+
describe '#definition' do
|
86
|
+
subject { first_static_field.definition }
|
87
|
+
it { should eq 'private static final Ljava/lang/String; TAG' }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe Android::Dex::MethodInfo do
|
93
|
+
include_context 'with sample_classes.dex'
|
94
|
+
context 'about the first direct method of the last class' do
|
95
|
+
let(:first_direct_method) { last_class.direct_methods.first }
|
96
|
+
subject { first_direct_method }
|
97
|
+
its(:name) { should eq '<init>' }
|
98
|
+
end
|
99
|
+
context 'about the first virtual method of the last class' do
|
100
|
+
let(:first_virtual_method) { last_class.virtual_methods.first }
|
101
|
+
subject { first_virtual_method }
|
102
|
+
its(:name) { should eq 'processDoWhile' }
|
103
|
+
its(:code_item) { should be_instance_of Android::Dex::DexObject::CodeItem }
|
104
|
+
describe '#parameters' do
|
105
|
+
subject { first_virtual_method.parameters }
|
106
|
+
it { should have(1).item }
|
107
|
+
it { should include('int') }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
context 'about the 12th virtual method(processTryCatch) of the last class' do
|
111
|
+
let(:a_virtual_method) { last_class.virtual_methods[12]}
|
112
|
+
subject { a_virtual_method }
|
113
|
+
its(:name) { should eq 'processTryCatch' }
|
114
|
+
describe '#code_item' do
|
115
|
+
subject { a_virtual_method.code_item }
|
116
|
+
it { should be_instance_of Android::Dex::DexObject::CodeItem }
|
117
|
+
its(:debug_info_item) { should be_instance_of Android::Dex::DexObject::DebugInfoItem }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Android::Dex do
|
4
|
+
|
5
|
+
describe ".uleb128" do
|
6
|
+
# @see http://en.wikipedia.org/wiki/LEB128
|
7
|
+
it "[0x00] should be 0" do
|
8
|
+
Android::Dex.uleb128("\x00").should == [0,1]
|
9
|
+
end
|
10
|
+
it "[0x01] should be 1" do
|
11
|
+
Android::Dex.uleb128("\x01").should == [1,1]
|
12
|
+
end
|
13
|
+
it "[0x7f] should be 127" do
|
14
|
+
Android::Dex.uleb128("\x7f").should == [127,1]
|
15
|
+
end
|
16
|
+
it "[0x80,0x7f] should be 16256" do
|
17
|
+
Android::Dex.uleb128("\x80\x7f").should == [16256,2]
|
18
|
+
end
|
19
|
+
it "[0xe5,0x8e,0x26] should be 624485" do
|
20
|
+
Android::Dex.uleb128("\xe5\x8e\x26").should == [624485,3]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".uleb128p1" do
|
25
|
+
it "[0x00] should be -1" do
|
26
|
+
Android::Dex.uleb128p1("\x00").should == [-1,1]
|
27
|
+
end
|
28
|
+
it "[0x01] should be 0" do
|
29
|
+
Android::Dex.uleb128p1("\x01").should == [0,1]
|
30
|
+
end
|
31
|
+
it "[0x7f] should be 126" do
|
32
|
+
Android::Dex.uleb128p1("\x7f").should == [126,1]
|
33
|
+
end
|
34
|
+
it "[0x80,0x7f] should be 16255" do
|
35
|
+
Android::Dex.uleb128p1("\x80\x7f").should == [16255,2]
|
36
|
+
end
|
37
|
+
it "[0xe5,0x8e,0x26] should be 624485" do
|
38
|
+
Android::Dex.uleb128("\xe5\x8e\x26").should == [624485,3]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
describe '.sleb128' do
|
42
|
+
it "[0x00] should be 0" do
|
43
|
+
Android::Dex.sleb128("\x00").should == [0,1]
|
44
|
+
end
|
45
|
+
it "[0x01] should be 1" do
|
46
|
+
Android::Dex.sleb128("\x01").should == [1,1]
|
47
|
+
end
|
48
|
+
it "[0x7f] should be -1" do
|
49
|
+
Android::Dex.sleb128("\x7f").should == [-1,1]
|
50
|
+
end
|
51
|
+
it "[0x80,0x7f] should be 127" do
|
52
|
+
Android::Dex.sleb128("\x80\x7f").should == [-128,2]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
data/spec/dex_spec.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Android::Dex do
|
4
|
+
let(:dex_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_classes.dex') }
|
5
|
+
let(:dex_bin){ File.open(dex_path, 'rb') {|f| f.read } }
|
6
|
+
let(:dex){ Android::Dex.new(dex_bin) }
|
7
|
+
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
subject { dex }
|
11
|
+
context 'with valid dex data' do
|
12
|
+
it { should be_instance_of(Android::Dex) }
|
13
|
+
end
|
14
|
+
context 'with nil data' do
|
15
|
+
let(:dex_bin) { nil }
|
16
|
+
specify { expect{ subject }.to raise_error }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#data' do
|
21
|
+
subject { dex.data }
|
22
|
+
it { should be_instance_of String }
|
23
|
+
specify { subject.encoding.should eq Encoding::ASCII_8BIT }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#strings' do
|
27
|
+
let(:num_str) { dex.header[:string_ids_size] }
|
28
|
+
subject { dex.strings }
|
29
|
+
it { should be_instance_of Array }
|
30
|
+
it 'should have string_ids_size items' do
|
31
|
+
should have(num_str).items
|
32
|
+
end
|
33
|
+
it "should be the particular string(depends on sample_classes.dex)" do
|
34
|
+
subject[0].should eq "%d"
|
35
|
+
end
|
36
|
+
it "should be the particular string(depends on sample_classes.dex)" do
|
37
|
+
subject[1].should eq "<init>"
|
38
|
+
end
|
39
|
+
it "should be the particular string(depends on sample_classes.dex)" do
|
40
|
+
subject[2].should eq "BuildConfig.java"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
describe '#inspect' do
|
44
|
+
subject { dex.inspect }
|
45
|
+
it { should match(/\A<Android::Dex.*>\Z/m) }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#classes' do
|
49
|
+
subject { dex.classes }
|
50
|
+
let(:num_classes) { dex.header[:class_defs_size] }
|
51
|
+
it{ should be_instance_of Array }
|
52
|
+
it{ should have(num_classes).items }
|
53
|
+
describe 'first item' do
|
54
|
+
subject { dex.classes.first }
|
55
|
+
it{ should be_instance_of Android::Dex::ClassInfo }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/spec/layout_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Android::Layout do
|
4
|
+
context 'with real apk sample file' do
|
5
|
+
let(:apk_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
|
6
|
+
let(:apk){ Android::Apk.new(apk_path) }
|
7
|
+
let(:layouts) { apk.layouts }
|
8
|
+
subject { layouts }
|
9
|
+
it { should be_a Hash }
|
10
|
+
it { should have_key "res/layout/main.xml" }
|
11
|
+
it { should have(1).item }
|
12
|
+
context 'about first item' do
|
13
|
+
subject { layouts['res/layout/main.xml'] }
|
14
|
+
it { should be_a Android::Layout }
|
15
|
+
describe '#path' do
|
16
|
+
it { subject.path.should eq 'res/layout/main.xml' }
|
17
|
+
end
|
18
|
+
describe '#doc' do
|
19
|
+
it { subject.doc.should be_a REXML::Document }
|
20
|
+
end
|
21
|
+
describe '#to_xml' do
|
22
|
+
it { subject.to_xml.should be_a String }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Android::Manifest do
|
4
|
+
describe Android::Manifest::Component do
|
5
|
+
describe "self.valid?" do
|
6
|
+
let(:elem) { REXML::Element.new('service') }
|
7
|
+
subject { Android::Manifest::Component.valid?(elem) }
|
8
|
+
context "with valid component element" do
|
9
|
+
it { should be_true }
|
10
|
+
end
|
11
|
+
context "with invalid component element" do
|
12
|
+
let(:elem) { REXML::Element.new('invalid-name') }
|
13
|
+
it { should be_false }
|
14
|
+
end
|
15
|
+
context "when some exception occurs in REXML::Element object" do
|
16
|
+
let(:elem) {
|
17
|
+
elem = stub(REXML::Element)
|
18
|
+
elem.stub(:name).and_raise(StandardError)
|
19
|
+
elem
|
20
|
+
}
|
21
|
+
it { should be_false }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
describe '#metas' do
|
25
|
+
subject { Android::Manifest::Component.new(elem).metas }
|
26
|
+
context 'with valid component element has 2 meta elements' do
|
27
|
+
let(:elem) {
|
28
|
+
elem = REXML::Element.new('service')
|
29
|
+
elem << REXML::Element.new('meta-data')
|
30
|
+
elem << REXML::Element.new('meta-data')
|
31
|
+
elem
|
32
|
+
}
|
33
|
+
it { should have(2).item }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
describe '#elem' do
|
37
|
+
subject { Android::Manifest::Component.new(elem).elem }
|
38
|
+
let(:elem) { REXML::Element.new('service') }
|
39
|
+
it { should eq elem }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe Android::Manifest::Meta do
|
43
|
+
let(:elem) do
|
44
|
+
attrs = { 'name' => 'meta name', 'resource' => 'res', 'value' => 'val' }
|
45
|
+
elem = stub(REXML::Element, :attributes => attrs)
|
46
|
+
elem
|
47
|
+
end
|
48
|
+
subject { Android::Manifest::Meta.new(elem) }
|
49
|
+
its(:name) { should eq 'meta name' }
|
50
|
+
its(:resource) { should eq 'res' }
|
51
|
+
its(:value) { should eq 'val' }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe Android::Manifest::IntentFilter do
|
56
|
+
describe '.parse' do
|
57
|
+
subject { Android::Manifest::IntentFilter.parse(elem) }
|
58
|
+
context 'assings "action" element' do
|
59
|
+
let(:elem) { REXML::Element.new('action') }
|
60
|
+
it { should be_instance_of Android::Manifest::IntentFilter::Action }
|
61
|
+
end
|
62
|
+
context 'assings "category" element' do
|
63
|
+
let(:elem) { REXML::Element.new('category') }
|
64
|
+
it { should be_instance_of Android::Manifest::IntentFilter::Category }
|
65
|
+
end
|
66
|
+
context 'assings "data" element' do
|
67
|
+
let(:elem) { REXML::Element.new('data') }
|
68
|
+
it { should be_instance_of Android::Manifest::IntentFilter::Data }
|
69
|
+
end
|
70
|
+
context 'assings unknown element' do
|
71
|
+
let(:elem) { REXML::Element.new('unknown') }
|
72
|
+
it { should be_nil }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "with stub AXMLParser" do
|
78
|
+
let(:dummy_xml) {
|
79
|
+
xml = REXML::Document.new
|
80
|
+
xml << REXML::Element.new('manifest')
|
81
|
+
}
|
82
|
+
let(:manifest) { Android::Manifest.new('mock data') }
|
83
|
+
|
84
|
+
before do
|
85
|
+
parser = stub(Android::AXMLParser, :parse => dummy_xml)
|
86
|
+
Android::AXMLParser.stub(:new).and_return(parser)
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#use_parmissions" do
|
90
|
+
subject { manifest.use_permissions }
|
91
|
+
context "with valid 3 parmission elements" do
|
92
|
+
before do
|
93
|
+
3.times do |i|
|
94
|
+
elem = REXML::Element.new("uses-permission")
|
95
|
+
elem.add_attribute 'name', "permission#{i}"
|
96
|
+
dummy_xml.root << elem
|
97
|
+
end
|
98
|
+
end
|
99
|
+
it { subject.should have(3).items }
|
100
|
+
it "should have permissions" do
|
101
|
+
subject.should include("permission0")
|
102
|
+
subject.should include("permission1")
|
103
|
+
subject.should include("permission2")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
context "with no parmissions" do
|
107
|
+
it { should be_empty }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "#components" do
|
112
|
+
subject { manifest.components }
|
113
|
+
context "with valid parmission element" do
|
114
|
+
before do
|
115
|
+
app = REXML::Element.new('application')
|
116
|
+
activity = REXML::Element.new('activity')
|
117
|
+
app << activity
|
118
|
+
dummy_xml.root << app
|
119
|
+
end
|
120
|
+
it "should have components" do
|
121
|
+
subject.should have(1).items
|
122
|
+
end
|
123
|
+
it "should returns Component object" do
|
124
|
+
subject[0].should be_kind_of Android::Manifest::Component
|
125
|
+
end
|
126
|
+
end
|
127
|
+
context "with no components" do
|
128
|
+
it { should be_empty }
|
129
|
+
end
|
130
|
+
context 'with text element in intent-filter element. (issue #3)' do
|
131
|
+
before do
|
132
|
+
app = REXML::Element.new('application')
|
133
|
+
activity = REXML::Element.new('activity')
|
134
|
+
intent_filter = REXML::Element.new('intent-filter')
|
135
|
+
text = REXML::Text.new('sample')
|
136
|
+
|
137
|
+
intent_filter << text
|
138
|
+
activity << intent_filter
|
139
|
+
app << activity
|
140
|
+
dummy_xml.root << app
|
141
|
+
end
|
142
|
+
it "should have components" do
|
143
|
+
subject.should have(1).items
|
144
|
+
end
|
145
|
+
it { expect { subject }.to_not raise_error }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "with real sample_AndroidManifest.xml data" do
|
151
|
+
let(:bin_xml_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample_AndroidManifest.xml') }
|
152
|
+
let(:bin_xml){ File.open(bin_xml_path, 'rb') {|f| f.read } }
|
153
|
+
let(:manifest){ Android::Manifest.new(bin_xml) }
|
154
|
+
|
155
|
+
describe "#components" do
|
156
|
+
subject { manifest.components }
|
157
|
+
it { should be_kind_of Array }
|
158
|
+
it { subject[0].should be_kind_of Android::Manifest::Component }
|
159
|
+
end
|
160
|
+
describe "#package_name" do
|
161
|
+
subject { manifest.package_name }
|
162
|
+
it { should == "example.app.sample" }
|
163
|
+
end
|
164
|
+
describe "#version_code" do
|
165
|
+
subject { manifest.version_code}
|
166
|
+
it { should == 101 }
|
167
|
+
end
|
168
|
+
describe "#version_name" do
|
169
|
+
subject { manifest.version_name}
|
170
|
+
it { should == "1.0.1-malware2" }
|
171
|
+
end
|
172
|
+
describe "#min_sdk_ver" do
|
173
|
+
subject { manifest.min_sdk_ver}
|
174
|
+
it { should == 10 }
|
175
|
+
end
|
176
|
+
describe "#label" do
|
177
|
+
subject { manifest.label }
|
178
|
+
it { should == "@0x7f040001" }
|
179
|
+
|
180
|
+
context "with real apk file" do
|
181
|
+
let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
|
182
|
+
let(:apk) { Android::Apk.new(tmp_path) }
|
183
|
+
let(:manifest){ apk.manifest }
|
184
|
+
subject { manifest.label }
|
185
|
+
it { should eq 'Sample' }
|
186
|
+
context 'when assign lang code' do
|
187
|
+
subject { manifest.label('ja') }
|
188
|
+
it { should eq 'Sample' }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
describe "#doc" do
|
193
|
+
subject { manifest.doc }
|
194
|
+
it { should be_instance_of REXML::Document }
|
195
|
+
end
|
196
|
+
describe "#to_xml" do
|
197
|
+
let(:raw_xml){ str = <<EOS
|
198
|
+
<manifest xmlns:android='http://schemas.android.com/apk/res/android' android:versionCode='101' android:versionName='1.0.1-malware2' package='example.app.sample'>
|
199
|
+
<uses-sdk android:minSdkVersion='10'/>
|
200
|
+
<uses-permission android:name='android.permission.INTERNET'/>
|
201
|
+
<uses-permission android:name='android.permission.WRITE_EXTERNAL_STORAGE'/>
|
202
|
+
<application android:label='@0x7f040001' android:icon='@0x7f020000' android:debuggable='true'>
|
203
|
+
<activity android:label='@0x7f040001' android:name='example.app.sample.SampleActivity'>
|
204
|
+
<intent-filter>
|
205
|
+
<action android:name='android.intent.action.MAIN'/>
|
206
|
+
<category android:name='android.intent.category.LAUNCHER'/>
|
207
|
+
</intent-filter>
|
208
|
+
</activity>
|
209
|
+
</application>
|
210
|
+
</manifest>
|
211
|
+
EOS
|
212
|
+
str.strip
|
213
|
+
}
|
214
|
+
|
215
|
+
subject { manifest.to_xml }
|
216
|
+
it "should return correct xml string" do
|
217
|
+
subject.should == raw_xml
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
3
|
+
|
4
|
+
describe Android::Resource do
|
5
|
+
let(:res_data) { File.read(res_path) }
|
6
|
+
let(:resource) { Android::Resource.new(res_data) }
|
7
|
+
|
8
|
+
shared_examples_for 'a valid Android::Resource' do
|
9
|
+
subject { resource }
|
10
|
+
describe '#initialize' do
|
11
|
+
context 'assigns resources.arsc data' do
|
12
|
+
it { should be_instance_of Android::Resource }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
its(:package_count) {should eq 1 }
|
16
|
+
describe '#strings' do
|
17
|
+
subject { resource.strings }
|
18
|
+
it { should have(7).items } # depends on sample_resources.arsc
|
19
|
+
it { should include 'Sample' }
|
20
|
+
it { should include 'Hello World, SampleActivity!' }
|
21
|
+
it { should include '日本語リソース' }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
context 'with sample_resources.arsc data' do
|
25
|
+
let(:res_path) { File.expand_path(File.dirname(__FILE__) + '/data/sample_resources.arsc') }
|
26
|
+
it_behaves_like 'a valid Android::Resource'
|
27
|
+
|
28
|
+
end
|
29
|
+
context 'with sample_resources_utf16.arsc data' do
|
30
|
+
let(:res_path) { File.expand_path(File.dirname(__FILE__) + '/data/sample_resources_utf16.arsc') }
|
31
|
+
it_behaves_like 'a valid Android::Resource'
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Android::Resource::ChunkHeader do
|
35
|
+
shared_examples_for 'a chunk header' do
|
36
|
+
its(:type) { should eq 20 }
|
37
|
+
its(:header_size) { should eq 244 }
|
38
|
+
its(:size) { should eq 1000 }
|
39
|
+
end
|
40
|
+
subject { Android::Resource::ChunkHeader.new(data, offset) }
|
41
|
+
context 'with no offset' do
|
42
|
+
let(:data) { "\x14\x00\xF4\x00\xE8\x03\x00\x00" } # [20, 244, 1000].pack('vvV')
|
43
|
+
let(:offset) { 0 }
|
44
|
+
it_behaves_like 'a chunk header'
|
45
|
+
end
|
46
|
+
context 'with 10byte offset' do
|
47
|
+
let(:data) { "\x00"*10 + "\x14\x00\xF4\x00\xE8\x03\x00\x00" } # [20, 244, 1000].pack('vvV')
|
48
|
+
let(:offset) { 10 }
|
49
|
+
it_behaves_like 'a chunk header'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe Android::Resource::ResStringPool do
|
54
|
+
describe '.utf8_len' do
|
55
|
+
subject { Android::Resource::ResStringPool.utf8_len(data) }
|
56
|
+
context 'assigns 0x7F' do
|
57
|
+
let(:data) { "\x7f" }
|
58
|
+
it { should eq [0x7f, 1] }
|
59
|
+
end
|
60
|
+
context 'assigns x81x01' do
|
61
|
+
let(:data) { "\x81\x01" }
|
62
|
+
it { should eq [0x0101, 2] }
|
63
|
+
end
|
64
|
+
context 'assigns xffxff' do
|
65
|
+
let(:data) { "\xff\xff" }
|
66
|
+
it { should eq [0x7fff, 2] }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
describe '#utf16_len' do
|
70
|
+
subject { Android::Resource::ResStringPool.utf16_len(data) }
|
71
|
+
context 'assigns x7fff' do
|
72
|
+
let(:data) { "\xff\x7f" }
|
73
|
+
it { should eq [0x7fff, 2] }
|
74
|
+
end
|
75
|
+
context 'assigns x8001,x0001' do
|
76
|
+
let(:data) { "\x01\x80\x01\x00" }
|
77
|
+
it { should eq [0x10001, 4] }
|
78
|
+
end
|
79
|
+
context 'assigns xffff,xffff' do
|
80
|
+
let(:data) { "\xff\xff\xff\xff" }
|
81
|
+
it 'should eq 0x7fff 0xffff' do
|
82
|
+
should eq [0x7fffffff, 4]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'with str_resources.arsc data' do
|
88
|
+
let(:res_data) { File.read(File.expand_path(File.dirname(__FILE__) + '/data/str_resources.arsc')) }
|
89
|
+
subject { resource }
|
90
|
+
describe 'about drawable resource' do
|
91
|
+
it 'hoge' do
|
92
|
+
table = resource.packages.first[1]
|
93
|
+
p table
|
94
|
+
table.type_strings.each_with_index do |type, id|
|
95
|
+
puts "[0x#{(id+1).to_s(16)}] #{type}"
|
96
|
+
end
|
97
|
+
puts "readable id:" + table.res_readable_id('@0x7f020000')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
context 'with str_resources.arsc data' do
|
102
|
+
let(:res_data) { File.read(File.expand_path(File.dirname(__FILE__) + '/data/str_resources.arsc')) }
|
103
|
+
subject { resource }
|
104
|
+
describe '#packages' do
|
105
|
+
subject {resource.packages}
|
106
|
+
it { should be_instance_of Hash}
|
107
|
+
it { subject.size.should eq 1 }
|
108
|
+
end
|
109
|
+
describe 'ResTablePackage' do
|
110
|
+
subject { resource.packages.first[1] }
|
111
|
+
it { subject.type(1).should eq 'attr' }
|
112
|
+
it { subject.type(4).should eq 'string' }
|
113
|
+
it { subject.name.should eq 'com.example.sample.ruby_apk' }
|
114
|
+
end
|
115
|
+
describe '#find' do
|
116
|
+
it '@0x7f040000 should return "sample.ruby_apk"' do
|
117
|
+
subject.find('@0x7f040000').should eq 'sample application'
|
118
|
+
end
|
119
|
+
it '@string/app_name should return "sample.ruby_apk"' do
|
120
|
+
subject.find('@string/app_name').should eq 'sample application'
|
121
|
+
end
|
122
|
+
it '@string/hello_world should return "Hello world!"' do
|
123
|
+
subject.find('@string/hello_world').should eq 'Hello world!'
|
124
|
+
end
|
125
|
+
it '@string/app_name should return "sample.ruby_apk"' do
|
126
|
+
subject.find('@string/app_name').should eq 'sample application'
|
127
|
+
end
|
128
|
+
it '@string/app_name with {:lang => "ja"} should return "サンプルアプリ"' do
|
129
|
+
subject.find('@string/app_name', :lang => 'ja').should eq 'サンプルアプリ'
|
130
|
+
end
|
131
|
+
it '@string/hello_world with {:lang => "ja"} should return nil' do
|
132
|
+
subject.find('@string/hello_world', :lang => 'ja').should be_nil
|
133
|
+
end
|
134
|
+
context 'assigns not exist string resource id' do
|
135
|
+
it { expect {subject.find('@string/not_exist') }.to raise_error Android::NotFoundError }
|
136
|
+
it { expect {subject.find('@0x7f040033') }.to raise_error Android::NotFoundError }
|
137
|
+
end
|
138
|
+
context 'assigns not string resource id' do
|
139
|
+
it { subject.find('@layout/activity_main').should be_nil }
|
140
|
+
end
|
141
|
+
context 'assigns invalid format id' do
|
142
|
+
it '"@xxyyxxyy" should raise ArgumentError' do
|
143
|
+
expect{ subject.find('@xxyyxxyy') }.to raise_error(ArgumentError)
|
144
|
+
end
|
145
|
+
it '"@0xff112233445566" should raise ArgumentError' do
|
146
|
+
expect{ subject.find('@0xff112233445566') }.to raise_error(ArgumentError)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
describe '#res_readable_id' do
|
151
|
+
it { subject.res_readable_id('@0x7f040000').should eq '@string/app_name' }
|
152
|
+
context 'assigns invalid type' do
|
153
|
+
it { expect{subject.res_readable_id('@0x7f0f0000')}.to raise_error Android::NotFoundError }
|
154
|
+
end
|
155
|
+
context 'assigns invalid key' do
|
156
|
+
it { expect{subject.res_readable_id('@0x7f040033')}.to raise_error Android::NotFoundError }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
describe '#res_hex_id' do
|
160
|
+
it { subject.res_hex_id('@string/app_name').should eq '@0x7f040000' }
|
161
|
+
context 'assigns invalid type' do
|
162
|
+
it { expect{subject.res_readable_id('@not_exist/xxxx')}.to raise_error Android::NotFoundError }
|
163
|
+
end
|
164
|
+
context 'assigns invalid key' do
|
165
|
+
it { expect{subject.res_readable_id('@string/not_exist')}.to raise_error Android::NotFoundError }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start do
|
5
|
+
add_filter "/spec"
|
6
|
+
end
|
7
|
+
require 'rspec'
|
8
|
+
require 'ruby_android'
|
9
|
+
|
10
|
+
|
11
|
+
# Requires supporting files with custom matchers and macros, etc,
|
12
|
+
# in ./support/ and its subdirectories.
|
13
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
|
17
|
+
end
|