android_parser 2.4.1 → 2.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Android
3
4
  class Dex
@@ -16,7 +17,7 @@ module Android
16
17
  parse()
17
18
  @size = @parsing_off
18
19
  end
19
-
20
+
20
21
  # returns symbol keys
21
22
  # @return [Array<Symbol>] header key
22
23
  def symbols
@@ -31,7 +32,7 @@ module Android
31
32
 
32
33
  # @return [String]
33
34
  def inspect
34
- str = "<#{self.class}\n"
35
+ str = "<#{self.class}\n".dup
35
36
  @params.each do |key,val|
36
37
  str.concat " #{key}: #{val}\n"
37
38
  end
@@ -82,7 +83,7 @@ module Android
82
83
  value
83
84
  end
84
85
  # read various values from data buffer as array
85
- # @param [Symbol] type
86
+ # @param [Symbol] type
86
87
  # @param [Integer] size num of data
87
88
  # @return [Array] value array
88
89
  def read_value_array(type, size)
@@ -263,7 +264,7 @@ module Android
263
264
  # @return bytes
264
265
  # @note this method for DexObject#read_class_array (private method)
265
266
  def self.size
266
- 2 * 2 + 4
267
+ 2 * 2 + 4
267
268
  end
268
269
  private
269
270
  def parse
@@ -280,7 +281,7 @@ module Android
280
281
  # @return bytes
281
282
  # @note this method for DexObject#read_class_array (private method)
282
283
  def self.size
283
- 2 * 2 + 4
284
+ 2 * 2 + 4
284
285
  end
285
286
  def parse
286
287
  @params[:class_idx] = read_value(:ushort)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'dex_object'
2
4
 
3
5
  module Android
@@ -32,7 +34,7 @@ module Android
32
34
  end
33
35
  def super_class
34
36
  if @class_def[:superclass_idx] != NO_INDEX
35
- @super_class = @dex.type_resolve(@class_def[:superclass_idx])
37
+ @super_class = @dex.type_resolve(@class_def[:superclass_idx])
36
38
  else
37
39
  nil
38
40
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Android
2
4
  class Dex
3
5
  class << self
4
6
  # parse uleb128(unsigned integer) data
5
7
  # @param [String] data target byte data
6
- # @param [Integer] offset
8
+ # @param [Integer] offset
7
9
  # @return [Integer, Integer] parsed value and parsed byte length
8
10
  # @see http://en.wikipedia.org/wiki/LEB128
9
11
  def uleb128(data, offset=0)
@@ -19,7 +21,7 @@ module Android
19
21
  end
20
22
  # parse uleb128 + 1 data
21
23
  # @param [String] data target byte data
22
- # @param [Integer] offset
24
+ # @param [Integer] offset
23
25
  # @return [Integer, Integer] parsed value and parsed byte length
24
26
  def uleb128p1(data, offset=0)
25
27
  ret, len = self.uleb128(data, offset)
@@ -27,7 +29,7 @@ module Android
27
29
  end
28
30
  # parse sleb128(signed integer) data
29
31
  # @param [String] data target byte data
30
- # @param [Integer] offset
32
+ # @param [Integer] offset
31
33
  # @return [Integer, Integer] parsed value and parsed byte length
32
34
  def sleb128(data, offset=0)
33
35
  result = 0
data/lib/android/dex.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'dex/dex_object'
2
4
  require_relative 'dex/info'
3
5
  require_relative 'dex/access_flag'
@@ -36,7 +38,7 @@ module Android
36
38
 
37
39
 
38
40
  # @private
