shadowbq-threatinator 0.5.0

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 (389) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +66 -0
  3. data/CONTRIBUTING.md +119 -0
  4. data/Gemfile +38 -0
  5. data/LICENSE +165 -0
  6. data/README.md +101 -0
  7. data/Rakefile +47 -0
  8. data/VERSION +1 -0
  9. data/bin/threatinator +5 -0
  10. data/bin/threatinator_loader +21 -0
  11. data/feeds/ET_block-ip_reputation.feed +27 -0
  12. data/feeds/ET_compromised-ip_reputation.feed +20 -0
  13. data/feeds/ET_openbadlist-ip_reputation.feed +36 -0
  14. data/feeds/alienvault-ip_reputation.feed +39 -0
  15. data/feeds/arbor_fastflux-domain_reputation.feed +19 -0
  16. data/feeds/arbor_ssh-ip_reputation.feed +24 -0
  17. data/feeds/autoshun_shunlist.feed +17 -0
  18. data/feeds/bambenek_c2_masterlist-domain_reputation.feed +16 -0
  19. data/feeds/bambenek_c2_masterlist-ip_reputation.feed +16 -0
  20. data/feeds/bambenek_dga_feed-domain_reputation.feed +16 -0
  21. data/feeds/berkeley-ip_reputation.feed +25 -0
  22. data/feeds/bitcash_cz_blacklist.feed +22 -0
  23. data/feeds/blocklist_de_apache-ip_reputation.feed +26 -0
  24. data/feeds/blocklist_de_bots-ip_reputation.feed +26 -0
  25. data/feeds/blocklist_de_ftp-ip_reputation.feed +25 -0
  26. data/feeds/blocklist_de_imap-ip_reputation.feed +25 -0
  27. data/feeds/blocklist_de_pop3-ip_reputation.feed +26 -0
  28. data/feeds/blocklist_de_proftpd-ip_reputation.feed +26 -0
  29. data/feeds/blocklist_de_sip-ip_reputation.feed +25 -0
  30. data/feeds/blocklist_de_ssh-ip_reputation.feed +25 -0
  31. data/feeds/blocklist_de_strongips-ip_reputation.feed +25 -0
  32. data/feeds/botscout-ip_reputation.feed +25 -0
  33. data/feeds/cert_mxpoison-ip_reputation.feed +22 -0
  34. data/feeds/chaosreigns-ip_reputation.feed +37 -0
  35. data/feeds/ciarmy-ip_reputation.feed +20 -0
  36. data/feeds/cruzit-ip_reputation.feed +30 -0
  37. data/feeds/cydef_torexit-ip_reputation.feed +25 -0
  38. data/feeds/dan_me_uk_torlist-ip_reputation.feed +25 -0
  39. data/feeds/danger_bruteforce-ip_reputation.feed +24 -0
  40. data/feeds/dshield_attackers-top1000.feed +34 -0
  41. data/feeds/falconcrest-ip_reputation.feed +19 -0
  42. data/feeds/feodo-domain_reputation.feed +19 -0
  43. data/feeds/feodo-ip_reputation.feed +20 -0
  44. data/feeds/h3x_asprox.feed +18 -0
  45. data/feeds/hosts-file_hphostspartial-domain_reputation.feed +19 -0
  46. data/feeds/infiltrated-ip_reputation.feed +26 -0
  47. data/feeds/infiltrated_vabl-ip_reputation.feed +30 -0
  48. data/feeds/isc_suspicious_high-domain_reputation.feed +26 -0
  49. data/feeds/isc_suspicious_low-domain_reputation.feed +26 -0
  50. data/feeds/isc_suspicious_medium-domain_reputation.feed +26 -0
  51. data/feeds/malc0de-domain_reputation.feed +24 -0
  52. data/feeds/malc0de-ip_reputation.feed +26 -0
  53. data/feeds/malwaredomainlist-url_reputation.feed +18 -0
  54. data/feeds/malwaredomains-domain_reputation.feed +29 -0
  55. data/feeds/malwaredomains_dyndns-domain_reputation.feed +29 -0
  56. data/feeds/malwaredomains_justdomains-domain_reputation.feed +20 -0
  57. data/feeds/mirc-domain_reputation.feed +30 -0
  58. data/feeds/multiproxy-ip_reputation.feed +22 -0
  59. data/feeds/nothink_irc-ip_reputation.feed +23 -0
  60. data/feeds/nothink_ssh-ip_reputation.feed +21 -0
  61. data/feeds/openbl-ip_reputation.feed +21 -0
  62. data/feeds/openphish-url_reputation.feed +24 -0
  63. data/feeds/packetmail_perimeterbad-ip_reputation.feed +28 -0
  64. data/feeds/palevo-domain_reputation.feed +22 -0
  65. data/feeds/palevo-ip_reputation.feed +23 -0
  66. data/feeds/phishtank.feed +22 -0
  67. data/feeds/sigmaproject_atma.feed +27 -0
  68. data/feeds/sigmaproject_spyware.feed +28 -0
  69. data/feeds/sigmaproject_webexploit.feed +26 -0
  70. data/feeds/snort_bpf-ip_reputation.feed +19 -0
  71. data/feeds/spyeye-domain_reputation.feed +18 -0
  72. data/feeds/spyeye-ip_reputation.feed +19 -0
  73. data/feeds/steeman-ip_reputation.feed +20 -0
  74. data/feeds/t-arend-de_ssh-ip_reputation.feed +20 -0
  75. data/feeds/the_haleys_ssh-ip_reputation.feed +20 -0
  76. data/feeds/trustedsec-ip_reputation.feed +18 -0
  77. data/feeds/virbl-ip_reputation.feed +25 -0
  78. data/feeds/vxvault-url_reputation.feed +23 -0
  79. data/feeds/yourcmc_ssh-ip_reputation.feed +20 -0
  80. data/feeds/yoyo_adservers-domain_reputation.feed +17 -0
  81. data/feeds/zeus-domain_reputation.feed +19 -0
  82. data/feeds/zeus-ip_reputation.feed +21 -0
  83. data/lib/threatinator/action.rb +14 -0
  84. data/lib/threatinator/actions/list/action.rb +97 -0
  85. data/lib/threatinator/actions/list/config.rb +12 -0
  86. data/lib/threatinator/actions/list.rb +2 -0
  87. data/lib/threatinator/actions/run/action.rb +57 -0
  88. data/lib/threatinator/actions/run/config.rb +32 -0
  89. data/lib/threatinator/actions/run/coverage_observer.rb +59 -0
  90. data/lib/threatinator/actions/run/output_config.rb +59 -0
  91. data/lib/threatinator/actions/run/status_observer.rb +37 -0
  92. data/lib/threatinator/actions/run.rb +2 -0
  93. data/lib/threatinator/cli/action_builder.rb +33 -0
  94. data/lib/threatinator/cli/list_action_builder.rb +19 -0
  95. data/lib/threatinator/cli/parser.rb +123 -0
  96. data/lib/threatinator/cli/run_action_builder.rb +41 -0
  97. data/lib/threatinator/cli.rb +19 -0
  98. data/lib/threatinator/config/base.rb +35 -0
  99. data/lib/threatinator/config/feed_search.rb +25 -0
  100. data/lib/threatinator/config/logger.rb +14 -0
  101. data/lib/threatinator/config.rb +7 -0
  102. data/lib/threatinator/decoder.rb +24 -0
  103. data/lib/threatinator/decoders/gzip.rb +30 -0
  104. data/lib/threatinator/event.rb +63 -0
  105. data/lib/threatinator/event_builder.rb +70 -0
  106. data/lib/threatinator/exceptions.rb +58 -0
  107. data/lib/threatinator/feed.rb +88 -0
  108. data/lib/threatinator/feed_builder.rb +161 -0
  109. data/lib/threatinator/feed_registry.rb +47 -0
  110. data/lib/threatinator/feed_runner.rb +177 -0
  111. data/lib/threatinator/fetcher.rb +22 -0
  112. data/lib/threatinator/fetchers/http.rb +50 -0
  113. data/lib/threatinator/filter.rb +12 -0
  114. data/lib/threatinator/filters/block.rb +18 -0
  115. data/lib/threatinator/filters/comments.rb +16 -0
  116. data/lib/threatinator/filters/whitespace.rb +19 -0
  117. data/lib/threatinator/logger.rb +66 -0
  118. data/lib/threatinator/logging.rb +20 -0
  119. data/lib/threatinator/model/base.rb +23 -0
  120. data/lib/threatinator/model/collection.rb +89 -0
  121. data/lib/threatinator/model/observables/fqdn_collection.rb +15 -0
  122. data/lib/threatinator/model/observables/ipv4.rb +33 -0
  123. data/lib/threatinator/model/observables/ipv4_collection.rb +14 -0
  124. data/lib/threatinator/model/observables/url_collection.rb +16 -0
  125. data/lib/threatinator/model/validations/type.rb +21 -0
  126. data/lib/threatinator/model/validations.rb +1 -0
  127. data/lib/threatinator/output.rb +50 -0
  128. data/lib/threatinator/parser.rb +23 -0
  129. data/lib/threatinator/parsers/csv/parser.rb +77 -0
  130. data/lib/threatinator/parsers/csv.rb +7 -0
  131. data/lib/threatinator/parsers/getline/parser.rb +45 -0
  132. data/lib/threatinator/parsers/getline.rb +8 -0
  133. data/lib/threatinator/parsers/json/adapters/oj.rb +65 -0
  134. data/lib/threatinator/parsers/json/parser.rb +45 -0
  135. data/lib/threatinator/parsers/json/record.rb +20 -0
  136. data/lib/threatinator/parsers/json.rb +8 -0
  137. data/lib/threatinator/parsers/xml/node.rb +79 -0
  138. data/lib/threatinator/parsers/xml/node_builder.rb +39 -0
  139. data/lib/threatinator/parsers/xml/parser.rb +44 -0
  140. data/lib/threatinator/parsers/xml/path.rb +70 -0
  141. data/lib/threatinator/parsers/xml/pattern.rb +53 -0
  142. data/lib/threatinator/parsers/xml/record.rb +14 -0
  143. data/lib/threatinator/parsers/xml/sax_document.rb +64 -0
  144. data/lib/threatinator/parsers/xml.rb +8 -0
  145. data/lib/threatinator/plugin_loader.rb +115 -0
  146. data/lib/threatinator/plugins/output/amqp/config.rb +18 -0
  147. data/lib/threatinator/plugins/output/amqp.rb +41 -0
  148. data/lib/threatinator/plugins/output/csv.rb +58 -0
  149. data/lib/threatinator/plugins/output/json/config.rb +14 -0
  150. data/lib/threatinator/plugins/output/json.rb +53 -0
  151. data/lib/threatinator/plugins/output/null.rb +17 -0
  152. data/lib/threatinator/plugins/output/rubydebug.rb +16 -0
  153. data/lib/threatinator/record.rb +22 -0
  154. data/lib/threatinator/registry.rb +53 -0
  155. data/lib/threatinator/util.rb +15 -0
  156. data/lib/threatinator.rb +3 -0
  157. data/spec/feeds/ET_block-ip_reputation_spec.rb +50 -0
  158. data/spec/feeds/ET_compromised-ip_reputation_spec.rb +47 -0
  159. data/spec/feeds/ET_openbadlist-ip_reputation_spec.rb +56 -0
  160. data/spec/feeds/alienvault-ip_reputation_spec.rb +46 -0
  161. data/spec/feeds/arbor_fastflux-domain_reputation_spec.rb +46 -0
  162. data/spec/feeds/arbor_ssh-ip_reputation_spec.rb +46 -0
  163. data/spec/feeds/autoshun_shunlist_spec.rb +38 -0
  164. data/spec/feeds/bambenek_c2_masterlist-domain_reputation_spec.rb +38 -0
  165. data/spec/feeds/bambenek_c2_masterlist-ip_reputation_spec.rb +39 -0
  166. data/spec/feeds/bambenek_dga_feed-domain_reputation_spec.rb +39 -0
  167. data/spec/feeds/berkeley-ip_reputation_spec.rb +47 -0
  168. data/spec/feeds/bitcash_cz_blacklist-ip_reputation_spec.rb +50 -0
  169. data/spec/feeds/blocklist_de_apache-ip_reputation_spec.rb +47 -0
  170. data/spec/feeds/blocklist_de_bots-ip_reputation_spec.rb +47 -0
  171. data/spec/feeds/blocklist_de_ftp-ip_reputation_spec.rb +47 -0
  172. data/spec/feeds/blocklist_de_imap-ip_reputation_spec.rb +47 -0
  173. data/spec/feeds/blocklist_de_pop3-ip_reputation_spec.rb +47 -0
  174. data/spec/feeds/blocklist_de_proftpd-ip_reputation_spec.rb +47 -0
  175. data/spec/feeds/blocklist_de_sip-ip_reputation_spec.rb +47 -0
  176. data/spec/feeds/blocklist_de_ssh-ip_reputation_spec.rb +47 -0
  177. data/spec/feeds/blocklist_de_strongips-ip_reputation_spec.rb +47 -0
  178. data/spec/feeds/botscout-ip_reputation_spec.rb +50 -0
  179. data/spec/feeds/cert_mxpoison-ip_reputation_spec.rb +47 -0
  180. data/spec/feeds/chaosreigns-ip_reputation_spec.rb +50 -0
  181. data/spec/feeds/ciarmy-ip_reputation_spec.rb +47 -0
  182. data/spec/feeds/cruzit-ip_reputation_spec.rb +47 -0
  183. data/spec/feeds/cydef_torexit-ip_reputation_spec.rb +47 -0
  184. data/spec/feeds/dan_me_uk_torlist-ip_reputation_spec.rb +47 -0
  185. data/spec/feeds/danger_bruteforce-ip_reputation_spec.rb +47 -0
  186. data/spec/feeds/data/ET_block-ip_reputation.txt +80 -0
  187. data/spec/feeds/data/ET_compromised-ip_reputation.txt +11 -0
  188. data/spec/feeds/data/ET_openbadlist-ip_reputation.txt +62 -0
  189. data/spec/feeds/data/alienvault-ip_reputation.txt +18 -0
  190. data/spec/feeds/data/arbor_domainlist.txt +11 -0
  191. data/spec/feeds/data/arbor_ssh.txt +16 -0
  192. data/spec/feeds/data/autoshun_shunlist.csv +20 -0
  193. data/spec/feeds/data/bambenek_c2-dommasterlist.csv +30 -0
  194. data/spec/feeds/data/bambenek_c2-ipmasterlist.csv +27 -0
  195. data/spec/feeds/data/bambenek_dga_feed.csv +42 -0
  196. data/spec/feeds/data/berkeley.txt +29 -0
  197. data/spec/feeds/data/bitcash_cz_blacklist.txt +7 -0
  198. data/spec/feeds/data/blocklist_de_apache-ip-reputation.txt +17 -0
  199. data/spec/feeds/data/blocklist_de_bots-ip-reputation.txt +15 -0
  200. data/spec/feeds/data/blocklist_de_ftp-ip-reputation.txt +7 -0
  201. data/spec/feeds/data/blocklist_de_imap-ip-reputation.txt +8 -0
  202. data/spec/feeds/data/blocklist_de_pop3-ip-reputation.txt +11 -0
  203. data/spec/feeds/data/blocklist_de_proftpd-ip-reputation.txt +12 -0
  204. data/spec/feeds/data/blocklist_de_sip-ip-reputation.txt +9 -0
  205. data/spec/feeds/data/blocklist_de_ssh-ip-reputation.txt +10 -0
  206. data/spec/feeds/data/blocklist_de_strongips-ip-reputation.txt +11 -0
  207. data/spec/feeds/data/botscout-ip-reputation.txt +713 -0
  208. data/spec/feeds/data/cert_mxpoison-ip_reputation.txt +17 -0
  209. data/spec/feeds/data/chaosreigns-ip-reputation.txt +26 -0
  210. data/spec/feeds/data/ciarmy-ip-reputation.txt +11 -0
  211. data/spec/feeds/data/cruzit-ip-reputation.txt +14 -0
  212. data/spec/feeds/data/cydef_torexit-ip_reputation.txt +27 -0
  213. data/spec/feeds/data/dan_me_uk_torlist-ip-reputation.txt +11 -0
  214. data/spec/feeds/data/danger_bruteforce-ip_reputation.txt +12 -0
  215. data/spec/feeds/data/dshield_topattackers.xml +4 -0
  216. data/spec/feeds/data/falconcrest_iplist.txt +345 -0
  217. data/spec/feeds/data/feodo_domainlist.txt +18 -0
  218. data/spec/feeds/data/feodo_iplist.txt +20 -0
  219. data/spec/feeds/data/h3x_asprox.txt +20 -0
  220. data/spec/feeds/data/hosts-file_hphostspartial_domainlist.txt +24 -0
  221. data/spec/feeds/data/infiltrated_iplist.txt +16 -0
  222. data/spec/feeds/data/infiltrated_vabl_iplist.txt +33 -0
  223. data/spec/feeds/data/isc_suspicious_high_domainlist.txt +26 -0
  224. data/spec/feeds/data/isc_suspicious_low_domainlist.txt +34 -0
  225. data/spec/feeds/data/isc_suspicious_medium_domainlist.txt +32 -0
  226. data/spec/feeds/data/malc0de_domainlist.txt +18 -0
  227. data/spec/feeds/data/malc0de_iplist.txt +14 -0
  228. data/spec/feeds/data/malwaredomainlist-url-reputation.txt +8 -0
  229. data/spec/feeds/data/malwaredomains_domainlist.txt +24 -0
  230. data/spec/feeds/data/malwaredomains_dyndns_domainlist.txt +34 -0
  231. data/spec/feeds/data/malwaredomains_justdomains_domainlist.txt +18 -0
  232. data/spec/feeds/data/mirc_domainlist.txt +31 -0
  233. data/spec/feeds/data/multiproxy_iplist.txt +15 -0
  234. data/spec/feeds/data/nothink_irc_iplist.txt +14 -0
  235. data/spec/feeds/data/nothink_ssh_iplist.txt +10 -0
  236. data/spec/feeds/data/openbl_iplist.txt +12 -0
  237. data/spec/feeds/data/openphish-url-reputation.txt +16 -0
  238. data/spec/feeds/data/packetmail_perimeterbad-ip_reputation.txt +44 -0
  239. data/spec/feeds/data/palevo_domainlist.txt +25 -0
  240. data/spec/feeds/data/palevo_iplist.txt +24 -0
  241. data/spec/feeds/data/phishtank-sample.json.gz +0 -0
  242. data/spec/feeds/data/sigmaproject_atma.return.gz +0 -0
  243. data/spec/feeds/data/sigmaproject_spyware.return.gz +0 -0
  244. data/spec/feeds/data/sigmaproject_webexploit.return.gz +0 -0
  245. data/spec/feeds/data/snort_bpf-ip_reputation.txt +16 -0
  246. data/spec/feeds/data/spyeye_domainlist.txt +16 -0
  247. data/spec/feeds/data/spyeye_iplist.txt +19 -0
  248. data/spec/feeds/data/steeman-ip-reputation.txt +13 -0
  249. data/spec/feeds/data/t-arend-de_ssh_iplist.txt +17 -0
  250. data/spec/feeds/data/the_haleys_ssh_iplist.txt +12 -0
  251. data/spec/feeds/data/trustedsec-ip-reputation.txt +12 -0
  252. data/spec/feeds/data/valid.json +2908 -0
  253. data/spec/feeds/data/virbl-ip_reputation.txt +14 -0
  254. data/spec/feeds/data/vxvault-url-reputation.txt +15 -0
  255. data/spec/feeds/data/yourcmc_ssh-ip_reputation.txt +27 -0
  256. data/spec/feeds/data/yoyo_adservers.txt +25 -0
  257. data/spec/feeds/data/zeus-ip_reputation.txt +285 -0
  258. data/spec/feeds/data/zeus_domainlist.txt +27 -0
  259. data/spec/feeds/dshield_attackers-top1000_spec.rb +39 -0
  260. data/spec/feeds/falconcrest-ip_reputation_spec.rb +39 -0
  261. data/spec/feeds/feodo-domain_reputation_spec.rb +47 -0
  262. data/spec/feeds/feodo-ip_reputation_spec.rb +47 -0
  263. data/spec/feeds/h3x_asprox-ip_reputation_spec.rb +50 -0
  264. data/spec/feeds/hosts-file_hphostspartial-domain_reputation_spec.rb +47 -0
  265. data/spec/feeds/infiltrated-ip_reputation_spec.rb +47 -0
  266. data/spec/feeds/infiltrated_vabl-ip_reputation_spec.rb +47 -0
  267. data/spec/feeds/isc_suspicious_high-domain_reputation_spec.rb +47 -0
  268. data/spec/feeds/isc_suspicious_low-domain_reputation_spec.rb +47 -0
  269. data/spec/feeds/isc_suspicious_medium-domain_reputation_spec.rb +47 -0
  270. data/spec/feeds/malc0de-domain_reputation_spec.rb +47 -0
  271. data/spec/feeds/malc0de-ip_reputation_spec.rb +47 -0
  272. data/spec/feeds/malwaredomainlist_url_reputation_spec.rb +50 -0
  273. data/spec/feeds/malwaredomains-domain_reputation_spec.rb +47 -0
  274. data/spec/feeds/malwaredomains_dyndns-domain_reputation_spec.rb +47 -0
  275. data/spec/feeds/malwaredomains_justdomains-domain_reputation_spec.rb +47 -0
  276. data/spec/feeds/mirc-domain_reputation_spec.rb +47 -0
  277. data/spec/feeds/multiproxy-ip_reputation_spec.rb +47 -0
  278. data/spec/feeds/nothink_irc-ip_reputation_spec.rb +47 -0
  279. data/spec/feeds/nothink_ssh-ip_reputation_spec.rb +47 -0
  280. data/spec/feeds/openbl-ip_reputation_spec.rb +47 -0
  281. data/spec/feeds/openphish_url_reputation_spec.rb +50 -0
  282. data/spec/feeds/packetmail_perimeterbad-ip_reputation_spec.rb +47 -0
  283. data/spec/feeds/palevo-domain_reputation_spec.rb +47 -0
  284. data/spec/feeds/palevo-ip_reputation_spec.rb +47 -0
  285. data/spec/feeds/phishtank_spec.rb +41 -0
  286. data/spec/feeds/sigmaproject_atma_spec.rb +62 -0
  287. data/spec/feeds/sigmaproject_spyware_spec.rb +63 -0
  288. data/spec/feeds/sigmaproject_webexploit_spec.rb +62 -0
  289. data/spec/feeds/snort_bpf-ip_reputation_spec.rb +47 -0
  290. data/spec/feeds/spyeye-domain_reputation_spec.rb +47 -0
  291. data/spec/feeds/spyeye-ip_reputation_spec.rb +47 -0
  292. data/spec/feeds/steeman-ip_reputation_spec.rb +50 -0
  293. data/spec/feeds/t-arend-de_ssh-ip_reputation_spec.rb +47 -0
  294. data/spec/feeds/the_haleys_ssh-ip_reputation_spec.rb +47 -0
  295. data/spec/feeds/trustedsec-ip_reputation_spec.rb +47 -0
  296. data/spec/feeds/virbl-ip_reputation_spec.rb +47 -0
  297. data/spec/feeds/vxvault_url_reputation_spec.rb +50 -0
  298. data/spec/feeds/yourcmc_ssh-ip_reputation_spec.rb +47 -0
  299. data/spec/feeds/yoyo_adservers_spec.rb +47 -0
  300. data/spec/feeds/zeus-domain_reputation_spec.rb +47 -0
  301. data/spec/feeds/zeus-ip_reputation_spec.rb +47 -0
  302. data/spec/fixtures/feed/provider1/feed1.feed +6 -0
  303. data/spec/fixtures/parsers/test.xml +13 -0
  304. data/spec/fixtures/parsers/test_self_closing.xml +20 -0
  305. data/spec/fixtures/plugins/bad/threatinator/plugins/test_error1/plugin.rb +1 -0
  306. data/spec/fixtures/plugins/bad/threatinator/plugins/test_missing1/plugin.rb +0 -0
  307. data/spec/fixtures/plugins/fake.rb +19 -0
  308. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_a.rb +8 -0
  309. data/spec/fixtures/plugins/good/threatinator/plugins/test_type1/plugin_b.rb +8 -0
  310. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_c.rb +8 -0
  311. data/spec/fixtures/plugins/good/threatinator/plugins/test_type2/plugin_d.rb +8 -0
  312. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_e.rb +8 -0
  313. data/spec/fixtures/plugins/good/threatinator/plugins/test_type3/plugin_f.rb +8 -0
  314. data/spec/spec_helper.rb +54 -0
  315. data/spec/support/bad_feeds/missing_fetcher.feed +7 -0
  316. data/spec/support/bad_feeds/missing_name.feed +6 -0
  317. data/spec/support/bad_feeds/missing_parser.feed +3 -0
  318. data/spec/support/bad_feeds/missing_provider.feed +5 -0
  319. data/spec/support/factories/event.rb +31 -0
  320. data/spec/support/factories/feed.rb +59 -0
  321. data/spec/support/factories/feed_builder.rb +65 -0
  322. data/spec/support/factories/feed_registry.rb +8 -0
  323. data/spec/support/factories/ipv4.rb +36 -0
  324. data/spec/support/factories/output.rb +11 -0
  325. data/spec/support/factories/record.rb +17 -0
  326. data/spec/support/factories/url.rb +34 -0
  327. data/spec/support/factories/xml_node.rb +33 -0
  328. data/spec/support/helpers/io.rb +11 -0
  329. data/spec/support/helpers/models.rb +13 -0
  330. data/spec/support/shared/action_builder.rb +47 -0
  331. data/spec/support/shared/decoder.rb +70 -0
  332. data/spec/support/shared/feed_runner_observer.rb +136 -0
  333. data/spec/support/shared/feeds.rb +233 -0
  334. data/spec/support/shared/fetcher.rb +48 -0
  335. data/spec/support/shared/filter.rb +14 -0
  336. data/spec/support/shared/io-like.rb +7 -0
  337. data/spec/support/shared/model/collection.rb +164 -0
  338. data/spec/support/shared/output.rb +120 -0
  339. data/spec/support/shared/parsers.rb +51 -0
  340. data/spec/support/shared/record.rb +111 -0
  341. data/spec/threatinator/actions/list/action_spec.rb +148 -0
  342. data/spec/threatinator/actions/run/action_spec.rb +106 -0
  343. data/spec/threatinator/actions/run/config_spec.rb +39 -0
  344. data/spec/threatinator/actions/run/coverage_observer_spec.rb +151 -0
  345. data/spec/threatinator/actions/run/output_config_spec.rb +89 -0
  346. data/spec/threatinator/actions/run/status_observer_spec.rb +86 -0
  347. data/spec/threatinator/cli/list_action_builder_spec.rb +57 -0
  348. data/spec/threatinator/cli/run_action_builder_spec.rb +133 -0
  349. data/spec/threatinator/cli_spec.rb +175 -0
  350. data/spec/threatinator/config/base_spec.rb +39 -0
  351. data/spec/threatinator/config/feed_search_spec.rb +76 -0
  352. data/spec/threatinator/decoders/gzip_spec.rb +75 -0
  353. data/spec/threatinator/event_builder_spec.rb +123 -0
  354. data/spec/threatinator/event_spec.rb +254 -0
  355. data/spec/threatinator/event_spec.rb.new +319 -0
  356. data/spec/threatinator/feed_builder_spec.rb +633 -0
  357. data/spec/threatinator/feed_registry_spec.rb +198 -0
  358. data/spec/threatinator/feed_runner_spec.rb +372 -0
  359. data/spec/threatinator/feed_spec.rb +169 -0
  360. data/spec/threatinator/fetcher_spec.rb +12 -0
  361. data/spec/threatinator/fetchers/http_spec.rb +32 -0
  362. data/spec/threatinator/filter_spec.rb +13 -0
  363. data/spec/threatinator/filters/block_spec.rb +16 -0
  364. data/spec/threatinator/filters/comments_spec.rb +13 -0
  365. data/spec/threatinator/filters/whitespace_spec.rb +12 -0
  366. data/spec/threatinator/logger_spec.rb +29 -0
  367. data/spec/threatinator/model/observables/fqdn_collection_spec.rb +41 -0
  368. data/spec/threatinator/model/observables/ipv4_collection_spec.rb +36 -0
  369. data/spec/threatinator/model/observables/ipv4_spec.rb +75 -0
  370. data/spec/threatinator/model/observables/url_collection_spec.rb +45 -0
  371. data/spec/threatinator/model/validations/type_spec.rb +37 -0
  372. data/spec/threatinator/parser_spec.rb +13 -0
  373. data/spec/threatinator/parsers/csv/parser_spec.rb +202 -0
  374. data/spec/threatinator/parsers/getline/parser_spec.rb +83 -0
  375. data/spec/threatinator/parsers/json/parser_spec.rb +106 -0
  376. data/spec/threatinator/parsers/json/record_spec.rb +30 -0
  377. data/spec/threatinator/parsers/xml/node_spec.rb +335 -0
  378. data/spec/threatinator/parsers/xml/parser_spec.rb +263 -0
  379. data/spec/threatinator/parsers/xml/path_spec.rb +209 -0
  380. data/spec/threatinator/parsers/xml/pattern_spec.rb +72 -0
  381. data/spec/threatinator/parsers/xml/record_spec.rb +27 -0
  382. data/spec/threatinator/plugin_loader_spec.rb +238 -0
  383. data/spec/threatinator/plugins/output/csv_spec.rb +47 -0
  384. data/spec/threatinator/plugins/output/null_spec.rb +17 -0
  385. data/spec/threatinator/plugins/output/rubydebug_spec.rb +37 -0
  386. data/spec/threatinator/record_spec.rb +19 -0
  387. data/spec/threatinator/registry_spec.rb +97 -0
  388. data/spec/threatinator/runner_spec.rb +273 -0
  389. metadata +674 -0
