mihari 5.2.1 → 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/lib/mihari/analyzers/base.rb +20 -115
  4. data/lib/mihari/analyzers/binaryedge.rb +0 -1
  5. data/lib/mihari/analyzers/censys.rb +26 -3
  6. data/lib/mihari/analyzers/circl.rb +1 -1
  7. data/lib/mihari/analyzers/onyphe.rb +1 -1
  8. data/lib/mihari/analyzers/passivetotal.rb +1 -1
  9. data/lib/mihari/analyzers/rule.rb +122 -75
  10. data/lib/mihari/analyzers/shodan.rb +1 -1
  11. data/lib/mihari/analyzers/urlscan.rb +6 -9
  12. data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -6
  13. data/lib/mihari/cli/main.rb +2 -2
  14. data/lib/mihari/clients/base.rb +1 -1
  15. data/lib/mihari/commands/database.rb +12 -11
  16. data/lib/mihari/commands/rule.rb +47 -45
  17. data/lib/mihari/commands/search.rb +88 -0
  18. data/lib/mihari/commands/version.rb +8 -6
  19. data/lib/mihari/commands/web.rb +26 -23
  20. data/lib/mihari/emitters/base.rb +14 -1
  21. data/lib/mihari/emitters/database.rb +3 -10
  22. data/lib/mihari/emitters/misp.rb +16 -5
  23. data/lib/mihari/emitters/slack.rb +13 -15
  24. data/lib/mihari/emitters/the_hive.rb +17 -19
  25. data/lib/mihari/emitters/webhook.rb +23 -23
  26. data/lib/mihari/enrichers/whois.rb +1 -0
  27. data/lib/mihari/feed/parser.rb +1 -0
  28. data/lib/mihari/feed/reader.rb +29 -14
  29. data/lib/mihari/mixins/configurable.rb +13 -4
  30. data/lib/mihari/mixins/error_notification.rb +0 -2
  31. data/lib/mihari/models/artifact.rb +1 -1
  32. data/lib/mihari/schemas/rule.rb +2 -17
  33. data/lib/mihari/structs/censys.rb +226 -56
  34. data/lib/mihari/structs/config.rb +48 -18
  35. data/lib/mihari/structs/google_public_dns.rb +56 -14
  36. data/lib/mihari/structs/greynoise.rb +122 -29
  37. data/lib/mihari/structs/ipinfo.rb +40 -0
  38. data/lib/mihari/structs/onyphe.rb +112 -26
  39. data/lib/mihari/structs/rule.rb +4 -2
  40. data/lib/mihari/structs/shodan.rb +189 -47
  41. data/lib/mihari/structs/urlscan.rb +123 -20
  42. data/lib/mihari/structs/virustotal_intelligence.rb +129 -26
  43. data/lib/mihari/type_checker.rb +10 -8
  44. data/lib/mihari/version.rb +1 -1
  45. data/lib/mihari.rb +1 -0
  46. data/mihari.gemspec +11 -10
  47. metadata +35 -36
  48. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
  49. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
  50. data/.github/workflows/test.yml +0 -90
  51. data/config/pre_commit.yml +0 -3
  52. data/docker/Dockerfile +0 -14
  53. data/examples/ipinfo_hosted_domains.rb +0 -45
  54. data/images/Tines-Full_Logo-Tines_Black.png +0 -0
  55. data/images/alert.png +0 -0
  56. data/images/logo.png +0 -0
  57. data/images/misp.png +0 -0
  58. data/images/overview.jpg +0 -0
  59. data/images/slack.png +0 -0
  60. data/images/tines.png +0 -0
  61. data/images/web_alerts.png +0 -0
  62. data/images/web_config.png +0 -0
  63. data/lib/mihari/commands/searcher.rb +0 -61
@@ -5,60 +5,62 @@ require "pathname"
5
5
  module Mihari
6
6
  module Commands
7
7
  module Rule