39
- TYPE_DESCRIPTOR = {
41
+ TYPE_DESCRIPTOR = {
40
42
  'V' => 'void',
41
43
  'Z' => 'boolean',
42
44
  'B' => 'byte',
@@ -1,5 +1,6 @@
1
- # encoding: utf-8
2
- require 'ruby_apk'
1
+ # frozen_string_literal: true
2
+
3
+ require 'android_parser'
3
4
  require 'rexml/document'
4
5
 
5
6
  module Android
@@ -8,7 +9,7 @@ module Android
8
9
  def self.collect_layouts(apk)
9
10
  targets = apk.find {|name, data| name =~ /^res\/layout\/*/ }
10
11
  ret = {}
11
- targets.each do |path|
12
+ targets.each do |path|
12
13
  data = apk.file(path)
13
14
  data.force_encoding(Encoding::ASCII_8BIT)
14
15
  ret[path] = nil
@@ -34,7 +35,7 @@ module Android
34
35
 
35
36
  # @return [String] xml string
36
37
  def to_xml(indent=4)
37
- xml = ''
38
+ xml = ''.dup
38
39
  formatter = REXML::Formatters::Pretty.new(indent)
39
40
  formatter.write(@doc.root, xml)
40
41
  xml
@@ -1,4 +1,5 @@
1
1
  require 'rexml/document'
2
+ require 'uri'
2
3
 
3
4
  module Android
4
5
  # parsed AndroidManifest.xml class
@@ -33,33 +34,46 @@ module Android
33
34
  # @return [REXML::Element]
34
35
  attr_reader :elem
35
36
 
36
-
37
37
  # @param [REXML::Element] elem target element
38
38
  # @raise [ArgumentError] when elem is invalid.
39
39
  def initialize(elem)
40
40
  raise ArgumentError unless Component.valid?(elem)
41
+
41
42
  @elem = elem
42
43
  @type = elem.name
43
44
  @name = elem.attributes['name']
44
45
  @icon_id = elem.attributes['icon']
45
46
 
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)
47
+ @intent_filters = parse_intent_filters
48
+ @metas = parse_metas
49
+ end
52
50
 
53
- intent_filters << IntentFilter.parse(filter)
54
- end
55
- @intent_filters << intent_filters
56
- end
51
+ private
52
+
53
+ def parse_intent_filters
54
+ intent_filters = []
55
+ return intent_filters if @elem.elements['intent-filter'].nil?
56
+
57
+ @elem.each_element('intent-filter') do |filter|
58
+ next if filter&.elements&.empty?
59
+ next unless IntentFilter.valid?(filter)
60
+
61
+ intent_filter = IntentFilter.new(filter)
62
+ intent_filters << intent_filter unless intent_filter.empty?
57
63
  end
58
64
 
59
- @metas = []
60
- elem.each_element('meta-data') do |e|
61
- @metas << Meta.new(e)
65
+ intent_filters
66
+ end
67
+
68
+ def parse_metas
69
+ metas = []
70
+ return metas if @elem.elements['meta-data'].nil?
71
+
72
+ @elem.each_element('meta-data') do |e|
73
+ metas << Meta.new(e)
62
74
  end
75
+
76
+ metas
63
77
  end
64
78
  end
65
79
 
@@ -73,17 +87,18 @@ module Android
73
87
  false
74
88
  end
75
89
 
90
+ # @return whether this instance is the launcher activity.
76
91
  def launcher_activity?
77
- intent_filters.flatten.any? do |filter|
78
- filter.type == 'category' && filter.name == 'android.intent.category.LAUNCHER'
92
+ intent_filters.any? do |intent_filter|
93
+ intent_filter.exist?('android.intent.category.LAUNCHER')
79
94
  end
80
95
  end
81
96
 
82
97
  # @return whether this instance is the default main launcher activity.
83
98
  def default_launcher_activity?
84
99
  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' }
100
+ intent_filter.exist?('android.intent.category.LAUNCHER') &&
101
+ intent_filter.exist?('android.intent.category.DEFAULT')
87
102
  end
88
103
  end
89
104
  end
@@ -116,41 +131,129 @@ module Android
116
131
  end
117
132
 
118
133
  # intent-filter element in components
119
- module IntentFilter
120
- # filter types
134
+ class IntentFilter
135
+ # filter types (action is required, category and data are optional)
121
136
  TYPES = ['action', 'category', 'data']
122
137
 
138
+ # browsable of category
139
+ CATEGORY_BROWSABLE = 'android.intent.category.BROWSABLE'
140
+
123
141
  # the element is valid IntentFilter element or not
124
142
  # @param [REXML::Element] elem xml element
125
143
  # @return [Boolean]
126
- def self.valid?(elem)
127
- TYPES.include?(elem.name.downcase)
144
+ def self.valid?(filter)
145
+ filter&.elements&.any? do |elem|
146
+ TYPES.include?(elem&.name&.downcase)
147
+ end
128
148
  rescue => e
129
149
  false
130
150
  end
131
151
 
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
152
+ # @return [IntentFilter::Action] intent-filter actions
153
+ attr_reader :actions
154
+ # @return [IntentFilter::Category] intent-filter categories
155
+ attr_reader :categories
156
+ # @return [IntentFilter::Data] intent-filter data
157
+ attr_reader :data
158
+ # @return [IntentFilter::Data] intent-filter data
159
+ attr_reader :activity
160
+
161
+ def initialize(filter)
162
+ @activity = filter.parent
163
+ @actions = []
164
+ @categories = []
165
+ @data = []
166
+
167
+ filter.elements.each do |element|
168
+ type = element.name.downcase
169
+ next unless TYPES.include?(type)
170
+
171
+ case type
172
+ when 'action'
173
+ @actions << Action.new(element)
174
+ when 'category'
175
+ @categories << Category.new(element)
176
+ when 'data'
177
+ @data << Data.new(element)
178
+ end
179
+ end
180
+ end
181
+
182
+ # Returns true if self contains no [IntentFilter::Action] elements.
183
+ # @return [Boolean]
184
+ def empty?
185
+ @actions.empty?
186
+ end
187
+
188
+ def exist?(name, type: nil)
189
+ if type.to_s.empty? && !name.start_with?('android.intent.')
190
+ raise 'Fill type or use correct name'
146
191
  end
192
+
193
+ type ||= name.split('.')[2]
194
+ raise 'Not found type' unless TYPES.include?(type)
195
+
196
+ method_name = case type
197
+ when 'action'
198
+ :actions
199
+ when 'category'
200
+ :categories
201
+ when 'data'
202
+ :data
203
+ end
204
+
205
+ values = send(method_name).select { |e| e.name == name }
206
+ values.empty? ? false : values #(values.size == 1 ? values.first : values)
207
+ end
208
+
209
+ # @return [Array<String>] all data elements
210
+ # @note return empty array when the manifest include no http or https scheme of data
211
+ # @since 2.5.0
212
+ def deep_links
213
+ return unless deep_links?
214
+
215
+ data.select {|d| !d.host.nil? }
216
+ .map { |d| d.host }
217
+ .uniq
218
+ end
219
+
220
+ # the deep links exists with http or https in data element or not
221
+ # @return [Boolean]
222
+ # @since 2.5.0
223
+ def deep_links?
224
+ browsable? && data.any? { |d| ['http', 'https'].include?(d.scheme) }
225
+ end
226
+
227
+ # @return [Array<String>] all data elements
228
+ # @note return empty array when the manifest not include http or https scheme(s) of data
229
+ # @since 2.5.0
230
+ def schemes
231
+ return unless schemes?
232
+
233
+ data.select {|d| !d.scheme.nil? && !['http', 'https'].include?(d.scheme) }
234
+ .map { |d| d.scheme }
235
+ .uniq
236
+ end
237
+
238
+ # the deep links exists with non-http or non-https in data element or not
239
+ # @return [Boolean]
240
+ # @since 2.5.0
241
+ def schemes?
242
+ browsable? && data.any? { |d| !['http', 'https'].include?(d.scheme) }
243
+ end
244
+
245
+ # the browsable category vaild or not
246
+ # @return [Boolean]
247
+ # @since 2.5.0
248
+ def browsable?
249
+ exist?(CATEGORY_BROWSABLE)
147
250
  end
148
251
 
149
252
  # intent-filter action class
150
253
  class Action
151
- # @return [String] action name of intent-filter
254
+ # @return [String] action name of intent-filter
152
255
  attr_reader :name
153
- # @return [String] action type of intent-filter
256
+ # @return [String] action type of intent-filter
154
257
  attr_reader :type
155
258
 
156
259
  def initialize(elem)
@@ -161,9 +264,9 @@ module Android
161
264
 
162
265
  # intent-filter category class
163
266
  class Category
164
- # @return [String] category name of intent-filter
267
+ # @return [String] category name of intent-filter
165
268
  attr_reader :name
166
- # @return [String] category type of intent-filter
269
+ # @return [String] category type of intent-filter
167
270
  attr_reader :type
168
271
 
169
272
  def initialize(elem)
@@ -237,11 +340,15 @@ module Android
237
340
  # @return [Array<String>] permission names
238
341
  # @note return empty array when the manifest includes no use-parmission element
239
342
  def use_permissions
240
- perms = []
241
- @doc.each_element('/manifest/uses-permission') do |elem|
242
- perms << elem.attributes['name']
243
- end
244
- perms.uniq
343
+ manifest_values('/manifest/uses-permission')
344
+ end
345
+
346
+ # used features array
347
+ # @return [Array<String>] features names
348
+ # @note return empty array when the manifest includes no use-features element
349
+ # @since 2.5.0
350
+ def use_features
351
+ manifest_values('/manifest/uses-feature')
245
352
  end
246
353
 
247
354
  # Returns the manifest's application element or nil, if there isn't any.
@@ -277,6 +384,45 @@ module Android
277
384
  activities
278
385
  end
279
386
 
387
+ # @return [Array<Android::Manifest::Component>] all services in the apk
388
+ # @note return empty array when the manifest include no services
389
+ # @since 2.5.0
390
+ def services
391
+ components.select { |c| c.type == 'service' }
392
+ end
393
+
394
+ # @return [Array<String>] all deep link host and schemes in intent filters
395
+ # @note return empty array when the manifest include no http or https scheme of data
396
+ # @since 2.5.0
397
+ def deep_links
398
+ activities.each_with_object([]) do |activity, obj|
399
+ intent_filters = activity.intent_filters
400
+ next if intent_filters.empty?
401
+
402
+ intent_filters.each do |filter|
403
+ next unless filter.deep_links?
404
+
405
+ obj << filter.deep_links
406
+ end
407
+ end.flatten.uniq
408
+ end
409
+
410
+ # @return [Array<String>] all schemes in intent filters
411
+ # @note return empty array when the manifest not include http or https scheme(s) of data
412
+ # @since 2.5.0
413
+ def schemes
414
+ activities.each_with_object([]) do |activity, obj|
415
+ intent_filters = activity.intent_filters
416
+ next if intent_filters.empty?
417
+
418
+ intent_filters.each do |filter|
419
+ next unless filter.schemes?
420
+
421
+ obj << filter.schemes
422
+ end
423
+ end.flatten.uniq
424
+ end
425
+
280
426
  # @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities that are launchers in the apk
281
427
  # @note return empty array when the manifest include no activities
282
428
  def launcher_activities
@@ -311,7 +457,17 @@ module Android
311
457
 
312
458
  # @return [Integer] minSdkVersion in uses element
313
459
  def min_sdk_ver
314
- @doc.elements['/manifest/uses-sdk'].attributes['minSdkVersion'].to_i
460
+ @doc.elements['/manifest/uses-sdk']
461
+ .attributes['minSdkVersion']
462
+ .to_i
463
+ end
464
+
465
+ # @return [Integer] targetSdkVersion in uses element
466
+ # @since 2.5.0
467
+ def target_sdk_version
468
+ @doc.elements['/manifest/uses-sdk']
469
+ .attributes['targetSdkVersion']
470
+ .to_i
315
471
  end
316
472
 
317
473
  # application label
@@ -324,7 +480,7 @@ module Android
324
480
  if label.nil?
325
481
  # application element has no label attributes.
326
482
  # so looking for activites that has label attribute.
327
- activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
483
+ activities = @doc.elements['/manifest/application'].find{ |e| e.name == 'activity' && !e.attributes['label'].nil? }
328
484
  label = activities.nil? ? nil : activities.first.attributes['label']
329
485
  end
330
486
  unless @rsc.nil?
@@ -346,5 +502,15 @@ module Android
346
502
  formatter.write(@doc.root, xml)
347
503
  xml
348
504
  end
505
+
506
+ private
507
+
508
+ def manifest_values(path, key = 'name')
509
+ values = []
510
+ @doc.each_element(path) do |elem|
511
+ values << elem.attributes[key]
512
+ end
513
+ values.uniq
514
+ end
349
515
  end
350
516
  end
@@ -239,6 +239,7 @@ module Android
239
239
 
240
240
  def res_types
241
241
  end
242
+
242
243
  def find_res_string(key, opts={})
243
244
  unless opts[:lang].nil?
244
245
  string = @res_strings_lang[opts[:lang]]
@@ -354,12 +355,7 @@ module Android
354
355
  @types[type_id('string')].each do |type|
355
356
  str_hash = {}
356
357
  type.entry_count.times do |i|
357
- entry = type[i]
358
- if entry.nil?
359
- str_hash[i] = nil
360
- else
361
- str_hash[i] = @global_string_pool.strings[type[i].val.data]
362
- end
358
+ str_hash[i] = lookup_string_value(type, i)
363
359
  end
364
360
  lang = type.config.locale_lang
365
361
  contry = type.config.locale_contry
@@ -374,6 +370,20 @@ module Android
374
370
  end
375
371
  private :extract_res_strings
376
372
 
373
+ def lookup_string_value(type, index)
374
+ entry = type[index]
375
+ return nil if entry.nil?
376
+
377
+ if entry.val.data_type == ResValue::TYPE_REFERENCE
378
+ # this assumes that the value references another string resource, i.e. we're ignoring the type id of the reference
379
+ reference_id = entry.val.data & 0xffff
380
+ lookup_string_value(type, reference_id)
381
+ else
382
+ @global_string_pool.strings[entry.val.data]
383
+ end
384
+ end
385
+ private :lookup_string_value
386
+
377
387
  def inspect
378
388
  "<ResTablePackage offset:%#08x, size:%#x, name:\"%s\">" % [@offset, @size, @name]
379
389
  end
@@ -522,6 +532,8 @@ module Android
522
532
  end
523
533
 
524
534
  class ResValue < Chunk
535
+ TYPE_REFERENCE = 0x01
536
+
525
537
  attr_reader :size, :data_type, :data
526
538
  def parse
527
539
  @size = read_int16
data/lib/android/utils.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Android
3
4
  # Utility methods
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'android/apk'
4
+ require_relative 'android/manifest'
5
+ require_relative 'android/axml_parser'
6
+ require_relative 'android/axml_writer'
7
+ require_relative 'android/dex'
8
+ require_relative 'android/resource'
9
+ require_relative 'android/utils'
10
+ require_relative 'android/layout'
data/lib/ruby_apk.rb CHANGED
@@ -1,8 +1,4 @@
1
- require_relative 'android/apk'
2
- require_relative 'android/manifest'
3
- require_relative 'android/axml_parser'
4
- require_relative 'android/axml_writer'
5
- require_relative 'android/dex'
6
- require_relative 'android/resource'
7
- require_relative 'android/utils'
8
- require_relative 'android/layout'
1
+ # frozen_string_literal: true
2
+
3
+ # Compatible with older versions
4
+ require File.dirname(__FILE__) + '/android_parser'