@@ -0,0 +1,23 @@
1
+ require 'active_model'
2
+ require 'active_model/validations'
3
+ require 'threatinator/model/validations'
4
+ require 'threatinator/exceptions'
5
+
6
+ module Threatinator
7
+ module Model
8
+ class Base
9
+ include ActiveModel::Validations
10
+ include Threatinator::Model::Validations
11
+
12
+ def initialize
13
+ validate!
14
+ end
15
+
16
+ def validate!
17
+ unless valid?
18
+ raise Threatinator::Exceptions::InvalidAttributeError, errors.full_messages.join("\n")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,89 @@
1
+ require 'threatinator/exceptions'
2
+ require 'set'
3
+
4
+ module Threatinator
5
+ module Model
6
+ class Collection
7
+ def initialize(values = [])
8
+ @collection = Set.new
9
+ values.each do |v|
10
+ self << v
11
+ end
12
+ end
13
+
14
+ def valid_member?(v)
15
+ #:nocov:
16
+ raise NotImplementedError, "#valid_member? not implemented"
17
+ #:nocov:
18
+ end
19
+
20
+ def <<(v)
21
+ unless valid_member?(v)
22
+ raise Threatinator::Exceptions::InvalidAttributeError, "Invalid member: #{v.class} '#{v.inspect}'"
23
+ end
24
+ @collection << v
25
+ end
26
+
27
+ def include?(member)
28
+ @collection.include?(member)
29
+ end
30
+ alias_method :member?, :include?
31
+
32
+ # @return [Boolean] true if empty, false otherwise
33
+ def empty?
34
+ @collection.empty?
35
+ end
36
+
37
+ def collect!
38
+ block_given? or return enum_for(__method__)
39
+ @collection.replace(@collection.class.new(@collection) { |o| yield(o) })
40
+ end
41
+
42
+ def delete(o)
43
+ @collection.delete(o)
44
+ end
45
+
46
+ def delete?(o)
47
+ @collection.delete?(o)
48
+ end
49
+
50
+ # @return [Integer] the number of members in the collection
51
+ def count
52
+ @collection.count
53
+ end
54
+ alias_method :size, :count
55
+ alias_method :length, :count
56
+
57
+ def to_ary
58
+ @collection.to_a
59
+ end
60
+ alias_method :to_a, :to_ary
61
+
62
+ # [31] pry(#<Threatinator::Plugins::Output::Json>)> event.urls.each{ |uri| p uri.to_s }
63
+ # "http://teamadrenaline.com/js/t1.exe"
64
+ # => [#<Addressable::URI:0x114c6ec URI:http://teamadrenaline.com/js/t1.exe>]
65
+ def each
66
+ return to_enum(:each) unless block_given?
67
+ @collection.each { |v| yield v }
68
+ end
69
+
70
+ def list
71
+ @collection.to_a.collect {|item|
72
+ item.to_s
73
+ }
74
+ end
75
+
76
+ def ==(other)
77
+ if self.equal?(other)
78
+ return true
79
+ elsif other.instance_of?(self.class)
80
+ @collection == other.instance_variable_get(:@collection)
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,15 @@
1
+ require 'threatinator/model/collection'
2
+ require 'domain_name_validator'
3
+
4
+ module Threatinator
5
+ module Model
6
+ module Observables
7
+ class FqdnCollection < Threatinator::Model::Collection
8
+ def valid_member?(v)
9
+ #v.is_a?(::String)
10
+ ::DomainNameValidator.new.validate(v.to_s)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ require 'threatinator/model/base'
2
+ require 'ip'
3
+ require 'equalizer'
4
+
5
+ module Threatinator
6
+ module Model
7
+ module Observables
8
+ class Ipv4 < Threatinator::Model::Base
9
+ include Equalizer.new(:ipv4)
10
+ attr_reader :ipv4
11
+
12
+ validates_each :ipv4 do |record, attr, value|
13
+ if value.is_a?(::IP::V4)
14
+ record.errors.add(attr, 'prefix length is not 32 bits') unless value.pfxlen == 32
15
+ else
16
+ record.errors.add(attr, 'not an IP::V4 object')
17
+ end
18
+ end
19
+
20
+ # @param [Hash] opts
21
+ # @option opts [IP::V4] :ipv4 An ipv4 object
22
+ def initialize(opts = {})
23
+ @ipv4 = opts.delete(:ipv4)
24
+ super()
25
+ end
26
+
27
+ def to_s
28
+ return ipv4.to_s
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ require 'threatinator/model/collection'
2
+ require 'threatinator/model/observables/ipv4'
3
+
4
+ module Threatinator
5
+ module Model
6
+ module Observables
7
+ class Ipv4Collection < Threatinator::Model::Collection
8
+ def valid_member?(v)
9
+ v.kind_of?(Threatinator::Model::Observables::Ipv4)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'threatinator/model/collection'
2
+ require 'addressable/uri'
3
+
4
+ module Threatinator
5
+ module Model
6
+ module Observables
7
+ class UrlCollection < Threatinator::Model::Collection
8
+ def valid_member?(v)
9
+ v.is_a?(::Addressable::URI) &&
10
+ v.absolute?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,21 @@
1
+ require 'active_model'
2
+ require 'active_model/validations'
3
+
4
+ module Threatinator
5
+ module Model
6
+ module Validations
7
+ class TypeValidator < ActiveModel::EachValidator
8
+ def initialize(options)
9
+ @type = options.delete(:with)
10
+ super
11
+ end
12
+
13
+ def validate_each(record, name, value)
14
+ unless value.is_a?(@type)
15
+ record.errors.add name, "Expected to be #{@type}, got #{value.class} #{value.inspect}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1 @@
1
+ require 'threatinator/model/validations/type'
@@ -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,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
@@ -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,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 Getline
4
+ require 'threatinator/parsers/getline/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 JSON
4
+ require 'threatinator/parsers/json/parser'
5
+ end
6
+ end
7
+ end
8
+
@@ -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