mihari 5.2.1 → 5.2.3

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 (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