threatinator 0.1.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.
Files changed (219) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/CONTRIBUTING.md +119 -0
  4. data/Gemfile +28 -0
  5. data/LICENSE +165 -0
  6. data/README.md +45 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/bin/threatinator +5 -0
  10. data/lib/threatinator.rb +3 -0
  11. data/lib/threatinator/action.rb +14 -0
  12. data/lib/threatinator/actions/list.rb +2 -0
  13. data/lib/threatinator/actions/list/action.rb +53 -0
  14. data/lib/threatinator/actions/list/config.rb +10 -0
  15. data/lib/threatinator/actions/run.rb +2 -0
  16. data/lib/threatinator/actions/run/action.rb +45 -0
  17. data/lib/threatinator/actions/run/config.rb +32 -0
  18. data/lib/threatinator/actions/run/coverage_observer.rb +54 -0
  19. data/lib/threatinator/actions/run/output_config.rb +59 -0
  20. data/lib/threatinator/cli.rb +13 -0
  21. data/lib/threatinator/cli/action_builder.rb +33 -0
  22. data/lib/threatinator/cli/list_action_builder.rb +19 -0
  23. data/lib/threatinator/cli/parser.rb +113 -0
  24. data/lib/threatinator/cli/run_action_builder.rb +41 -0
  25. data/lib/threatinator/config.rb +6 -0
  26. data/lib/threatinator/config/base.rb +35 -0
  27. data/lib/threatinator/config/feed_search.rb +25 -0
  28. data/lib/threatinator/decoder.rb +24 -0
  29. data/lib/threatinator/decoders/gzip.rb +30 -0
  30. data/lib/threatinator/event.rb +27 -0
  31. data/lib/threatinator/event_builder.rb +41 -0
  32. data/lib/threatinator/exceptions.rb +61 -0
  33. data/lib/threatinator/feed.rb +82 -0
  34. data/lib/threatinator/feed_builder.rb +156 -0
  35. data/lib/threatinator/feed_registry.rb +47 -0
  36. data/lib/threatinator/feed_runner.rb +118 -0
  37. data/lib/threatinator/fetcher.rb +22 -0
  38. data/lib/threatinator/fetchers/http.rb +46 -0
  39. data/lib/threatinator/filter.rb +12 -0
  40. data/lib/threatinator/filters/block.rb +18 -0
  41. data/lib/threatinator/filters/comments.rb +16 -0
  42. data/lib/threatinator/filters/whitespace.rb +19 -0
  43. data/lib/threatinator/output.rb +50 -0
  44. data/lib/threatinator/parser.rb +23 -0
  45. data/lib/threatinator/parsers/csv.rb +7 -0
  46. data/lib/threatinator/parsers/csv/parser.rb +77 -0
  47. data/lib/threatinator/parsers/getline.rb +8 -0
  48. data/lib/threatinator/parsers/getline/parser.rb +45 -0
  49. data/lib/threatinator/parsers/json.rb +8 -0
  50. data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
  51. data/lib/threatinator/parsers/json/parser.rb +45 -0
  52. data/lib/threatinator/parsers/json/record.rb +20 -0
  53. data/lib/threatinator/parsers/xml.rb +8 -0
  54. data/lib/threatinator/parsers/xml/node.rb +79 -0
  55. data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
  56. data/lib/threatinator/parsers/xml/parser.rb +44 -0
  57. data/lib/threatinator/parsers/xml/path.rb +70 -0
  58. data/lib/threatinator/parsers/xml/pattern.rb +53 -0
  59. data/lib/threatinator/parsers/xml/record.rb +14 -0
  60. data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
  61. data/lib/threatinator/plugin_loader.rb +115 -0
  62. data/lib/threatinator/plugins/output/csv.rb +47 -0
  63. data/lib/threatinator/plugins/output/null.rb +17 -0
  64. data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
  65. data/lib/threatinator/property_definer.rb +101 -0
  66. data/lib/threatinator/record.rb +22 -0
  67. data/lib/threatinator/registry.rb +53 -0
  68. data/lib/threatinator/util.rb +15 -0
  69. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +50 -0
  70. data/spec/feeds/alienvault-ip_reputation_spec.rb +50 -0
  71. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +50 -0
  72. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +50 -0
  73. data/spec/feeds/autoshun_shunlist_spec.rb +42 -0
  74. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +50 -0
  75. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +50 -0
  76. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +50 -0
  77. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +50 -0
  78. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +50 -0
  79. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +50 -0
  80. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +50 -0
  81. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +50 -0
  82. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +50 -0
  83. data/spec/feeds/ciarmy-ip_reputation_spec.rb +50 -0
  84. data/spec/feeds/cruzit-ip_reputation_spec.rb +50 -0
  85. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +50 -0
  86. data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
  87. data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
  88. data/spec/feeds/data/arbor_domainlist.txt +11 -0
  89. data/spec/feeds/data/arbor_ssh.txt +16 -0
  90. data/spec/feeds/data/autoshun_shunlist.csv +20 -0
  91. data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
  92. data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
  93. data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
  94. data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
  95. data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
  96. data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
  97. data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
  98. data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
  99. data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
  100. data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
  101. data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
  102. data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
  103. data/spec/feeds/data/dshield_topattackers.xml +4 -0
  104. data/spec/feeds/data/feodo_domainlist.txt +18 -0
  105. data/spec/feeds/data/feodo_iplist.txt +20 -0
  106. data/spec/feeds/data/infiltrated_iplist.txt +16 -0
  107. data/spec/feeds/data/malc0de_domainlist.txt +18 -0
  108. data/spec/feeds/data/malc0de_iplist.txt +14 -0
  109. data/spec/feeds/data/mirc_domainlist.txt +31 -0
  110. data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
  111. data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
  112. data/spec/feeds/data/openbl_iplist.txt +12 -0
  113. data/spec/feeds/data/palevo_domainlist.txt +25 -0
  114. data/spec/feeds/data/palevo_iplist.txt +24 -0
  115. data/spec/feeds/data/phishtank-sample.json.gz +0 -0
  116. data/spec/feeds/data/spyeye_domainlist.txt +16 -0
  117. data/spec/feeds/data/spyeye_iplist.txt +19 -0
  118. data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
  119. data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
  120. data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
  121. data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
  122. data/spec/feeds/data/zeus_domainlist.txt +27 -0
  123. data/spec/feeds/dshield_attackers-top1000_spec.rb +43 -0
  124. data/spec/feeds/feodo-domain_reputation_spec.rb +50 -0
  125. data/spec/feeds/feodo-ip_reputation_spec.rb +50 -0
  126. data/spec/feeds/infiltrated-ip_reputation_spec.rb +50 -0
  127. data/spec/feeds/malc0de-domain_reputation_spec.rb +50 -0
  128. data/spec/feeds/malc0de-ip_reputation_spec.rb +50 -0
  129. data/spec/feeds/mirc-domain_reputation_spec.rb +50 -0
  130. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +50 -0
  131. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +50 -0
  132. data/spec/feeds/openbl-ip_reputation_spec.rb +50 -0
  133. data/spec/feeds/palevo-domain_reputation_spec.rb +50 -0
  134. data/spec/feeds/palevo-ip_reputation_spec.rb +50 -0
  135. data/spec/feeds/phishtank_spec.rb +45 -0
  136. data/spec/feeds/spyeye-domain_reputation_spec.rb +50 -0
  137. data/spec/feeds/spyeye-ip_reputation_spec.rb +50 -0
  138. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +50 -0
  139. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +50 -0
  140. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +50 -0
  141. data/spec/feeds/zeus-domain_reputation_spec.rb +50 -0
  142. data/spec/feeds/zeus-ip_reputation_spec.rb +50 -0
  143. data/spec/fixtures/feed/provider1/feed1.feed +6 -0
  144. data/spec/fixtures/parsers/test.xml +13 -0
  145. data/spec/fixtures/parsers/test_self_closing.xml +20 -0
  146. data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
  147. data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
  148. data/spec/fixtures/plugins/fake.rb +19 -0
  149. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
  150. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
  151. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
  152. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
  153. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
  154. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
  155. data/spec/spec_helper.rb +52 -0
  156. data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
  157. data/spec/support/bad_feeds/missing_name.feed +6 -0
  158. data/spec/support/bad_feeds/missing_parser.feed +3 -0
  159. data/spec/support/bad_feeds/missing_provider.feed +5 -0
  160. data/spec/support/factories/event.rb +27 -0
  161. data/spec/support/factories/feed.rb +32 -0
  162. data/spec/support/factories/feed_builder.rb +65 -0
  163. data/spec/support/factories/feed_registry.rb +8 -0
  164. data/spec/support/factories/output.rb +11 -0
  165. data/spec/support/factories/record.rb +17 -0
  166. data/spec/support/factories/xml_node.rb +33 -0
  167. data/spec/support/helpers/io.rb +11 -0
  168. data/spec/support/helpers/models.rb +13 -0
  169. data/spec/support/shared/action_builder.rb +47 -0
  170. data/spec/support/shared/decoder.rb +70 -0
  171. data/spec/support/shared/feeds.rb +218 -0
  172. data/spec/support/shared/fetcher.rb +48 -0
  173. data/spec/support/shared/filter.rb +14 -0
  174. data/spec/support/shared/io-like.rb +7 -0
  175. data/spec/support/shared/output.rb +120 -0
  176. data/spec/support/shared/parsers.rb +51 -0
  177. data/spec/support/shared/record.rb +111 -0
  178. data/spec/threatinator/actions/list/action_spec.rb +93 -0
  179. data/spec/threatinator/actions/run/action_spec.rb +89 -0
  180. data/spec/threatinator/actions/run/config_spec.rb +39 -0
  181. data/spec/threatinator/actions/run/coverage_observer_spec.rb +116 -0
  182. data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
  183. data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
  184. data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
  185. data/spec/threatinator/cli_spec.rb +175 -0
  186. data/spec/threatinator/config/base_spec.rb +39 -0
  187. data/spec/threatinator/config/feed_search_spec.rb +76 -0
  188. data/spec/threatinator/decoders/gzip_spec.rb +75 -0
  189. data/spec/threatinator/event_builder_spec.rb +33 -0
  190. data/spec/threatinator/event_spec.rb +30 -0
  191. data/spec/threatinator/feed_builder_spec.rb +636 -0
  192. data/spec/threatinator/feed_registry_spec.rb +198 -0
  193. data/spec/threatinator/feed_runner_spec.rb +155 -0
  194. data/spec/threatinator/feed_spec.rb +169 -0
  195. data/spec/threatinator/fetcher_spec.rb +12 -0
  196. data/spec/threatinator/fetchers/http_spec.rb +32 -0
  197. data/spec/threatinator/filter_spec.rb +13 -0
  198. data/spec/threatinator/filters/block_spec.rb +16 -0
  199. data/spec/threatinator/filters/comments_spec.rb +13 -0
  200. data/spec/threatinator/filters/whitespace_spec.rb +12 -0
  201. data/spec/threatinator/parser_spec.rb +13 -0
  202. data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
  203. data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
  204. data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
  205. data/spec/threatinator/parsers/json/record_spec.rb +30 -0
  206. data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
  207. data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
  208. data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
  209. data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
  210. data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
  211. data/spec/threatinator/plugin_loader_spec.rb +238 -0
  212. data/spec/threatinator/plugins/output/csv_spec.rb +46 -0
  213. data/spec/threatinator/plugins/output/null_spec.rb +17 -0
  214. data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
  215. data/spec/threatinator/property_definer_spec.rb +155 -0
  216. data/spec/threatinator/record_spec.rb +19 -0
  217. data/spec/threatinator/registry_spec.rb +97 -0
  218. data/spec/threatinator/runner_spec.rb +273 -0
  219. metadata +376 -0
