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,12 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/fetcher'
3
+
4
+ describe Threatinator::Fetcher do
5
+ let(:fetcher) { Threatinator::Fetcher.new }
6
+ describe "#fetch" do
7
+ it "should raise NotImplementedError because it's not implemented" do
8
+ expect {fetcher.fetch}.to raise_error(NotImplementedError)
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/fetchers/http'
3
+
4
+ describe Threatinator::Fetchers::Http do
5
+ let(:url) { "http://foobar.com/bla.json" }
6
+ let(:fetcher_builder) { lambda { described_class.new(url: url) } }
7
+ let(:fetcher_builder_different) { lambda { described_class.new(url: "http://foobar.com/sdf.json") } }
8
+ let(:fetcher) { fetcher_builder.call() }
9
+
10
+ it_should_behave_like "a fetcher" do
11
+ before :each do
12
+ stub_request(:get, url).to_return(:body => expected_data)
13
+ end
14
+ end
15
+
16
+ describe "#url" do
17
+ it "should return the value of the URL" do
18
+ expect(fetcher.url).to eq(url)
19
+ end
20
+ end
21
+
22
+ [404, 500].each do |status_code|
23
+ context "when an HTTP response has a status code of #{status_code}" do
24
+ it_should_behave_like "a #fetch call that failed" do
25
+ before :each do
26
+ stub_request(:get, url)
27
+ .to_return(:status => status_code)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/filter'
3
+
4
+ describe Threatinator::Filter do
5
+ let(:filter) { Threatinator::Filter.new }
6
+ describe "#filter?(record)" do
7
+ it "should raise NotImplementedError because it's not implemented" do
8
+ expect {filter.filter?(1234)}.to raise_error(NotImplementedError)
9
+ end
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/record'
3
+ require 'threatinator/filters/block'
4
+
5
+ describe Threatinator::Filters::Block do
6
+ it_should_behave_like "a filter" do
7
+ let(:filter_block) {
8
+ lambda do |record|
9
+ record.data =~ /^FILTERME$/
10
+ end
11
+ }
12
+ let(:filter) { described_class.new(filter_block) }
13
+ let(:should_filter) { Threatinator::Record.new("FILTERME") }
14
+ let(:shouldnt_filter) { Threatinator::Record.new("I'm OK!") }
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/record'
3
+ require 'threatinator/filters/comments'
4
+
5
+ describe Threatinator::Filters::Comments do
6
+ it_should_behave_like "a filter" do
7
+ let(:filter) { described_class.new() }
8
+ let(:should_filter) { Threatinator::Record.new("# Here's my comment") }
9
+ let(:shouldnt_filter) { Threatinator::Record.new("I'm OK # wooo!") }
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/record'
3
+ require 'threatinator/filters/whitespace'
4
+
5
+ describe Threatinator::Filters::Whitespace do
6
+ it_should_behave_like "a filter" do
7
+ let(:filter) { described_class.new() }
8
+ let(:should_filter) { Threatinator::Record.new(" ") }
9
+ let(:shouldnt_filter) { Threatinator::Record.new(" I'm OK!") }
10
+ end
11
+ end
12
+
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/parser'
3
+
4
+ describe Threatinator::Parser do
5
+ let(:parser) { Threatinator::Parser.new() }
6
+ describe "#run" do
7
+ it "should raise NotImplementedError because it's not implemented" do
8
+ expect { |b| parser.run(double("io"), &b) }.to raise_error(NotImplementedError)
9
+ end
10
+ end
11
+ end
12
+
13
+
@@ -0,0 +1,202 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/parsers/csv/parser'
3
+ require 'stringio'
4
+ require 'threatinator/record'
5
+
6
+ describe Threatinator::Parsers::CSV::Parser do
7
+ it_should_behave_like "a parser when compared to an identically configured parser" do
8
+ let(:parser1) { described_class.new(:row_separator => "X") }
9
+ let(:parser2) { described_class.new(:row_separator => "X") }
10
+ end
11
+
12
+ context "two instances with differently configured row_separators" do
13
+ it_should_behave_like "a parser when compared to a differently configured parser" do
14
+ let(:parser1) { described_class.new(:row_separator => "X") }
15
+ let(:parser2) { described_class.new(:row_separator => "Y") }
16
+ end
17
+ end
18
+
19
+ context "by default" do
20
+ it "should parse \\n as a row separator" do
21
+ str = "abc\ndef\nhij"
22
+ sio = StringIO.new(str)
23
+ parser = described_class.new()
24
+
25
+ expect { |b|
26
+ parser.run(sio, &b)
27
+ }.to yield_successive_args(
28
+ Threatinator::Record.new(['abc'], line_number: 1, pos_start: 0, pos_end: 4),
29
+ Threatinator::Record.new(['def'], line_number: 2, pos_start: 4, pos_end: 8),
30
+ Threatinator::Record.new(['hij'], line_number: 3, pos_start: 8, pos_end: 11),
31
+ )
32
+ end
33
+
34
+ it "should parse \\r\\n as a row separator" do
35
+ str = "abc\r\ndef\r\nhij"
36
+ sio = StringIO.new(str)
37
+ parser = described_class.new()
38
+
39
+ expect { |b|
40
+ parser.run(sio, &b)
41
+ }.to yield_successive_args(
42
+ Threatinator::Record.new(['abc'], line_number: 1, pos_start: 0, pos_end: 5),
43
+ Threatinator::Record.new(['def'], line_number: 2, pos_start: 5, pos_end: 10),
44
+ Threatinator::Record.new(['hij'], line_number: 3, pos_start: 10, pos_end: 13),
45
+ )
46
+ end
47
+
48
+ it "should parse \\r as a row separator" do
49
+ str = "abc\rdef\rhij"
50
+ sio = StringIO.new(str)
51
+ parser = described_class.new()
52
+
53
+ expect { |b|
54
+ parser.run(sio, &b)
55
+ }.to yield_successive_args(
56
+ Threatinator::Record.new(['abc'], line_number: 1, pos_start: 0, pos_end: 4),
57
+ Threatinator::Record.new(['def'], line_number: 2, pos_start: 4, pos_end: 8),
58
+ Threatinator::Record.new(['hij'], line_number: 3, pos_start: 8, pos_end: 11),
59
+ )
60
+ end
61
+
62
+ it "should use commas as column separators" do
63
+ str = "1,2,3\n4,5,6\n7,8,9"
64
+ sio = StringIO.new(str)
65
+ parser = described_class.new()
66
+
67
+ expect { |b|
68
+ parser.run(sio, &b)
69
+ }.to yield_successive_args(
70
+ Threatinator::Record.new(['1','2','3'], line_number: 1, pos_start: 0, pos_end: 6),
71
+ Threatinator::Record.new(['4','5','6'], line_number: 2, pos_start: 6, pos_end: 12),
72
+ Threatinator::Record.new(['7','8','9'], line_number: 3, pos_start: 12, pos_end: 17),
73
+ )
74
+ end
75
+
76
+ it "should ignore separators contained in between a set of double-quotes" do
77
+ str = "1,\"2\",3\n\"4,5\",6\n\"7,8,9\""
78
+ sio = StringIO.new(str)
79
+ parser = described_class.new()
80
+
81
+ expect { |b|
82
+ parser.run(sio, &b)
83
+ }.to yield_successive_args(
84
+ Threatinator::Record.new(['1','2','3'], line_number: 1, pos_start: 0, pos_end: 8),
85
+ Threatinator::Record.new(['4,5','6'], line_number: 2, pos_start: 8, pos_end: 16),
86
+ Threatinator::Record.new(['7,8,9'], line_number: 3, pos_start: 16, pos_end: 23),
87
+ )
88
+ end
89
+ end
90
+
91
+ describe "configuration options" do
92
+ describe ":row_separator" do
93
+ it "should specify the row separator" do
94
+ str = "1,2,3X4,5,6X7,8,9"
95
+ sio = StringIO.new(str)
96
+ parser = described_class.new(row_separator: "X")
97
+
98
+ expect { |b|
99
+ parser.run(sio, &b)
100
+ }.to yield_successive_args(
101
+ Threatinator::Record.new(['1','2','3'], line_number: 1, pos_start: 0, pos_end: 6),
102
+ Threatinator::Record.new(['4','5','6'], line_number: 2, pos_start: 6, pos_end: 12),
103
+ Threatinator::Record.new(['7','8','9'], line_number: 3, pos_start: 12, pos_end: 17),
104
+ )
105
+ end
106
+ it "should be accessible via #row_separator" do
107
+ parser = described_class.new(row_separator: "X")
108
+ expect(parser.row_separator).to eq("X")
109
+ end
110
+ end
111
+
112
+ describe ":col_separator" do
113
+ it "should specify the column separator" do
114
+ str = "1X2X3\n4X5X6\n7X8X9"
115
+ sio = StringIO.new(str)
116
+ parser = described_class.new(col_separator: "X")
117
+
118
+ expect { |b|
119
+ parser.run(sio, &b)
120
+ }.to yield_successive_args(
121
+ Threatinator::Record.new(['1','2','3'], line_number: 1, pos_start: 0, pos_end: 6),
122
+ Threatinator::Record.new(['4','5','6'], line_number: 2, pos_start: 6, pos_end: 12),
123
+ Threatinator::Record.new(['7','8','9'], line_number: 3, pos_start: 12, pos_end: 17),
124
+ )
125
+ end
126
+ it "should be accessible via #col_separator" do
127
+ parser = described_class.new(col_separator: "X")
128
+ expect(parser.col_separator).to eq("X")
129
+ end
130
+ end
131
+ describe ":headers" do
132
+ it "should be accessible via #headers" do
133
+ parser = described_class.new(headers: :first_row)
134
+ expect(parser.headers).to eq(:first_row)
135
+ end
136
+ context "when set to false" do
137
+ it "should not use any headers" do
138
+ str = "1,2,3\n4,5,6\n7,8,9"
139
+ sio = StringIO.new(str)
140
+ parser = described_class.new(headers: false)
141
+
142
+ expect { |b|
143
+ parser.run(sio, &b)
144
+ }.to yield_successive_args(
145
+ Threatinator::Record.new(['1','2','3'], line_number: 1, pos_start: 0, pos_end: 6),
146
+ Threatinator::Record.new(['4','5','6'], line_number: 2, pos_start: 6, pos_end: 12),
147
+ Threatinator::Record.new(['7','8','9'], line_number: 3, pos_start: 12, pos_end: 17),
148
+ )
149
+ end
150
+ end
151
+ context "when set to true" do
152
+ it "should read the first row as the headers, returning Hashes with the headers as keys" do
153
+
154
+ str = "a,b,c\n1,2,3\n4,5,6\n7,8,9"
155
+ sio = StringIO.new(str)
156
+ parser = described_class.new(headers: true)
157
+
158
+ expect { |b|
159
+ parser.run(sio, &b)
160
+ }.to yield_successive_args(
161
+ Threatinator::Record.new({'a'=>'1','b'=>'2','c'=>'3'}, line_number: 2, pos_start: 6, pos_end: 12),
162
+ Threatinator::Record.new({'a'=>'4','b'=>'5','c'=>'6'}, line_number: 3, pos_start: 12, pos_end: 18),
163
+ Threatinator::Record.new({'a'=>'7','b'=>'8','c'=>'9'}, line_number: 4, pos_start: 18, pos_end: 23)
164
+ )
165
+ end
166
+ end
167
+ context "when set to :first_row" do
168
+ it "should read the first row as the headers, returning Hashes with the headers as keys" do
169
+
170
+ str = "a,b,c\n1,2,3\n4,5,6\n7,8,9"
171
+ sio = StringIO.new(str)
172
+ parser = described_class.new(headers: :first_row)
173
+
174
+ expect { |b|
175
+ parser.run(sio, &b)
176
+ }.to yield_successive_args(
177
+ Threatinator::Record.new({'a'=>'1','b'=>'2','c'=>'3'}, line_number: 2, pos_start: 6, pos_end: 12),
178
+ Threatinator::Record.new({'a'=>'4','b'=>'5','c'=>'6'}, line_number: 3, pos_start: 12, pos_end: 18),
179
+ Threatinator::Record.new({'a'=>'7','b'=>'8','c'=>'9'}, line_number: 4, pos_start: 18, pos_end: 23)
180
+ )
181
+ end
182
+ end
183
+ context "when set to an array of strings" do
184
+ it "should use the array as the headers, returning Hashes with the headers as keys" do
185
+
186
+ str = "1,2,3\n4,5,6\n7,8,9"
187
+ sio = StringIO.new(str)
188
+ parser = described_class.new(headers: ['a', 'b', 'c'])
189
+
190
+ expect { |b|
191
+ parser.run(sio, &b)
192
+ }.to yield_successive_args(
193
+ Threatinator::Record.new({'a'=>'1','b'=>'2','c'=>'3'}, line_number: 2, pos_start: 0, pos_end: 6),
194
+ Threatinator::Record.new({'a'=>'4','b'=>'5','c'=>'6'}, line_number: 3, pos_start: 6, pos_end: 12),
195
+ Threatinator::Record.new({'a'=>'7','b'=>'8','c'=>'9'}, line_number: 4, pos_start: 12, pos_end: 17)
196
+ )
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/parsers/getline/parser'
3
+ require 'stringio'
4
+ require 'threatinator/record'
5
+
6
+ describe Threatinator::Parsers::Getline::Parser do
7
+ it_should_behave_like "a parser when compared to an identically configured parser" do
8
+ let(:parser1) { described_class.new(:separator => "X") }
9
+ let(:parser2) { described_class.new(:separator => "X") }
10
+ end
11
+
12
+ context "two instances with differently configured separators" do
13
+ it_should_behave_like "a parser when compared to a differently configured parser" do
14
+ let(:parser1) { described_class.new(:separator => "X") }
15
+ let(:parser2) { described_class.new(:separator => "Y") }
16
+ end
17
+ end
18
+
19
+ it "should default to newline as a separator" do
20
+ str = "line 1\nline 2\nline3!"
21
+ sio = StringIO.new(str)
22
+ parser = described_class.new()
23
+
24
+ expect { |b|
25
+ parser.run(sio, &b)
26
+ }.to yield_successive_args(
27
+ Threatinator::Record.new("line 1\n", line_number: 1, pos_start: 0, pos_end: 7),
28
+ Threatinator::Record.new("line 2\n", line_number: 2, pos_start: 7, pos_end: 14),
29
+ Threatinator::Record.new("line3!", line_number: 3, pos_start: 14, pos_end: 20)
30
+ )
31
+ end
32
+ describe ":separator" do
33
+ it "should be accessible via the #separator method" do
34
+ parser = described_class.new(:separator => "Z")
35
+ expect(parser.separator).to eq("Z")
36
+ end
37
+ it "should specify a separator" do
38
+ str = "line 1\0line 2\0line3!"
39
+ sio = StringIO.new(str)
40
+ parser = described_class.new(:separator => "\0")
41
+
42
+ expect { |b|
43
+ parser.run(sio, &b)
44
+ }.to yield_successive_args(
45
+ Threatinator::Record.new("line 1\0", line_number: 1, pos_start: 0, pos_end: 7),
46
+ Threatinator::Record.new("line 2\0", line_number: 2, pos_start: 7, pos_end: 14),
47
+ Threatinator::Record.new("line3!", line_number: 3, pos_start: 14, pos_end: 20)
48
+ )
49
+ end
50
+ end
51
+
52
+ it "shouldn't split anything up if the separator isn't found" do
53
+ str = "line 1\0line 2\0line3!"
54
+ sio = StringIO.new(str)
55
+ parser = described_class.new(:separator => "\n")
56
+
57
+ expect { |b|
58
+ parser.run(sio, &b)
59
+ }.to yield_successive_args(
60
+ Threatinator::Record.new("line 1\0line 2\0line3!", line_number: 1, pos_start: 0, pos_end: 20)
61
+ )
62
+ end
63
+
64
+ it "should raise an ArgumentError if the separator isn't a single character" do
65
+ sio = StringIO.new()
66
+ expect { described_class.new(:separator => "asdf") }.to raise_error(ArgumentError)
67
+ end
68
+
69
+ it "should work on really, really long lines" do
70
+ line1 = ("A" * 100_000) + "\n"
71
+ line2 = ("B" * 100_000) + "\n"
72
+ line3 = "C" * 100_000
73
+ record1 = Threatinator::Record.new(line1, :line_number => 1, :pos_start => 0, :pos_end => 100_001)
74
+ record2 = Threatinator::Record.new(line2, :line_number => 2, :pos_start => 100_001, :pos_end => 200_002)
75
+ record3 = Threatinator::Record.new(line3, :line_number => 3, :pos_start => 200_002, :pos_end => 300_002)
76
+ sio = StringIO.new(line1 + line2 + line3)
77
+ parser = described_class.new(:separator => "\n")
78
+
79
+ expect { |b|
80
+ parser.run(sio, &b)
81
+ }.to yield_successive_args(record1, record2, record3)
82
+ end
83
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+ require 'threatinator/parsers/json/parser'
3
+ require 'stringio'
4
+ require 'multi_json'
5
+
6
+ describe Threatinator::Parsers::JSON::Parser do
7
+ it_should_behave_like "a parser when compared to an identically configured parser" do
8
+ let(:parser1) { described_class.new() }
9
+ let(:parser2) { described_class.new() }
10
+ end
11
+
12
+ let(:element1) {
13
+ {
14
+ 'id' => 1,
15
+ 'marbles' => [
16
+ { 'color' => 'red', 'count' => 20 },
17
+ { 'color' => 'blue', 'count' => 15 }
18
+ ],
19
+ 'owner' => { 'name' => 'Billy', 'age' => '6' }
20
+ }
21
+ }
22
+ let(:element2) {
23
+ {
24
+ 'id' => 2,
25
+ 'marbles' => [
26
+ { 'color' => 'red', 'count' => 3 },
27
+ { 'color' => 'green', 'count' => 37 }
28
+ ],
29
+ 'owner' => { 'name' => 'Sarah', 'age' => '7' }
30
+ }
31
+ }
32
+ let(:element3) {
33
+ {
34
+ 'id' => 3,
35
+ 'marbles' => [
36
+ { 'color' => 'purple', 'count' => 73 }
37
+ ],
38
+ 'owner' => { 'name' => 'Phil', 'age' => '5' }
39
+ }
40
+ }
41
+
42
+ context "when parsing a JSON string whose root element is an Array" do
43
+ let(:json_string) { MultiJson.dump([element1, element2, element3]) }
44
+ let(:io) { StringIO.new(json_string) }
45
+
46
+ it "should yield each of the array's elements as the :data of a Parsers::JSON::Record" do
47
+ parser = described_class.new()
48
+
49
+ expect { |b|
50
+ parser.run(io, &b)
51
+ }.to yield_successive_args(
52
+ Threatinator::Parsers::JSON::Record.new(element1),
53
+ Threatinator::Parsers::JSON::Record.new(element2),
54
+ Threatinator::Parsers::JSON::Record.new(element3)
55
+ )
56
+ end
57
+ end
58
+
59
+ context "when parsing a JSON string whose root element is a Hash" do
60
+ let(:json_string) { MultiJson.dump({'foo' => element1, 'bar' => element2, 'bla' => element3}) }
61
+ let(:io) { StringIO.new(json_string) }
62
+
63
+ it "should yield each of the hash's key/value pairs as the :key and :data of a Parsers::JSON::Record" do
64
+ parser = described_class.new()
65
+
66
+ expect { |b|
67
+ parser.run(io, &b)
68
+ }.to yield_successive_args(
69
+ Threatinator::Parsers::JSON::Record.new(element1, key: 'foo'),
70
+ Threatinator::Parsers::JSON::Record.new(element2, key: 'bar'),
71
+ Threatinator::Parsers::JSON::Record.new(element3, key: 'bla')
72
+ )
73
+ end
74
+ end
75
+
76
+ context "when parsing something that is not JSON data" do
77
+ let(:json_string) { "hi there! this is my data feed" }
78
+ let(:io) { StringIO.new(json_string) }
79
+
80
+ it "should raise a exception Threatinator::Exceptions::ParseError" do
81
+ parser = described_class.new()
82
+
83
+ expect { |b|
84
+ parser.run(io, &b)
85
+ }.to raise_error(Threatinator::Exceptions::ParseError)
86
+ end
87
+ end
88
+
89
+ context "when parsing a JSON that is truncated" do
90
+ let(:json_string) { MultiJson.dump({'foo' => element1, 'bar' => element2, 'bla' => element3})[0..-3] }
91
+ let(:io) { StringIO.new(json_string) }
92
+
93
+ it "should yield as many records as possible before raising a ParseError" do
94
+ parser = described_class.new()
95
+
96
+ expect { |b|
97
+ expect {
98
+ parser.run(io, &b)
99
+ }.to raise_error(Threatinator::Exceptions::ParseError)
100
+ }.to yield_successive_args(
101
+ Threatinator::Parsers::JSON::Record.new(element1, key: 'foo'),
102
+ Threatinator::Parsers::JSON::Record.new(element2, key: 'bar')
103
+ )
104
+ end
105
+ end
106
+ end