8
- def self.included(thor)
9
- thor.class_eval do
10
- desc "validate [PATH]", "Validate a rule file"
11
- #
12
- # Validate format of a rule
13
- #
14
- # @param [String] path
15
- #
16
- def validate(path)
17
- rule = Structs::Rule.from_path_or_id(path)
18
-
19
- begin
20
- rule.validate!
21
- Mihari.logger.info "Valid format. The input is parsed as the following:"
22
- Mihari.logger.info rule.data.to_yaml
23
- rescue RuleValidationError
24
- nil
25
- end
26
- end
27
-
28
- desc "init [PATH]", "Initialize a new rule file"
29
- #
30
- # Initialize a new rule file
31
- #
32
- # @param [String] path
33
- #
34
- #
35
- def init(path = "./rule.yml")
36
- warning = "#{path} exists. Do you want to overwrite it? (y/n)"
37
- return if Pathname(path).exist? && !(yes? warning)
38
-
39
- initialize_rule path
40
-
41
- Mihari.logger.info "A new rule is initialized: #{path}."
42
- end
43
-
44
- no_commands do
8
+ class << self
9
+ def included(thor)
10
+ thor.class_eval do
11
+ desc "validate [PATH]", "Validate a rule file"
45
12
  #
46
- # @return [Mihari::Structs::Rule]
13
+ # Validate format of a rule
47
14
  #
48
- def rule_template
49
- Structs::Rule.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
15
+ # @param [String] path
16
+ #
17
+ def validate(path)
18
+ rule = Structs::Rule.from_path_or_id(path)
19
+
20
+ begin
21
+ rule.validate!
22
+ Mihari.logger.info "Valid format. The input is parsed as the following:"
23
+ Mihari.logger.info rule.data.to_yaml
24
+ rescue RuleValidationError
25
+ nil
26
+ end
50
27
  end
51
28
 
29
+ desc "init [PATH]", "Initialize a new rule file"
52
30
  #
53
- # Create a new rule
31
+ # Initialize a new rule file
54
32
  #
55
33
  # @param [String] path
56
- # @param [Dry::Files] files
57
34
  #
58
- # @return [nil]
59
35
  #
60
- def initialize_rule(path, files = Dry::Files.new)
61
- files.write(path, rule_template.yaml)
36
+ def init(path = "./rule.yml")
37
+ warning = "#{path} exists. Do you want to overwrite it? (y/n)"
38
+ return if Pathname(path).exist? && !(yes? warning)
39
+
40
+ initialize_rule path
41
+
42
+ Mihari.logger.info "A new rule file has been initialized: #{path}."
43
+ end
44
+
45
+ no_commands do
46
+ #
47
+ # @return [Mihari::Structs::Rule]
48
+ #
49
+ def rule_template
50
+ Structs::Rule.from_path File.expand_path("../templates/rule.yml.erb", __dir__)
51
+ end
52
+
53
+ #
54
+ # Create a new rule
55
+ #
56
+ # @param [String] path
57
+ # @param [Dry::Files] files
58
+ #
59
+ # @return [nil]
60
+ #
61
+ def initialize_rule(path, files = Dry::Files.new)
62
+ files.write(path, rule_template.yaml)
63
+ end
62
64
  end
63
65
  end
64
66
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module Search
6
+ class << self
7
+ class RuleWrapper
8
+ include Mixins::ErrorNotification
9
+
10
+ # @return [Nihari::Structs::Rule]
11
+ attr_reader :rule
12
+
13
+ # @return [Boolean]
14
+ attr_reader :force_overwrite
15
+
16
+ def initialize(rule:, force_overwrite:)
17
+ @rule = rule
18
+ @force_overwrite = force_overwrite
19
+ end
20
+
21
+ def force_overwrite?
22
+ force_overwrite
23
+ end
24
+
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ def diff?
29
+ model = Mihari::Rule.find(rule.id)
30
+ model.data != rule.data.deep_stringify_keys
31
+ rescue ActiveRecord::RecordNotFound
32
+ false
33
+ end
34
+
35
+ def update_or_create
36
+ rule.model.save
37
+ end
38
+
39
+ def run
40
+ with_error_notification do
41
+ alert = rule.analyzer.run
42
+ if alert
43
+ data = Mihari::Entities::Alert.represent(alert)
44
+ puts JSON.pretty_generate(data.as_json)
45
+ else
46
+ Mihari.logger.info "There is no new artifact found"
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def included(thor)
53
+ thor.class_eval do
54
+ desc "search [PATH]", "Search by a rule"
55
+ method_option :force_overwrite, type: :boolean, aliases: "-f", desc: "Force an overwrite the rule"
56
+ #
57
+ # Search by a rule
58
+ #
59
+ # @param [String] path_or_id
60
+ #
61
+ def search(path_or_id)
62
+ Mihari::Database.with_db_connection do
63
+ rule = Structs::Rule.from_path_or_id path_or_id
64
+
65
+ begin
66
+ rule.validate!
67
+ rescue RuleValidationError
68
+ return
69
+ end
70
+
71
+ force_overwrite = options["force_overwrite"] || false
72
+ wrapper = RuleWrapper.new(rule: rule, force_overwrite: force_overwrite)
73
+
74
+ if wrapper.diff? && !force_overwrite
75
+ message = "There is diff in the rule (#{rule.id}). Are you sure you want to overwrite the rule? (y/n)"
76
+ return unless yes?(message)
77
+ end
78
+
79
+ wrapper.update_or_create
80
+ wrapper.run
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -3,13 +3,15 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Version
6
- def self.included(thor)
7
- thor.class_eval do
8
- map %w[--version -v] => :__print_version
6
+ class << self
7
+ def included(thor)
8
+ thor.class_eval do
9
+ map %w[--version -v] => :__print_version
9
10
 
