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
data/lib/android/dex.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative 'dex/dex_object'
|
2
|
+
require_relative 'dex/info'
|
3
|
+
require_relative 'dex/access_flag'
|
4
|
+
require_relative 'dex/utils'
|
5
|
+
|
6
|
+
module Android
|
7
|
+
# parsed dex object
|
8
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
9
|
+
# @attr_reader strings [Array<String>] strings in dex file.
|
10
|
+
class Dex
|
11
|
+
# @return [Dex::Header] dex header information
|
12
|
+
attr_reader :header
|
13
|
+
alias :h :header
|
14
|
+
|
15
|
+
# @return [String] dex binary data
|
16
|
+
attr_reader :data
|
17
|
+
# @return [Array<Dex::ClassInfo>] array of class information
|
18
|
+
attr_reader :classes
|
19
|
+
|
20
|
+
attr_reader :field_ids, :method_ids, :proto_ids
|
21
|
+
# @param [String] data dex binary data
|
22
|
+
def initialize(data)
|
23
|
+
@data = data
|
24
|
+
@data.force_encoding(Encoding::ASCII_8BIT)
|
25
|
+
@classes = []
|
26
|
+
parse()
|
27
|
+
end
|
28
|
+
|
29
|
+
def strings
|
30
|
+
@strings ||= @string_data_items.map{|item| item.to_s }
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"<Android::Dex @classes => #{@classes.size}, datasize => #{@data.size}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# @private
|
39
|
+
TYPE_DESCRIPTOR = {
|
40
|
+
'V' => 'void',
|
41
|
+
'Z' => 'boolean',
|
42
|
+
'B' => 'byte',
|
43
|
+
'S' => 'short',
|
44
|
+
'C' => 'short',
|
45
|
+
'I' => 'int',
|
46
|
+
'J' => 'long',
|
47
|
+
'F' => 'float',
|
48
|
+
'D' => 'double'
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
def type_resolve(typeid)
|
53
|
+
type = strings[@type_ids[typeid]]
|
54
|
+
if type.start_with? '['
|
55
|
+
type = type[1..type.size]
|
56
|
+
return TYPE_DESCRIPTOR.fetch(type, type) + "[]" # TODO: recursive
|
57
|
+
else
|
58
|
+
return TYPE_DESCRIPTOR.fetch(type, type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
private
|
64
|
+
def parse
|
65
|
+
@header = DexObject::Header.new(@data)
|
66
|
+
@map_list = DexObject::MapList.new(@data, h[:map_off])
|
67
|
+
|
68
|
+
# parse strings
|
69
|
+
@string_ids = DexObject::StringIdItem.new(@data, h[:string_ids_off], h[:string_ids_size])
|
70
|
+
@string_data_items = []
|
71
|
+
@string_ids[:string_data_off].each { |off| @string_data_items << DexObject::StringDataItem.new(@data, off) }
|
72
|
+
|
73
|
+
@type_ids = DexObject::TypeIdItem.new(@data, h[:type_ids_off], h[:type_ids_size])
|
74
|
+
@proto_ids = ids_list_array(DexObject::ProtoIdItem, h[:proto_ids_off], h[:proto_ids_size])
|
75
|
+
@field_ids = ids_list_array(DexObject::FieldIdItem, h[:field_ids_off], h[:field_ids_size])
|
76
|
+
@method_ids = ids_list_array(DexObject::MethodIdItem, h[:method_ids_off], h[:method_ids_size])
|
77
|
+
@class_defs = ids_list_array(DexObject::ClassDefItem, h[:class_defs_off], h[:class_defs_size])
|
78
|
+
|
79
|
+
@classes = []
|
80
|
+
@class_defs.each do |cls_def|
|
81
|
+
@classes << ClassInfo.new(cls_def, self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ids_list_array(cls, offset, size)
|
86
|
+
ret_array = []
|
87
|
+
size.times { |i| ret_array << cls.new(@data, offset + cls.size * i) }
|
88
|
+
ret_array
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'ruby_android'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
module Android
|
6
|
+
class Layout
|
7
|
+
# @return [Hash] { path => Layout }
|
8
|
+
def self.collect_layouts(apk)
|
9
|
+
targets = apk.find {|name, data| name =~ /^res\/layout\/*/ }
|
10
|
+
ret = {}
|
11
|
+
targets.each do |path|
|
12
|
+
data = apk.file(path)
|
13
|
+
data.force_encoding(Encoding::ASCII_8BIT)
|
14
|
+
ret[path] = nil
|
15
|
+
begin
|
16
|
+
ret[path] = Layout.new(data, path) if AXMLParser.axml?(data)
|
17
|
+
rescue => e
|
18
|
+
$stderr.puts e
|
19
|
+
end
|
20
|
+
end
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] layout file path
|
25
|
+
attr_reader :path
|
26
|
+
# @return [REXML::Document] xml document object
|
27
|
+
attr_reader :doc
|
28
|
+
|
29
|
+
def initialize(data, path=nil)
|
30
|
+
@data = data
|
31
|
+
@path = path
|
32
|
+
@doc = AXMLParser.new(data).parse
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] xml string
|
36
|
+
def to_xml(indent=4)
|
37
|
+
xml = ''
|
38
|
+
formatter = REXML::Formatters::Pretty.new(indent)
|
39
|
+
formatter.write(@doc.root, xml)
|
40
|
+
xml
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
module Android
|
4
|
+
# parsed AndroidManifest.xml class
|
5
|
+
# @see http://developer.android.com/guide/topics/manifest/manifest-intro.html
|
6
|
+
class Manifest
|
7
|
+
APPLICATION_TAG = '/manifest/application'
|
8
|
+
|
9
|
+
# <activity>, <service>, <receiver> or <provider> element in <application> element of the manifest file.
|
10
|
+
class Component
|
11
|
+
# component types
|
12
|
+
TYPES = ['service', 'activity', 'receiver', 'provider']
|
13
|
+
|
14
|
+
# the element is valid Component element or not
|
15
|
+
# @param [REXML::Element] elem xml element
|
16
|
+
# @return [Boolean]
|
17
|
+
def self.valid?(elem)
|
18
|
+
TYPES.include?(elem.name.downcase)
|
19
|
+
rescue => e
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] type string in TYPES
|
24
|
+
attr_reader :type
|
25
|
+
# @return [String] component name
|
26
|
+
attr_reader :name
|
27
|
+
# @return [Array<Manifest::IntentFilter>]
|
28
|
+
attr_reader :intent_filters
|
29
|
+
# @return [Array<Manifest::Meta>]
|
30
|
+
attr_reader :metas
|
31
|
+
# @return [REXML::Element]
|
32
|
+
attr_reader :elem
|
33
|
+
|
34
|
+
|
35
|
+
# @param [REXML::Element] elem target element
|
36
|
+
# @raise [ArgumentError] when elem is invalid.
|
37
|
+
def initialize(elem)
|
38
|
+
raise ArgumentError unless Component.valid?(elem)
|
39
|
+
@elem = elem
|
40
|
+
@type = elem.name
|
41
|
+
@name = elem.attributes['name']
|
42
|
+
@intent_filters = []
|
43
|
+
unless elem.elements['intent-filter'].nil?
|
44
|
+
elem.elements['intent-filter'].each do |e|
|
45
|
+
next unless e.instance_of? REXML::Element
|
46
|
+
@intent_filters << IntentFilter.parse(e)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@metas = []
|
50
|
+
elem.each_element('meta-data') do |e|
|
51
|
+
@metas << Meta.new(e)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# intent-filter element in components
|
57
|
+
module IntentFilter
|
58
|
+
# parse inside of intent-filter element
|
59
|
+
# @param [REXML::Element] elem target element
|
60
|
+
# @return [IntentFilter::Action, IntentFilter::Category, IntentFilter::Data]
|
61
|
+
# intent-filter element
|
62
|
+
def self.parse(elem)
|
63
|
+
case elem.name
|
64
|
+
when 'action'
|
65
|
+
Action.new(elem)
|
66
|
+
when 'category'
|
67
|
+
Category.new(elem)
|
68
|
+
when 'data'
|
69
|
+
Data.new(elem)
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# intent-filter action class
|
76
|
+
class Action
|
77
|
+
# @return [String] action name of intent-filter
|
78
|
+
attr_reader :name
|
79
|
+
# @return [String] action type of intent-filter
|
80
|
+
attr_reader :type
|
81
|
+
|
82
|
+
def initialize(elem)
|
83
|
+
@type = 'action'
|
84
|
+
@name = elem.attributes['name']
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# intent-filter category class
|
89
|
+
class Category
|
90
|
+
# @return [String] category name of intent-filter
|
91
|
+
attr_reader :name
|
92
|
+
# @return [String] category type of intent-filter
|
93
|
+
attr_reader :type
|
94
|
+
|
95
|
+
def initialize(elem)
|
96
|
+
@type = 'category'
|
97
|
+
@name = elem.attributes['name']
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# intent-filter data class
|
102
|
+
class Data
|
103
|
+
# @return [String]
|
104
|
+
attr_reader :type
|
105
|
+
# @return [String]
|
106
|
+
attr_reader :host
|
107
|
+
# @return [String]
|
108
|
+
attr_reader :mime_type
|
109
|
+
# @return [String]
|
110
|
+
attr_reader :path
|
111
|
+
# @return [String]
|
112
|
+
attr_reader :path_pattern
|
113
|
+
# @return [String]
|
114
|
+
attr_reader :path_prefix
|
115
|
+
# @return [String]
|
116
|
+
attr_reader :port
|
117
|
+
# @return [String]
|
118
|
+
attr_reader :scheme
|
119
|
+
|
120
|
+
def initialize(elem)
|
121
|
+
@type = 'data'
|
122
|
+
@host = elem.attributes['host']
|
123
|
+
@mime_type = elem.attributes['mimeType']
|
124
|
+
@path = elem.attributes['path']
|
125
|
+
@path_pattern = elem.attributes['pathPattern']
|
126
|
+
@path_prefix = elem.attributes['pathPrefix']
|
127
|
+
@port = elem.attributes['port']
|
128
|
+
@scheme = elem.attributes['scheme']
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# meta information class
|
134
|
+
class Meta
|
135
|
+
# @return [String]
|
136
|
+
attr_reader :name
|
137
|
+
# @return [String]
|
138
|
+
attr_reader :resource
|
139
|
+
# @return [String]
|
140
|
+
attr_reader :value
|
141
|
+
def initialize(elem)
|
142
|
+
@name = elem.attributes['name']
|
143
|
+
@resource = elem.attributes['resource']
|
144
|
+
@value = elem.attributes['value']
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
#################################
|
149
|
+
# Manifest class definitions
|
150
|
+
#################################
|
151
|
+
#
|
152
|
+
# @return [REXML::Document] manifest xml
|
153
|
+
attr_reader :doc
|
154
|
+
|
155
|
+
# @param [String] data binary data of AndroidManifest.xml
|
156
|
+
def initialize(data, rsc=nil)
|
157
|
+
parser = AXMLParser.new(data)
|
158
|
+
@doc = parser.parse
|
159
|
+
@rsc = rsc
|
160
|
+
end
|
161
|
+
|
162
|
+
# used permission array
|
163
|
+
# @return [Array<String>] permission names
|
164
|
+
# @note return empty array when the manifest includes no use-parmission element
|
165
|
+
def use_permissions
|
166
|
+
perms = []
|
167
|
+
@doc.each_element('/manifest/uses-permission') do |elem|
|
168
|
+
perms << elem.attributes['name']
|
169
|
+
end
|
170
|
+
perms.uniq
|
171
|
+
end
|
172
|
+
|
173
|
+
# @return [Array<Android::Manifest::Component>] all components in apk
|
174
|
+
# @note return empty array when the manifest include no components
|
175
|
+
def components
|
176
|
+
components = []
|
177
|
+
unless @doc.elements['/manifest/application'].nil?
|
178
|
+
@doc.elements['/manifest/application'].each do |elem|
|
179
|
+
components << Component.new(elem) if Component.valid?(elem)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
components
|
183
|
+
end
|
184
|
+
|
185
|
+
# application package name
|
186
|
+
# @return [String]
|
187
|
+
def package_name
|
188
|
+
@doc.root.attributes['package']
|
189
|
+
end
|
190
|
+
|
191
|
+
# application version code
|
192
|
+
# @return [Integer]
|
193
|
+
def version_code
|
194
|
+
@doc.root.attributes['versionCode'].to_i
|
195
|
+
end
|
196
|
+
|
197
|
+
# application version name
|
198
|
+
# @return [String]
|
199
|
+
def version_name(lang=nil)
|
200
|
+
vername = @doc.root.attributes['versionName']
|
201
|
+
unless @rsc.nil?
|
202
|
+
if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ vername
|
203
|
+
opts = {}
|
204
|
+
opts[:lang] = lang unless lang.nil?
|
205
|
+
vername = @rsc.find(vername, opts)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
vername
|
209
|
+
end
|
210
|
+
|
211
|
+
# @return [Integer] minSdkVersion in uses element
|
212
|
+
def min_sdk_ver
|
213
|
+
@doc.elements['/manifest/uses-sdk'].attributes['minSdkVersion'].to_i
|
214
|
+
end
|
215
|
+
|
216
|
+
# application label
|
217
|
+
# @param [String] lang language code like 'ja', 'cn', ...
|
218
|
+
# @return [String] application label string(if resouce is provided), or label resource id
|
219
|
+
# @return [nil] when label is not found
|
220
|
+
# @since 0.5.1
|
221
|
+
def label(lang=nil)
|
222
|
+
label = @doc.elements['/manifest/application'].attributes['label']
|
223
|
+
if label.nil?
|
224
|
+
# application element has no label attributes.
|
225
|
+
# so looking for activites that has label attribute.
|
226
|
+
activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
|
227
|
+
label = activities.nil? ? nil : activities.first.attributes['label']
|
228
|
+
end
|
229
|
+
unless @rsc.nil?
|
230
|
+
if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ label
|
231
|
+
opts = {}
|
232
|
+
opts[:lang] = lang unless lang.nil?
|
233
|
+
label = @rsc.find(label, opts)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
label
|
237
|
+
end
|
238
|
+
|
239
|
+
# return xml as string format
|
240
|
+
# @param [Integer] indent size(bytes)
|
241
|
+
# @return [String] raw xml string
|
242
|
+
def to_xml(indent=4)
|
243
|
+
xml =''
|
244
|
+
formatter = REXML::Formatters::Pretty.new(indent)
|
245
|
+
formatter.write(@doc.root, xml)
|
246
|
+
xml
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|