@@ -0,0 +1,8 @@
1
+ module Threatinator
2
+ module Parsers
3
+ module Getline
4
+ require 'threatinator/parsers/getline/parser'
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,45 @@
1
+ require 'threatinator/record'
2
+ require 'threatinator/parser'
3
+
4
+ module Threatinator
5
+ module Parsers
6
+ module Getline
7
+ # Parses an IO, yielding each 'line' of data as deliniated by :separator.
8
+ # The text matching :separator will be included.
9
+ class Parser < Threatinator::Parser
10
+ attr_reader :separator
11
+
12
+ # @param [Hash] opts
13
+ # @option opts [String] :separator ("\n") A character that will be used
14
+ # to detect the end of a line.
15
+ def initialize(opts = {})
16
+ @separator = opts.delete(:separator) || "\n"
17
+ unless @separator.length == 1
18
+ raise ArgumentError.new(":separator must be exactly one character long")
19
+ end
20
+ super(opts)
21
+ end
22
+
23
+ def ==(other)
24
+ @separator == other.separator &&
25
+ super(other)
26
+ end
27
+
28
+ # @param [IO] io The IO to be parsed
29
+ # @yield [line] Gives one line to the block
30
+ # @yieldparam line [String] a line from the IO stream.
31
+ def run(io)
32
+ return enum_for(:each) unless block_given?
33
+ lineno = 1
34
+ while str = io.gets(@separator)
35
+ return if str.nil?
36
+ pos_start = io.pos - str.length
37
+ yield Record.new(str, line_number: lineno, pos_start: pos_start, pos_end: io.pos)
38
+ lineno += 1
39
+ end
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ module Threatinator
2
+ module Parsers
3
+ module JSON
4
+ require 'threatinator/parsers/json/parser'
5
+ end
6
+ end
7
+ end
8
+
@@ -0,0 +1,65 @@
1
+ require 'threatinator/parsers/json'
2
+ require 'threatinator/exceptions'
3
+ require 'oj'
4
+
5
+ module Threatinator::Parsers::JSON::Adapters
6
+ class Oj < ::Oj::ScHandler
7
+ def initialize
8
+ @root = nil
9
+ @depth = 0
10
+ end
11
+
12
+ def run(io, &callback)
13
+ @callback = callback
14
+ begin
15
+ ::Oj.sc_parse(self, io)
16
+ rescue ::Oj::ParseError => e
17
+ raise Threatinator::Exceptions::ParseError.new(e)
18
+ end
19
+ end
20
+
21
+ def do_callback(data, key = nil)
22
+ @callback.call(data, key: key)
23
+ end
24
+
25
+ def hash_start
26
+ ret = {}
27
+ @depth += 1
28
+ @root = ret if @root.nil?
29
+ ret
30
+ end
31
+
32
+ def hash_set(h,k,v)
33
+ if @depth == 1
34
+ do_callback(v, k)
35
+ else
36
+ h[k] = v
37
+ end
38
+ v
39
+ end
40
+
41
+ def hash_end
42
+ @depth -= 1
43
+ end
44
+
45
+ def array_start
46
+ ret = []
47
+ @depth += 1
48
+ @root = ret if @root.nil?
49
+ ret
50
+ end
51
+
52
+ def array_append(a,v)
53
+ if @depth == 1
54
+ do_callback(v)
55
+ else
56
+ a << v
57
+ end
58
+ end
59
+
60
+ def array_end
61
+ @depth -= 1
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,45 @@
1
+ require 'threatinator/parser'
2
+ require 'threatinator/parsers/json/record'
3
+
4
+ module Threatinator
5
+ module Parsers
6
+ module JSON
7
+ class Parser < Threatinator::Parser
8
+ def initialize(opts = {})
9
+ @adapter_class = self.class.adapter_class
10
+ super(opts)
11
+ end
12
+
13
+ # Detects the platform, loads the JSON adapter, and returns the
14
+ # adapter's class.
15
+ def self.adapter_class
16
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
17
+ #:nocov:
18
+ raise "JSON parsing not supported for JRuby"
19
+ #:nocov:
20
+ else
21
+ require 'threatinator/parsers/json/adapters/oj'
22
+ return Threatinator::Parsers::JSON::Adapters::Oj
23
+ end
24
+ end
25
+
26
+ def ==(other)
27
+ super(other)
28
+ end
29
+
30
+ # @param [IO] io
31
+ # @yield [record] Gives one line to the block
32
+ # @yieldparam record [Threatinator::Parsers::JSON::Record] a record
33
+ def run(io)
34
+ adapter = @adapter_class.new
35
+ callback = lambda do |object, opts = {}|
36
+ yield Threatinator::Parsers::JSON::Record.new(object, opts)
37
+ end
38
+ adapter.run(io, &callback)
39
+ nil
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ require 'threatinator/record'
2
+
3
+ module Threatinator
4
+ module Parsers
5
+ module JSON
6
+ class Record < Threatinator::Record
7
+ attr_reader :key
8
+ def initialize(object, opts = {})
9
+ @key = opts.delete(:key)
10
+ super(object, opts)
11
+ end
12
+
13
+ def ==(other)
14
+ @key == other.key &&
15
+ super(other)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module Threatinator
2
+ module Parsers
3
+ module XML
4
+ require 'threatinator/parsers/xml/parser'
5
+ require 'threatinator/parsers/xml/pattern'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,79 @@
1
+ module Threatinator
2
+ module Parsers
3
+ module XML
4
+ class Node
5
+ attr_accessor :text
6
+ attr_reader :name
7
+ attr_reader :attrs
8
+ attr_reader :children
9
+
10
+ # @param [String, Symbol] name
11
+ # @param [Hash] opts
12
+ # @option opts [String] :text The text
13
+ # @option opts [Hash] :attrs The attributes
14
+ # @option opts [Array<Threatinator::Parsers::XML::Node>] :children An array
15
+ # of child child nodes that belong to this node.
16
+ def initialize(name, opts = {})
17
+ unless name.kind_of?(::Symbol) or name.kind_of?(::String)
18
+ raise TypeError.new("name must be a String or a Symbol")
19
+ end
20
+
21
+ @name = name.to_sym
22
+ @text = opts.delete(:text) || ""
23
+ unless @text.kind_of?(::String)
24
+ raise TypeError.new(":text must be a String")
25
+ end
26
+ @attrs = opts.delete(:attrs) || {}
27
+ unless @attrs.kind_of?(::Hash)
28
+ raise TypeError.new(":text must be a Hash")
29
+ end
30
+
31
+ @children = {}
32
+ if _children = opts.delete(:children)
33
+ _children.each do |child|
34
+ add_child(child)
35
+ end
36
+ end
37
+ end
38
+
39
+ def ==(other)
40
+ @name == other.name &&
41
+ @attrs == other.attrs &&
42
+ @text == other.text &&
43
+ @children == other.children
44
+ end
45
+
46
+ def eql?(other)
47
+ other.kind_of?(self.class) &&
48
+ self == other
49
+ end
50
+
51
+ # @return [Integer] the number of children
52
+ def num_children
53
+ @children.values.inject(0) {|total, child_set| total + child_set.count}
54
+ end
55
+
56
+ # @param [String, Symbol] name The name of the child element
57
+ # @return [Array<Node>] An array containing all the child nodes for the given
58
+ # name. The array will be empty if there are no children by the given name.
59
+ def [](name)
60
+ @children[name.to_sym] || []
61
+ end
62
+
63
+ # @return [Array<Symbol>] an array containing all the names of child elements
64
+ def child_names
65
+ @children.keys
66
+ end
67
+
68
+ private
69
+ def add_child(child)
70
+ name = child.name
71
+ unless child_set = @children[name]
72
+ child_set = @children[name] = []
73
+ end
74
+ child_set << child
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,39 @@
1
+ require 'threatinator/parsers/xml/node'
2
+
3
+ module Threatinator
4
+ module Parsers
5
+ module XML
6
+ class NodeBuilder
7
+ def initialize(name, attributes)
8
+ @name = name
9
+ @attributes = {}
10
+ @children = []
11
+ @text = ""
12
+
13
+ unless attributes.empty?
14
+ attributes.each { |attr| self.add_attribute(attr.localname, attr.value) }
15
+ end
16
+ end
17
+
18
+ def append_text(chars)
19
+ @text << chars
20
+ end
21
+
22
+ def add_attribute(name, value)
23
+ @attributes[name.to_sym] = value
24
+ end
25
+
26
+ def add_child(node)
27
+ @children << node
28
+ end
29
+
30
+ def build
31
+ Threatinator::Parsers::XML::Node.new(@name,
32
+ attrs: @attributes,
33
+ text: @text.strip,
34
+ children: @children)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,44 @@
1
+ require 'threatinator/parser'
2
+ require 'nokogiri'
3
+
4
+ module Threatinator
5
+ module Parsers
6
+ module XML
7
+ class Parser < Threatinator::Parser
8
+ require 'threatinator/parsers/xml/path'
9
+ require 'threatinator/parsers/xml/record'
10
+ require 'threatinator/parsers/xml/sax_document'
11
+
12
+ attr_reader :pattern
13
+ # @param [Hash] opts Parameters hash
14
+ # @option opts [Threatinator::Parsers::XML::Pattern] :pattern The pattern
15
+ # object to use for matching chunks of XML
16
+ def initialize(opts = {})
17
+ @pattern = opts.delete(:pattern) or raise ArgumentError.new("Missing argument :pattern")
18
+ @max_cursor_depth = @pattern.max_depth - 1
19
+ super(opts)
20
+ end
21
+
22
+ def ==(other)
23
+ @pattern == other.pattern &&
24
+ super(other)
25
+ end
26
+
27
+ # @param [IO] io
28
+ # @yield [record] Gives one line to the block
29
+ # @yieldparam record [Threatinator::Parser::XML::Record] a record
30
+ def run(io)
31
+ stack = Path.new
32
+ callback = lambda do |element|
33
+ yield(Threatinator::Parsers::XML::Record.new(element))
34
+ end
35
+
36
+ doc = SAXDocument.new(@pattern, callback)
37
+ parser = Nokogiri::XML::SAX::Parser.new(doc)
38
+ parser.parse(io)
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,70 @@
1
+ module Threatinator
2
+ module Parsers
3
+ module XML
4
+ class Path
5
+ attr_reader :parts
6
+
7
+ # @param [String, Array, nil] str_or_parts ([]) If set to a String, splits
8
+ # the string by '/' into an array. If set to an Array, sets parts to a
9
+ # duplicate of the array. If set to nil or not specified, defaults to
10
+ # a new array.
11
+ # @raise [TypeError] if something other than a String, Array, or nil is
12
+ # specified for str_or_parts.
13
+ def initialize(str_or_parts = nil)
14
+ @parts =
15
+ case str_or_parts
16
+ when ::String
17
+ if str_or_parts.length == 0 or !str_or_parts.start_with?('/')
18
+ raise ArgumentError.new('str_or_parts must be a String beginning with "/"')
19
+ end
20
+ r = str_or_parts.split('/')
21
+ r.shift
22
+ r
23
+ when ::Array
24
+ str_or_parts.dup
25
+ when nil
26
+ []
27
+ else
28
+ raise TypeError.new("Expected argument must be a String, Array, or nil")
29
+ end
30
+ end
31
+
32
+ def ==(other)
33
+ @parts == other.parts
34
+ end
35
+
36
+ def eql?(other)
37
+ other.kind_of?(self.class) &&
38
+ self == other
39
+ end
40
+
41
+ # length = 5
42
+ # 0 1 2 3 4
43
+ # /a/b/c/d/e
44
+ # 0 1
45
+ # /d/e
46
+ def end_with?(other_path)
47
+ return false if other_path.length > self.length
48
+ return true if other_path.length == 0
49
+ pos = length - other_path.length
50
+ other_path.parts.each_with_index do |other_part, idx|
51
+ return false unless @parts[(pos + idx)] == other_part
52
+ end
53
+ true
54
+ end
55
+
56
+ def push(name)
57
+ @parts.push(name)
58
+ end
59
+
60
+ def pop
61
+ @parts.pop
62
+ end
63
+
64
+ def length
65
+ @parts.length
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end