android_parser 2.4.1 → 2.5.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.
@@ -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'