10
- desc "--version, -v", "Print the version"
11
- def __print_version
12
- puts Mihari::VERSION
11
+ desc "--version, -v", "Print the version"
12
+ def __print_version
13
+ puts Mihari::VERSION
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -3,29 +3,32 @@
3
3
  module Mihari
4
4
  module Commands
5
5
  module Web
6
- def self.included(thor)
7
- thor.class_eval do
8
- desc "web", "Launch the web app"
9
- method_option :port, type: :numeric, default: 9292, desc: "Hostname to listen on"
10
- method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
11
- method_option :threads, type: :string, default: "0:5", desc: "min:max threads to use"
12
- method_option :verbose, type: :boolean, default: true, desc: "Report each request"
13
- method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
14
- method_option :hide_config_values, type: :boolean, default: false,
15
- desc: "Whether to hide config values or not"
16
- method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
17
- def web
18
- Mihari.config.hide_config_values = options["hide_config_values"]
19
- # set rack env as production
20
- ENV["RACK_ENV"] ||= "production"
21
- Mihari::App.run!(
22
- port: options["port"],
23
- host: options["host"],
24
- threads: options["threads"],
25
- verbose: options["verbose"],
26
- worker_timeout: options["worker_timeout"],
27
- open: options["open"]
28
- )
6
+ class << self
7
+ def included(thor)
8
+ thor.class_eval do
9
+ desc "web", "Launch the web app"
10
+ method_option :port, type: :numeric, default: 9292, desc: "Hostname to listen on"
11
+ method_option :host, type: :string, default: "localhost", desc: "Port to listen on"
12
+ method_option :threads, type: :string, default: "0:5", desc: "min:max threads to use"
13
+ method_option :verbose, type: :boolean, default: true, desc: "Report each request"
14
+ method_option :worker_timeout, type: :numeric, default: 60, desc: "Worker timeout value (in seconds)"
15
+ method_option :hide_config_values, type: :boolean, default: false,
16
+ desc: "Whether to hide config values or not"
17
+ method_option :open, type: :boolean, default: true, desc: "Whether to open the app in browser or not"
18
+ method_option :rack_env, type: :string, default: "production", desc: "Rack environment"
19
+ def web
20
+ Mihari.config.hide_config_values = options["hide_config_values"]
21
+ # set rack env as production
22
+ ENV["RACK_ENV"] ||= options["rack_env"]
23
+ Mihari::App.run!(
24
+ port: options["port"],
25
+ host: options["host"],
26
+ threads: options["threads"],
27
+ verbose: options["verbose"],
28
+ worker_timeout: options["worker_timeout"],
29
+ open: options["open"]
30
+ )
31
+ end
29
32
  end
30
33
  end
31
34
  end
@@ -6,7 +6,20 @@ module Mihari
6
6
  include Mixins::Configurable
7
7
  include Mixins::Retriable
8
8
 
9
- def initialize(*)
9
+ # @return [Array<Mihari::Artifact>]
10
+ attr_reader :artifacts
11
+
12
+ # @return [Mihari::Structs::Rule]
13
+ attr_reader :rule
14
+
15
+ #
16
+ # @param [Array<Mihari::Artifact>] artifacts
17
+ # @param [Mihari::Structs::Rule] rule
18
+ # @param [Hash] **_options
19
+ #
20
+ def initialize(artifacts:, rule:, **_options)
21
+ @artifacts = artifacts
22
+ @rule = rule
10
23
  end
11
24
 
12
25
  class << self
