android_parser 2.4.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,151 @@
1
+ require_relative 'dex_object'
2
+
3
+ module Android
4
+ class Dex
5
+ # class information in dex
6
+ # @!attribute [r] name
7
+ # @return [String] class name
8
+ # @!attribute [r] super_class
9
+ # @return [String] super class name
10
+ class ClassInfo
11
+ # no index flag
12
+ NO_INDEX = 0xffffffff
13
+
14
+ # @return [ClassAccessFlag]
15
+ attr_reader :access_flags
16
+ # @return [Array<FieldInfo>] static fields
17
+ attr_reader :static_fields
18
+ # @return [Array<FieldInfo>] instance fields
19
+ attr_reader :instance_fields
20
+ # @return [Array<MethodInfo>] direct methods
21
+ attr_reader :direct_methods
22
+ # @return [Array<MethodInfo>] virtual methods
23
+ attr_reader :virtual_methods
24
+
25
+ # @return [DexObject::ClassDataItem]
26
+ attr_reader :class_data
27
+ # @return [DexObject::ClassDefItem]
28
+ attr_reader :class_def
29
+
30
+ def name
31
+ @dex.type_resolve(@class_def[:class_idx])
32
+ end
33
+ def super_class
34
+ if @class_def[:superclass_idx] != NO_INDEX
35
+ @super_class = @dex.type_resolve(@class_def[:superclass_idx])
36
+ else
37
+ nil
38
+ end
39
+ end
40
+ # @param [Dex::ClassDefItem] class_def
41
+ # @param [Dex] dex dex class instance
42
+ def initialize(class_def, dex)
43
+ @class_def = class_def
44
+ @dex = dex
45
+ @access_flags = ClassAccessFlag.new(@class_def[:access_flags])
46
+ @class_data = @class_def.class_data_item
47
+ @static_fields = @instance_fields = @direct_methods = @virtual_methods = []
48
+ unless @class_data.nil?
49
+ @static_fields = cls2info(@class_data[:static_fields], FieldInfo, :field_idx_diff)
50
+ @instance_fields = cls2info(@class_data[:instance_fields], FieldInfo, :field_idx_diff)
51
+ @direct_methods = cls2info(@class_data[:direct_methods], MethodInfo, :method_idx_diff)
52
+ @virtual_methods = cls2info(@class_data[:virtual_methods], MethodInfo, :method_idx_diff)
53
+ end
54
+ end
55
+
56
+ # @return [String] class difinition
57
+ def definition
58
+ ret = "#{access_flags} class #{name}"
59
+ super_class.nil? ? ret : ret + " extends #{super_class}"
60
+ end
61
+
62
+ private
63
+ def cls2info(arr, cls, idx_key)
64
+ idx = 0
65
+ ret = []
66
+ arr.each do |item|
67
+ idx += item[idx_key]
68
+ ret << cls.new(item, idx, @dex)
69
+ end
70
+ ret
71
+ end
72
+ end
73
+
74
+ # field info object
75
+ # @!attribute [r] name
76
+ # @return [String] field name
77
+ # @!attribute [r] type
78
+ # @return [String] field type
79
+ class FieldInfo
80
+ # @return [ClassAccessFlag]
81
+ attr_reader :access_flags
82
+
83
+ def name
84
+ @dex.strings[@dex.field_ids[@field_id][:name_idx]]
85
+ end
86
+ def type
87
+ @dex.type_resolve(@dex.field_ids[@field_id][:type_idx])
88
+ end
89
+ def initialize(encoded_field, field_id, dex)
90
+ @dex = dex
91
+ @encoded_field = encoded_field
92
+ @field_id = field_id
93
+ @access_flags = ClassAccessFlag.new(encoded_field[:access_flags])
94
+ end
95
+
96
+ # @return [String] field definition
97
+ def definition
98
+ "#{@access_flags.to_s} #{type} #{name}"
99
+ end
100
+ end
101
+
102
+ # method info object
103
+ # @!attribute [r] name
104
+ # @return [String] method name
105
+ # @!attribute [r] ret_type
106
+ # @return [String] return type of the method
107
+ # @!attribute [r] parameters
108
+ # @return [Array<String>] method parameters
109
+ class MethodInfo
110
+ # @return [MethodAccessFlag]
111
+ attr_reader :access_flags
112
+
113
+ def initialize(encoded_method, method_id, dex)
114
+ @encoded_method = encoded_method
115
+ @method_id = method_id
116
+ @dex = dex
117
+ @access_flags = MethodAccessFlag.new(encoded_method[:access_flags])
118
+ end
119
+ def name
120
+ @dex.strings[@dex.method_ids[@method_id][:name_idx]]
121
+ end
122
+ def ret_type
123
+ @dex.type_resolve(proto[:return_type_idx])
124
+ end
125
+ def parameters
126
+ unless proto[:parameters_off] == 0
127
+ list = DexObject::TypeList.new(@dex.data, proto[:parameters_off])
128
+ list[:list].map { |item| @dex.type_resolve(item) }
129
+ else
130
+ []
131
+ end
132
+ end
133
+
134
+ # @return [String] method definition string
135
+ def definition
136
+ "#{access_flags.to_s} #{ret_type} #{name}(#{parameters.join(', ')});"
137
+ end
138
+
139
+ # @return [DexObject::CodeItem]
140
+ def code_item
141
+ @encoded_method.code_item
142
+ end
143
+
144
+ private
145
+ def proto
146
+ @dex.proto_ids[@dex.method_ids[@method_id][:proto_idx]]
147
+ end
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,45 @@
1
+ module Android
2
+ class Dex
3
+ class << self
4
+ # parse uleb128(unsigned integer) data
5
+ # @param [String] data target byte data
6
+ # @param [Integer] offset
7
+ # @return [Integer, Integer] parsed value and parsed byte length
8
+ # @see http://en.wikipedia.org/wiki/LEB128
9
+ def uleb128(data, offset=0)
10
+ result = 0
11
+ shift = 0
12
+ d = data[offset...data.size]
13
+ (0..4).each do |i|
14
+ byte = d.getbyte(i)
15
+ result |= ((byte & 0x7f) << shift)
16
+ return result, i+1 if ((byte & 0x80) == 0)
17
+ shift += 7
18
+ end
19
+ end
20
+ # parse uleb128 + 1 data
21
+ # @param [String] data target byte data
22
+ # @param [Integer] offset
23
+ # @return [Integer, Integer] parsed value and parsed byte length
24
+ def uleb128p1(data, offset=0)
25
+ ret, len = self.uleb128(data, offset)
26
+ return (ret - 1), len
27
+ end
28
+ # parse sleb128(signed integer) data
29
+ # @param [String] data target byte data
30
+ # @param [Integer] offset
31
+ # @return [Integer, Integer] parsed value and parsed byte length
32
+ def sleb128(data, offset=0)
33
+ result = 0
34
+ shift = 0
35
+ d = data[offset...data.size]
36
+ (0..4).each do |i|
37
+ byte = d.getbyte(i)
38
+ result |=((byte & 0x7F) << shift)
39
+ return (0 == (byte & 0x40) ? result : result - (1 << (shift+7))), i+1 if ((byte & 0x80) == 0)
40
+ shift += 7
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -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_apk'
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,350 @@
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 = ['activity', 'activity-alias', 'service', 'receiver', 'provider', 'application']
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 [String] icon id - use apk.icon_by_id(icon_id) to retrieve it's corresponding data.
28
+ attr_reader :icon_id
29
+ # @return [Array<Manifest::IntentFilters<Manifest::IntentFilter>>]
30
+ attr_reader :intent_filters
31
+ # @return [Array<Manifest::Meta>]
32
+ attr_reader :metas
33
+ # @return [REXML::Element]
34
+ attr_reader :elem
35
+
36
+
37
+ # @param [REXML::Element] elem target element
38
+ # @raise [ArgumentError] when elem is invalid.
39
+ def initialize(elem)
40
+ raise ArgumentError unless Component.valid?(elem)
41
+ @elem = elem
42
+ @type = elem.name
43
+ @name = elem.attributes['name']
44
+ @icon_id = elem.attributes['icon']
45
+
46
+ @intent_filters = []
47
+ unless elem.elements['intent-filter'].nil?
48
+ elem.each_element('intent-filter') do |filters|
49
+ intent_filters = []
50
+ filters.elements.each do |filter|
51
+ next unless IntentFilter.valid?(filter)
52
+
53
+ intent_filters << IntentFilter.parse(filter)
54
+ end
55
+ @intent_filters << intent_filters
56
+ end
57
+ end
58
+
59
+ @metas = []
60
+ elem.each_element('meta-data') do |e|
61
+ @metas << Meta.new(e)
62
+ end
63
+ end
64
+ end
65
+
66
+ class Activity < Component
67
+ # the element is valid Activity element or not
68
+ # @param [REXML::Element] elem xml element
69
+ # @return [Boolean]
70
+ def self.valid?(elem)
71
+ ['activity', 'activity-alias'].include?(elem.name.downcase)
72
+ rescue => e
73
+ false
74
+ end
75
+
76
+ def launcher_activity?
77
+ intent_filters.flatten.any? do |filter|
78
+ filter.type == 'category' && filter.name == 'android.intent.category.LAUNCHER'
79
+ end
80
+ end
81
+
82
+ # @return whether this instance is the default main launcher activity.
83
+ def default_launcher_activity?
84
+ intent_filters.any? do |intent_filter|
85
+ intent_filter.any? { |f| f.type == 'category' && f.name == 'android.intent.category.LAUNCHER' } &&
86
+ intent_filter.any? { |f| f.type == 'category' && f.name == 'android.intent.category.DEFAULT' }
87
+ end
88
+ end
89
+ end
90
+
91
+ class ActivityAlias < Activity
92
+ # @return [String] target activity name
93
+ attr_reader :target_activity
94
+
95
+ # @param [REXML::Element] elem target element
96
+ # @raise [ArgumentError] when elem is invalid.
97
+ def initialize(elem)
98
+ super
99
+ @target_activity = elem.attributes['targetActivity']
100
+ end
101
+ end
102
+
103
+ class Service < Component
104
+ end
105
+
106
+ class Receiver < Component
107
+ end
108
+
109
+ class Provider < Component
110
+ end
111
+
112
+ class Application < Component
113
+ def self.valid?(elem)
114
+ elem&.name == 'application'
115
+ end
116
+ end
117
+
118
+ # intent-filter element in components
119
+ module IntentFilter
120
+ # filter types
121
+ TYPES = ['action', 'category', 'data']
122
+
123
+ # the element is valid IntentFilter element or not
124
+ # @param [REXML::Element] elem xml element
125
+ # @return [Boolean]
126
+ def self.valid?(elem)
127
+ TYPES.include?(elem.name.downcase)
128
+ rescue => e
129
+ false
130
+ end
131
+
132
+ # parse inside of intent-filter element
133
+ # @param [REXML::Element] elem target element
134
+ # @return [IntentFilter::Action, IntentFilter::Category, IntentFilter::Data]
135
+ # intent-filter element
136
+ def self.parse(elem)
137
+ case elem.name
138
+ when 'action'
139
+ Action.new(elem)
140
+ when 'category'
141
+ Category.new(elem)
142
+ when 'data'
143
+ Data.new(elem)
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ # intent-filter action class
150
+ class Action
151
+ # @return [String] action name of intent-filter
152
+ attr_reader :name
153
+ # @return [String] action type of intent-filter
154
+ attr_reader :type
155
+
156
+ def initialize(elem)
157
+ @type = 'action'
158
+ @name = elem.attributes['name']
159
+ end
160
+ end
161
+
162
+ # intent-filter category class
163
+ class Category
164
+ # @return [String] category name of intent-filter
165
+ attr_reader :name
166
+ # @return [String] category type of intent-filter
167
+ attr_reader :type
168
+
169
+ def initialize(elem)
170
+ @type = 'category'
171
+ @name = elem.attributes['name']
172
+ end
173
+ end
174
+
175
+ # intent-filter data class
176
+ class Data
177
+ # @return [String]
178
+ attr_reader :type
179
+ # @return [String]
180
+ attr_reader :host
181
+ # @return [String]
182
+ attr_reader :mime_type
183
+ # @return [String]
184
+ attr_reader :path
185
+ # @return [String]
186
+ attr_reader :path_pattern
187
+ # @return [String]
188
+ attr_reader :path_prefix
189
+ # @return [String]
190
+ attr_reader :port
191
+ # @return [String]
192
+ attr_reader :scheme
193
+
194
+ def initialize(elem)
195
+ @type = 'data'
196
+ @host = elem.attributes['host']
197
+ @mime_type = elem.attributes['mimeType']
198
+ @path = elem.attributes['path']
199
+ @path_pattern = elem.attributes['pathPattern']
200
+ @path_prefix = elem.attributes['pathPrefix']
201
+ @port = elem.attributes['port']
202
+ @scheme = elem.attributes['scheme']
203
+ end
204
+ end
205
+ end
206
+
207
+ # meta information class
208
+ class Meta
209
+ # @return [String]
210
+ attr_reader :name
211
+ # @return [String]
212
+ attr_reader :resource
213
+ # @return [String]
214
+ attr_reader :value
215
+ def initialize(elem)
216
+ @name = elem.attributes['name']
217
+ @resource = elem.attributes['resource']
218
+ @value = elem.attributes['value']
219
+ end
220
+ end
221
+
222
+ #################################
223
+ # Manifest class definitions
224
+ #################################
225
+ #
226
+ # @return [REXML::Document] manifest xml
227
+ attr_reader :doc
228
+
229
+ # @param [String] data binary data of AndroidManifest.xml
230
+ def initialize(data, rsc=nil)
231
+ parser = AXMLParser.new(data)
232
+ @doc = parser.parse
233
+ @rsc = rsc
234
+ end
235
+
236
+ # used permission array
237
+ # @return [Array<String>] permission names
238
+ # @note return empty array when the manifest includes no use-parmission element
239
+ def use_permissions
240
+ perms = []
241
+ @doc.each_element('/manifest/uses-permission') do |elem|
242
+ perms << elem.attributes['name']
243
+ end
244
+ perms.uniq
245
+ end
246
+
247
+ # Returns the manifest's application element or nil, if there isn't any.
248
+ # @return [Android::Manifest::Application] the manifest's application element
249
+ def application
250
+ element = @doc.elements['//application']
251
+ Application.new(element) if Application.valid?(element)
252
+ end
253
+
254
+ # @return [Array<Android::Manifest::Component>] all components in apk
255
+ # @note return empty array when the manifest include no components
256
+ def components
257
+ components = []
258
+ unless @doc.elements['/manifest/application'].nil?
259
+ @doc.elements['/manifest/application'].each do |elem|
260
+ components << Component.new(elem) if Component.valid?(elem)
261
+ end
262
+ end
263
+ components
264
+ end
265
+
266
+ # @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities in the apk
267
+ # @note return empty array when the manifest include no activities
268
+ def activities
269
+ activities = []
270
+ unless @doc.elements['/manifest/application'].nil?
271
+ @doc.elements['/manifest/application'].each do |elem|
272
+ next unless Activity.valid?(elem)
273
+
274
+ activities << (elem.name == 'activity-alias' ? ActivityAlias.new(elem) : Activity.new(elem))
275
+ end
276
+ end
277
+ activities
278
+ end
279
+
280
+ # @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities that are launchers in the apk
281
+ # @note return empty array when the manifest include no activities
282
+ def launcher_activities
283
+ activities.select(&:launcher_activity?)
284
+ end
285
+
286
+ # application package name
287
+ # @return [String]
288
+ def package_name
289
+ @doc.root.attributes['package']
290
+ end
291
+
292
+ # application version code
293
+ # @return [Integer]
294
+ def version_code
295
+ @doc.root.attributes['versionCode'].to_i
296
+ end
297
+
298
+ # application version name
299
+ # @return [String]
300
+ def version_name(lang=nil)
301
+ vername = @doc.root.attributes['versionName']
302
+ unless @rsc.nil?
303
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ vername
304
+ opts = {}
305
+ opts[:lang] = lang unless lang.nil?
306
+ vername = @rsc.find(vername, opts)
307
+ end
308
+ end
309
+ vername
310
+ end
311
+
312
+ # @return [Integer] minSdkVersion in uses element
313
+ def min_sdk_ver
314
+ @doc.elements['/manifest/uses-sdk'].attributes['minSdkVersion'].to_i
315
+ end
316
+
317
+ # application label
318
+ # @param [String] lang language code like 'ja', 'cn', ...
319
+ # @return [String] application label string(if resouce is provided), or label resource id
320
+ # @return [nil] when label is not found
321
+ # @since 0.5.1
322
+ def label(lang=nil)
323
+ label = @doc.elements['/manifest/application'].attributes['label']
324
+ if label.nil?
325
+ # application element has no label attributes.
326
+ # so looking for activites that has label attribute.
327
+ activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
328
+ label = activities.nil? ? nil : activities.first.attributes['label']
329
+ end
330
+ unless @rsc.nil?
331
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ label
332
+ opts = {}
333
+ opts[:lang] = lang unless lang.nil?
334
+ label = @rsc.find(label, opts)
335
+ end
336
+ end
337
+ label
338
+ end
339
+
340
+ # return xml as string format
341
+ # @param [Integer] indent size(bytes)
342
+ # @return [String] raw xml string
343
+ def to_xml(indent=4)
344
+ xml =''
345
+ formatter = REXML::Formatters::Pretty.new(indent)
346
+ formatter.write(@doc.root, xml)
347
+ xml
348
+ end
349
+ end
350
+ end