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,47 @@
1
+ require 'threatinator/registry'
2
+ require 'threatinator/feed_builder'
3
+
4
+ module Threatinator
5
+ class FeedRegistry < Registry
6
+ # @param [Threatinator::Feed] feed The feed to register
7
+ # @raise [Threatinator::Exceptions::AlreadyRegisteredError] if a feed
8
+ # with the same name and provider is already registered.
9
+ def register(feed)
10
+ super([feed.provider, feed.name], feed)
11
+ end
12
+
13
+ # @param [String] provider
14
+ # @param [String] name
15
+ # @return [Threatinator::Feed]
16
+ def get(provider, name)
17
+ super([provider, name])
18
+ end
19
+
20
+ def each
21
+ return enum_for(:each) unless block_given?
22
+ super do |key, feed|
23
+ yield(feed)
24
+ end
25
+ end
26
+
27
+ def register_from_file(filename)
28
+ builder = Threatinator::FeedBuilder.from_file(filename)
29
+ feed = builder.build
30
+ register(feed)
31
+ feed
32
+ end
33
+
34
+ # Builds a new FeedRegistry based on the provided config
35
+ # @param [Threatinator::Config::FeedSearch] config The configuration
36
+ def self.build(config)
37
+ ret = self.new
38
+ config.search_path.each do |path|
39
+ pattern = File.join(path, "**", "*.feed")
40
+ Dir.glob(pattern).each do |filename|
41
+ ret.register_from_file(filename)
42
+ end
43
+ end
44
+ ret
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,118 @@
1
+ require 'threatinator/event_builder'
2
+ require 'observer'
3
+
4
+ module Threatinator
5
+ # Runs those feeds!
6
+ #
7
+ # Has the following observations:
8
+ # :start - start of feed parsing
9
+ #
10
+ # :start_fetch - start of fetching
11
+ # :end_fetch - end of fetching
12
+ #
13
+ # :start_decode - start of decoding
14
+ # :end_decode - end of decoding
15
+ #
16
+ # :start_parse_record - start of record parse
17
+ # - record - The record
18
+ #
19
+ # :record_filtered - Indicates that the record was filtered.
20
+ # - record - The record
21
+ #
22
+ # :record_missed - Indicates that the record was not parsed
23
+ # - record - The record
24
+ #
25
+ # :record_parsed - Indicates that the record WAS parsed
26
+ # - record - The record
27
+ # - events - The events that were parsed out of the
28
+ # record
29
+ #
30
+ # :end_parse_record - when a record has been parsed
31
+ # - record - The record
32
+ #
33
+ # :end - completion of feed parsing
34
+ #
35
+ class FeedRunner
36
+ include Observable
37
+
38
+ # @param [Threatinator::Feed] feed The feed that we want to run.
39
+ # @param [Threatinator::Output] output_formatter
40
+ def initialize(feed, output_formatter, opts = {})
41
+ @feed = feed
42
+ @output_formatter = output_formatter
43
+ @event_builder = Threatinator::EventBuilder.new(@feed)
44
+ @feed_filters = @feed.filter_builders.map { |x| x.call }
45
+ @decoders = @feed.decoder_builders.map { |x| x.call }
46
+ @parser_block = @feed.parser_block
47
+ @create_event_proc = @event_builder.create_event_proc()
48
+ end
49
+
50
+ # @param [Hash] opts The options hash
51
+ # @option opts [IO-like] :io Override the fetcher by providing
52
+ # an IO directly.
53
+ # @option opts [Boolean] :skip_decoding (false) Skip all decoding if set
54
+ # to true. Useful for testing.
55
+ def run(opts = {})
56
+ changed(true); notify_observers(:start)
57
+ skip_decoding = !!opts.delete(:skip_decoding)
58
+
59
+ unless io = opts.delete(:io)
60
+ fetcher = @feed.fetcher_builder.call()
61
+ changed(true); notify_observers(:start_fetch)
62
+ io = fetcher.fetch()
63
+ changed(true); notify_observers(:end_fetch)
64
+ end
65
+
66
+ unless skip_decoding == true
67
+ changed(true); notify_observers(:start_decode)
68
+ @decoders.each do |decoder|
69
+ io = decoder.decode(io)
70
+ end
71
+ changed(true); notify_observers(:end_decode)
72
+ end
73
+
74
+ parser = @feed.parser_builder.call()
75
+
76
+ parser.run(io) do |record|
77
+ rr = parse_record(record)
78
+ end
79
+
80
+ changed(true); notify_observers(:end)
81
+ nil
82
+ end
83
+
84
+ def parse_record(record)
85
+ @event_builder.clear
86
+ events = []
87
+ changed(true); notify_observers(:start_parse_record, record)
88
+
89
+ if @feed_filters.any? { |filter| filter.filter?(record) }
90
+ changed(true); notify_observers(:record_filtered, record)
91
+ return
92
+ end
93
+ @parser_block.call(@create_event_proc, record)
94
+ if @event_builder.count == 0
95
+ changed(true); notify_observers(:record_missed, record)
96
+ # Keep track of the fact that this line did not generate any events?
97
+ else
98
+ @event_builder.each_built_event do |event|
99
+ events << event
100
+ @output_formatter.handle_event(event)
101
+ end
102
+ changed(true); notify_observers(:record_parsed, record, events)
103
+ end
104
+ return
105
+ ensure
106
+ changed(true); notify_observers(:end_parse_record, record)
107
+ end
108
+
109
+ # Runs a feed
110
+ # @param [Threatinator::Feed] feed The feed to run
111
+ # @param [Threatinator::Output] output The output instance
112
+ # @param [Hash] run_opts Options passed to #run. See #run .
113
+ def self.run(feed, output, run_opts = {})
114
+ self.new(feed, output).run(run_opts)
115
+ end
116
+
117
+ end
118
+ end
@@ -0,0 +1,22 @@
1
+ module Threatinator
2
+ class Fetcher
3
+
4
+ # @param [Hash] opts An options hash. See subclasses for details.
5
+ def initialize(opts = {})
6
+ end
7
+
8
+ # @return [IO] an IO object
9
+ def fetch
10
+ raise NotImplementedError.new("#{self.class}#fetch not implemented!")
11
+ end
12
+
13
+ def ==(other)
14
+ true
15
+ end
16
+
17
+ def eql?(other)
18
+ self.class == other.class &&
19
+ self == other
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ require 'threatinator/fetcher'
2
+ require 'threatinator/exceptions'
3
+ require 'typhoeus'
4
+ require 'tempfile'
5
+
6
+ module Threatinator
7
+ module Fetchers
8
+ class Http < Threatinator::Fetcher
9
+ attr_reader :url
10
+ # @param [Hash] opts An options hash.
11
+ # @option opts [Addressable::URI] :url The URL that is to be fetched
12
+ # (required)
13
+ #
14
+ def initialize(opts = {})
15
+ @url = opts.delete(:url) or raise ArgumentError.new("Missing :url")
16
+ super(opts)
17
+ end
18
+
19
+ def ==(other)
20
+ @url == other.url && super(other)
21
+ end
22
+
23
+ # @return [IO] an IO-style object.
24
+ # @raise [Threatinator::Exceptions::FetchFailed] if the fetch fails
25
+ def fetch
26
+ tempfile = Tempfile.new("threatinator_http")
27
+ request = Typhoeus::Request.new(@url, ssl_verifypeer: false)
28
+ request.on_headers do |response|
29
+ if response.response_code != 200
30
+
31
+ raise Threatinator::Exceptions::FetchFailed.new("Request failed!")
32
+ end
33
+ end
34
+ request.on_body do |chunk|
35
+ tempfile.write(chunk)
36
+ end
37
+ # Run it
38
+ request.run
39
+ # Reset the IO to the beginning of the file
40
+ tempfile.rewind
41
+ tempfile
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ module Threatinator
2
+ # Acts as a filter for parser data.
3
+ class Filter
4
+ # What is passed in as arguments depends upon the parser.
5
+ #
6
+ # @param [Threatinator::Record] record The record to filter
7
+ # @return [Boolean] true if filtered, false otherwise.
8
+ def filter?(record)
9
+ raise NotImplementedError.new("#{self.class}.filter?(record) not implemented")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ require 'threatinator/filter'
2
+
3
+ module Threatinator
4
+ module Filters
5
+ # Basic filter that allows for arbitrary filtering.
6
+ class Block < Threatinator::Filter
7
+ def initialize(block)
8
+ @block = block
9
+ end
10
+
11
+ # @param [Threatinator::Record] record The record to filter
12
+ # @return [Boolean] true if filtered, false otherwise.
13
+ def filter?(record)
14
+ !! @block.call(record)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require 'threatinator/filter'
2
+
3
+ module Threatinator
4
+ module Filters
5
+ # Filters out any lines of text that begin with a comment '#'
6
+ class Comments < Threatinator::Filter
7
+ # @param [Threatinator::Record] record The record to filter
8
+ # @return [Boolean] true if filtered, false otherwise.
9
+ def filter?(record)
10
+ record.data[0] == '#'
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+
@@ -0,0 +1,19 @@
1
+ require 'threatinator/filter'
2
+
3
+ module Threatinator
4
+ module Filters
5
+ # Filters on any lines of text that consist entirely of whitespace
6
+ class Whitespace < Threatinator::Filter
7
+ def initialize()
8
+ @re = /^\s*$/
9
+ end
10
+
11
+ # @param [Threatinator::Record] record The record to filter
12
+ # @return [Boolean] true if filtered, false otherwise.
13
+ def filter?(record)
14
+ !! @re.match(record.data)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,50 @@
1
+ require 'threatinator/config/base'
2
+
3
+ module Threatinator
4
+ class Output
5
+ def initialize(config)
6
+ end
7
+
8
+ def handle_event(event)
9
+ #:nocov:
10
+ raise NotImplementedError.new("#{self.class}#handle_event is not implemented")
11
+ #:nocov:
12
+ end
13
+
14
+ def finish
15
+ #:nocov:
16
+ raise NotImplementedError.new("#{self.class}#finish is not implemented")
17
+ #:nocov:
18
+ end
19
+
20
+ class Config < Threatinator::Config::Base
21
+ end
22
+ end
23
+
24
+ class FileBasedOutput < Output
25
+ attr_reader :output_io
26
+ protected :output_io
27
+
28
+ def initialize(config)
29
+ super(config)
30
+ if io = config.io
31
+ @output_io = io
32
+ elsif filename = config.filename
33
+ @output_io = File.open(filename, 'w:UTF-8')
34
+ else
35
+ @output_io = $stdout.dup
36
+ end
37
+ end
38
+
39
+ def finish
40
+ @output_io.close
41
+ end
42
+
43
+ class Config < superclass::Config
44
+ attribute :filename, String,
45
+ description: "Path to the file where output will be written"
46
+
47
+ attribute :io
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ module Threatinator
2
+ class Parser
3
+ # @param [Hash] opts An options hash. See subclasses for details.
4
+ def initialize(opts = {})
5
+ end
6
+
7
+ # Runs the parser against the provided io, yielding records.
8
+ # @param [IO] io The IO to be parsed.
9
+ def run(io)
10
+ raise NotImplementedError.new("#{self.class}#run not implemented!")
11
+ end
12
+
13
+ def ==(other)
14
+ true
15
+ end
16
+
17
+ def eql?(other)
18
+ self.class == other.class &&
19
+ self == other
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,7 @@
1
+ module Threatinator
2
+ module Parsers
3
+ module CSV
4
+ require 'threatinator/parsers/csv/parser'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,77 @@
1
+ require 'threatinator/record'
2
+ require 'threatinator/parser'
3
+ require 'csv'
4
+
5
+ module Threatinator
6
+ module Parsers
7
+ module CSV
8
+ # Parses an IO, yielding a record with a CSV::Row.
9
+ class Parser < Threatinator::Parser
10
+ attr_reader :csv_opts, :row_separator, :col_separator, :headers
11
+
12
+ # @param [Hash] opts
13
+ # @option opts [String, :auto] :row_separator A string that represent the row
14
+ # separator. Identical to ::CSV.new's :row_sep.
15
+ # @option opts [String] :col_separator A string that represents the column
16
+ # separator. Identical to ::CSV.new's :col_sep.
17
+ # @option opts [Array<String>, :first_row, true, false] :headers The header
18
+ # configuration. Identical to ::CSV.new's :headers.
19
+ # @option opts [Hash] :csv_opts A hash of options that will be passed to
20
+ # Ruby's CSV.new.
21
+ # @see ::CSV
22
+ def initialize(opts = {})
23
+ @csv_opts = {}.merge(opts.delete(:csv_opts) || {})
24
+ @row_separator = opts.delete(:row_separator)
25
+ @col_separator = opts.delete(:col_separator)
26
+ @headers = opts.delete(:headers)
27
+
28
+ super(opts)
29
+ end
30
+
31
+ def ==(other)
32
+ @csv_opts == other.csv_opts &&
33
+ @row_separator == other.row_separator &&
34
+ @col_separator == other.col_separator &&
35
+ @headers == other.headers &&
36
+ super(other)
37
+ end
38
+
39
+ def _build_csv_opts
40
+ opts = {}.merge(@csv_opts)
41
+ opts[:return_headers] = true
42
+ opts[:row_sep] = @row_separator unless @row_separator.nil?
43
+ opts[:col_sep] = @col_separator unless @col_separator.nil?
44
+ opts[:headers] = @headers unless @headers.nil?
45
+ opts
46
+ end
47
+
48
+ # @param [IO] io
49
+ # @yield [record] Gives one line to the block
50
+ # @yieldparam record [Record] a record
51
+ def run(io)
52
+ lineno = 1
53
+ previous_pos = io.pos
54
+ csv = ::CSV.new(io, _build_csv_opts())
55
+ csv.each do |row|
56
+ begin
57
+ if row.kind_of?(::CSV::Row)
58
+ next if row.header_row?
59
+ row = row.to_hash
60
+ end
61
+
62
+ yield Record.new(row,
63
+ line_number: lineno,
64
+ pos_start: previous_pos,
65
+ pos_end: io.pos)
66
+
67
+ ensure
68
+ previous_pos = io.pos
69
+ lineno += 1
70
+ end
71
+ end
72
+ nil
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end