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
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
|