@@ -4,28 +4,21 @@ module Mihari
4
4
  module Emitters
5
5
  class Database < Base
6
6
  def valid?
7
- true
7
+ configured?
8
8
  end
9
9
 
10
10
  #
11
11
  # Create an alert
12
12
  #
13
- # @param [Arra<Mihari::Artifact>] artifacts
14
- # @param [Mihari::Structs::Rule] rule
15
- #
16
13
  # @return [Mihari::Alert]
17
14
  #
18
- def emit(artifacts:, rule:)
15
+ def emit
19
16
  return if artifacts.empty?
20
17
 
21
18
  tags = rule.tags.filter_map { |name| Tag.find_or_create_by(name: name) }.uniq
22
19
  taggings = tags.map { |tag| Tagging.new(tag_id: tag.id) }
23
20
 
24
- alert = Alert.new(
25
- artifacts: artifacts,
26
- taggings: taggings,
27
- rule_id: rule.id
28
- )
21
+ alert = Alert.new(artifacts: artifacts, taggings: taggings, rule_id: rule.id)
29
22
  alert.save
30
23
  alert
31
24
  end
@@ -9,11 +9,22 @@ module Mihari
9
9
  # @return [String, nil]
10
10
  attr_reader :api_key
11
11
 
12
- def initialize(*args, **kwargs)
13
- super(*args, **kwargs)
12
+ # @return [Array<Mihari::Artifact>]
13
+ attr_reader :artifacts
14
14
 
15
- @url = kwargs[:url] || Mihari.config.misp_url
16
- @api_key = kwargs[:api_key] || Mihari.config.misp_api_key
15
+ # @return [Mihari::Structs::Rule]
16
+ attr_reader :rule
17
+
18
+ #
19
+ # @param [Array<Mihari::Artifact>] artifacts
20
+ # @param [Mihari::Structs::Rule] rule
21
+ # @param [Hash] **options
22
+ #
23
+ def initialize(artifacts:, rule:, **options)
24
+ super(artifacts: artifacts, rule: rule, **options)
25
+
26
+ @url = options[:url] || Mihari.config.misp_url
27
+ @api_key = options[:api_key] || Mihari.config.misp_api_key
17
28
  end
18
29
 
19
30
  # @return [Boolean]
@@ -40,7 +51,7 @@ module Mihari
40
51
  #
41
52
  # @return [::MISP::Event]
42
53
  #
43
- def emit(rule:, artifacts:, **_options)
54
+ def emit
44
55
  return if artifacts.empty?
45
56
 
