ruby_android 0.0.2 → 0.7.2
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/.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
|