46
57
  client.create_event({
@@ -121,11 +121,16 @@ module Mihari
121
121
  # @return [String]
122
122
  attr_reader :username
123
123
 
124
- def initialize(*args, **kwargs)
125
- super(*args, **kwargs)
124
+ #
125
+ # @param [Array<Mihari::Artifact>] artifacts
126
+ # @param [Mihari::Structs::Rule] rule
127
+ # @param [Hash] **_options
128
+ #
129
+ def initialize(artifacts:, rule:, **options)
130
+ super(artifacts: artifacts, rule: rule, **options)
126
131
 
127
- @webhook_url = kwargs[:webhook_url] || Mihari.config.slack_webhook_url
128
- @channel = kwargs[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
132
+ @webhook_url = options[:webhook_url] || Mihari.config.slack_webhook_url
133
+ @channel = options[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
129
134
  @username = DEFAULT_USERNAME
130
135
  end
131
136
 
@@ -152,13 +157,11 @@ module Mihari
152
157
  end
153
158
 
154
159
  #
155
- # Build attachements
156
- #
157
- # @param [Array<Mihari::Artifact>] artifacts
160
+ # Build attachments
158
161
  #
159
162
  # @return [Array<Mihari::Emitters::Attachment>]
160
163
  #
161
- def to_attachments(artifacts)
164
+ def attachments
162
165
  artifacts.map do |artifact|
163
166
  Attachment.new(data: artifact.data, data_type: artifact.data_type).to_a
164
167
  end.flatten
@@ -167,11 +170,9 @@ module Mihari
167
170
  #
168
171
  # Build a text
169
172
  #
170
- # @param [Mihari::Structs::Rule] rule
171
- #
172
173
  # @return [String]
173
174
  #
174
- def to_text(rule)
175
+ def text
175
176
  tags = rule.tags
176
177
  tags = ["N/A"] if tags.empty?
177
178
 
@@ -182,12 +183,9 @@ module Mihari
182
183
  ].join("\n")
183
184
  end
184
185
 
185
- def emit(rule:, artifacts:, **_options)
186
+ def emit
186
187
  return if artifacts.empty?
187
188
 
188
- attachments = to_attachments(artifacts)
189
- text = to_text(rule)
190
-
191
189
  notifier.post(text: text, attachments: attachments, mrkdwn: true)
192
190
  end
193
191
 
@@ -12,12 +12,17 @@ module Mihari
12
12
  # @return [String, nil]
13
13
  attr_reader :api_version
14
14
 
15
- def initialize(*args, **kwargs)
16
- super(*args, **kwargs)
15
+ #
16
+ # @param [Array<Mihari::Artifact>] artifacts
17
+ # @param [Mihari::Structs::Rule] rule
18
+ # @param [Hash] **options
19
+ #
20
+ def initialize(artifacts:, rule:, **options)
21
+ super(artifacts: artifacts, rule: rule, **options)
17
22
 
18
- @url = kwargs[:url] || Mihari.config.thehive_url
19
- @api_key = kwargs[:api_key] || Mihari.config.thehive_api_key
20
- @api_version = kwargs[:api_version] || Mihari.config.thehive_api_version
23
+ @url = options[:url] || Mihari.config.thehive_url
24
+ @api_key = options[:api_key] || Mihari.config.thehive_api_key
25
+ @api_version = options[:api_version] || Mihari.config.thehive_api_version
21
26
  end
22
27
 
23
28
  # @return [Boolean]
@@ -39,15 +44,11 @@ module Mihari
39
44
  #
40
45
  # Create a Hive alert
41
46
  #
42
- # @param [Arra<Mihari::Artifact>] artifacts
43
- # @param [Mihari::Structs::Rule] rule
44
- #
45
47
  # @return [::MISP::Event]
46
48
  #
47
- def emit(rule:, artifacts:, **_options)
49
+ def emit
48
50
  return if artifacts.empty?
49
51
 
50
- payload = payload(rule: rule, artifacts: artifacts)
51
52
  client.alert(payload)
52
53
  end
53
54
 
@@ -102,18 +103,15 @@ module Mihari
102
103
  #
103
104
  # Build payload for alert
104
105
  #
105
- # @param [Arra<Mihari::Artifact>] artifacts
106
- # @param [Mihari::Structs::Rule] rule
107
- #
108
- # @return [<Type>] <description>
106
+ # @return [Hash]
109
107
  #
110
- def payload(rule:, artifacts:)
111
- return v4_payload(rule: rule, artifacts: artifacts) if normalized_api_version.nil?
108
+ def payload
109
+ return v4_payload if normalized_api_version.nil?
112
110
 
113
- v5_payload(rule: rule, artifacts: artifacts)
111
+ v5_payload
114
112
  end
115
113
 
116
- def v4_payload(rule:, artifacts:)
114
+ def v4_payload
117
115
  {
118
116
  title: rule.title,
119
117
  description: rule.description,
@@ -130,7 +128,7 @@ module Mihari
130
128
  }
131
129
  end
132
130
 
133
- def v5_payload(rule:, artifacts:)
131
+ def v5_payload
134
132
  {
135
133
  title: rule.title,
136
134
  description: rule.description,
@@ -55,30 +55,26 @@ module Mihari
55
55
  # @return [String, nil]
56
56
  attr_reader :template
57
57
 
58
- def initialize(*args, **kwargs)
59
- super(*args, **kwargs)
60
-
61
- url = kwargs[:url]
62
- headers = kwargs[:headers] || {}
63
- method = kwargs[:method] || "POST"
64
- template = kwargs[:template]
65
-
66
- @url = Addressable::URI.parse(url)
67
- @headers = headers
68
- @method = method
69
- @template = template
58
+ #
59
+ # @param [Array<Mihari::Artifact>] artifacts
60
+ # @param [Mihari::Structs::Rule] rule
61
+ # @param [Hash] **options
62
+ #
63
+ def initialize(artifacts:, rule:, **options)
64
+ super(artifacts: artifacts, rule: rule, **options)
65
+
66
+ @url = Addressable::URI.parse(options[:url])
67
+ @headers = options[:headers] || {}
68
+ @method = options[:method] || "POST"
69
+ @template = options[:template]
70
70
  end
71
71
 
72
- def emit(artifacts:, rule:)
72
+ def emit
73
73
  return if artifacts.empty?
74
74
 
75
- res = nil
76
-
77
- payload_ = payload_as_string(artifacts: artifacts, rule: rule)
78
- payload = JSON.parse(payload_)
79
-
80
75
  client = Mihari::HTTP.new(url, headers: headers)
81
76
 
77
+ res = nil
82
78
  case method
83
79
  when "GET"
84
80
  res = client.get
@@ -100,13 +96,10 @@ module Mihari
100
96
  #
101
97
  # Convert payload into string
102
98
  #
103
- # @param [Array<Mihari::Artifact>] artifacts
104
- # @param [Mihari::Structs::Rule] rule
105
- #
106
99
  # @return [String]
107
100
  #
108
- def payload_as_string(artifacts:, rule:)
109
- @payload_as_string ||= [].tap do |out|
101
+ def payload_as_string
102
+ [].tap do |out|
110
103
  options = {}
111
104
  options[:template] = File.read(template) unless template.nil?
112
105
 
@@ -118,6 +111,13 @@ module Mihari
118
111
  out << payload_template.result
119
112
  end.first
120
113
  end
114
+
115
+ #
116
+ # @return [Hash]
117
+ #
118
+ def payload
119
+ JSON.parse payload_as_string
120
+ end
121
121
  end
122
122
  end
123
123
  end
@@ -5,6 +5,7 @@ require "whois-parser"
5
5
  module Mihari
6
6
  module Enrichers
7
7
  class Whois < Base
8
+ # @type [Hash]
8
9
  @memo = {}
9
10
 
10
11
  # @return [Boolean]
@@ -5,6 +5,7 @@ require "jr/cli/core_ext"
5
5
  module Mihari
6
6
  module Feed
7
7
  class Parser
8
+ # @return [Array<Hash>, Array<Array<String>>]
8
9
  attr_reader :data
9
10
 
10
11
  #
@@ -6,7 +6,23 @@ require "insensitive_hash"
6
6
  module Mihari
7
7
  module Feed
8
8
  class Reader
9
- attr_reader :url, :headers, :params, :json, :data, :method
9
+ # @return [String]
10
+ attr_reader :url
11
+
12
+ # @return [Hash]
13
+ attr_reader :headers
14
+
15
+ # @return [Hash, nil]
16
+ attr_reader :params
17
+
18
+ # @return [Hash, nil]
19
+ attr_reader :json
20
+
21
+ # @return [Hash, nil]
22
+ attr_reader :data
23
+
24
+ # @return [String]
25
+ attr_reader :method
10
26
 
11
27
  def initialize(url, headers: {}, method: "GET", params: nil, json: nil, data: nil)
12
28
  @url = Addressable::URI.parse(url)
@@ -20,6 +36,9 @@ module Mihari
20
36
  headers["content-type"] = "application/json" unless json.nil?
21
37
  end
22
38
 
39
+ #
40
+ # @return [Array<Hash>]
41
+ #
23
42
  def read
24
43
  return read_file(url.path) if url.scheme == "file"
25
44
 
@@ -33,11 +52,9 @@ module Mihari
33
52
 
34
53
  body = res.body
35
54
  content_type = res["Content-Type"].to_s
36
- if content_type.include?("application/json")
37
- convert_as_json(body)
38
- else
39
- convert_as_csv(body)
40
- end
55
+ return convert_as_json(body) if content_type.include?("application/json")
56
+
57
+ convert_as_csv(body)
41
58
  end
42
59
 
43
60
  #
@@ -48,10 +65,10 @@ module Mihari
48
65
  # @return [Array<Hash>]
49
66
  #
50
67
  def convert_as_json(text)
51
- data = JSON.parse(text, symbolize_names: true)
52
- return data if data.is_a?(Array)
68
+ parsed = JSON.parse(text, symbolize_names: true)
69
+ return parsed if parsed.is_a?(Array)
53
70
 
54
- [data]
71
+ [parsed]
55
72
  end
56
73
 
57
74
  #
@@ -77,11 +94,9 @@ module Mihari
77
94
  def read_file(path)
78
95
  text = File.read(path)
79
96
 
80
- if path.end_with?(".json")
81
- convert_as_json text
82
- else
83
- convert_as_csv text
84
- end
97
+ return convert_as_json(text) if path.end_with?(".json")
98
+
99
+ convert_as_csv text
85
100
  end
86
101
  end
87
102
  end