mihari 5.4.1 → 5.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/frontend/package-lock.json +145 -146
  3. data/frontend/package.json +8 -8
  4. data/frontend/src/swagger.yaml +306 -272
  5. data/lib/mihari/analyzers/base.rb +0 -4
  6. data/lib/mihari/analyzers/binaryedge.rb +4 -44
  7. data/lib/mihari/analyzers/censys.rb +4 -20
  8. data/lib/mihari/analyzers/circl.rb +2 -26
  9. data/lib/mihari/analyzers/crtsh.rb +2 -17
  10. data/lib/mihari/analyzers/dnstwister.rb +1 -3
  11. data/lib/mihari/analyzers/greynoise.rb +5 -4
  12. data/lib/mihari/analyzers/hunterhow.rb +8 -23
  13. data/lib/mihari/analyzers/onyphe.rb +5 -39
  14. data/lib/mihari/analyzers/otx.rb +2 -38
  15. data/lib/mihari/analyzers/passivetotal.rb +3 -41
  16. data/lib/mihari/analyzers/securitytrails.rb +3 -41
  17. data/lib/mihari/analyzers/shodan.rb +7 -39
  18. data/lib/mihari/analyzers/urlscan.rb +2 -38
  19. data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -25
  20. data/lib/mihari/analyzers/zoomeye.rb +17 -83
  21. data/lib/mihari/cli/alert.rb +11 -0
  22. data/lib/mihari/cli/main.rb +6 -1
  23. data/lib/mihari/clients/base.rb +9 -1
  24. data/lib/mihari/clients/binaryedge.rb +27 -2
  25. data/lib/mihari/clients/censys.rb +32 -2
  26. data/lib/mihari/clients/circl.rb +28 -1
  27. data/lib/mihari/clients/crtsh.rb +9 -2
  28. data/lib/mihari/clients/dnstwister.rb +4 -2
  29. data/lib/mihari/clients/greynoise.rb +31 -4
  30. data/lib/mihari/clients/hunterhow.rb +41 -3
  31. data/lib/mihari/clients/onyphe.rb +25 -3
  32. data/lib/mihari/clients/otx.rb +40 -0
  33. data/lib/mihari/clients/passivetotal.rb +33 -15
  34. data/lib/mihari/clients/securitytrails.rb +44 -0
  35. data/lib/mihari/clients/shodan.rb +30 -2
  36. data/lib/mihari/clients/urlscan.rb +32 -6
  37. data/lib/mihari/clients/virustotal.rb +29 -4
  38. data/lib/mihari/clients/zoomeye.rb +53 -2
  39. data/lib/mihari/commands/alert.rb +42 -0
  40. data/lib/mihari/commands/rule.rb +2 -2
  41. data/lib/mihari/commands/search.rb +20 -59
  42. data/lib/mihari/commands/web.rb +1 -1
  43. data/lib/mihari/config.rb +2 -2
  44. data/lib/mihari/emitters/base.rb +1 -1
  45. data/lib/mihari/emitters/database.rb +2 -2
  46. data/lib/mihari/errors.rb +23 -2
  47. data/lib/mihari/http.rb +7 -1
  48. data/lib/mihari/schemas/alert.rb +14 -0
  49. data/lib/mihari/services/alert_proxy.rb +106 -0
  50. data/lib/mihari/services/alert_runner.rb +22 -0
  51. data/lib/mihari/services/{rule.rb → rule_proxy.rb} +10 -6
  52. data/lib/mihari/services/rule_runner.rb +49 -0
  53. data/lib/mihari/structs/censys.rb +11 -11
  54. data/lib/mihari/structs/greynoise.rb +17 -8
  55. data/lib/mihari/structs/onyphe.rb +7 -7
  56. data/lib/mihari/structs/shodan.rb +5 -5
  57. data/lib/mihari/structs/urlscan.rb +3 -3
  58. data/lib/mihari/structs/virustotal_intelligence.rb +3 -3
  59. data/lib/mihari/version.rb +1 -1
  60. data/lib/mihari/web/endpoints/alerts.rb +22 -0
  61. data/lib/mihari/web/endpoints/rules.rb +8 -8
  62. data/lib/mihari/web/public/assets/{index-61dc587c.js → index-4d7eda9f.js} +1 -1
  63. data/lib/mihari/web/public/index.html +1 -1
  64. data/lib/mihari/web/public/redoc-static.html +29 -27
  65. data/lib/mihari.rb +6 -1
  66. data/mihari.gemspec +9 -10
  67. metadata +28 -37
  68. data/Steepfile +0 -31
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Services
5
+ class AlertProxy
6
+ # @return [Hash]
7
+ attr_reader :data
8
+
9
+ # @return [Array, nil]
10
+ attr_reader :errors
11
+
12
+ #
13
+ # Initialize
14
+ #
15
+ # @param [Hash] data
16
+ #
17
+ def initialize(data)
18
+ @data = data.deep_symbolize_keys
19
+
20
+ @errors = nil
21
+
22
+ validate
23
+ end
24
+
25
+ #
26
+ # @return [Boolean]
27
+ #
28
+ def errors?
29
+ return false if @errors.nil?
30
+
31
+ !@errors.empty?
32
+ end
33
+
34
+ def validate
35
+ contract = Schemas::AlertContract.new
36
+ result = contract.call(data)
37
+
38
+ @data = result.to_h
39
+ @errors = result.errors
40
+ end
41
+
42
+ def validate!
43
+ return unless errors?
44
+
45
+ Mihari.logger.error "Failed to parse the input as an alert:"
46
+ Mihari.logger.error JSON.pretty_generate(errors.to_h)
47
+
48
+ raise AlertValidationError, errors
49
+ end
50
+
51
+ def [](key)
52
+ data key.to_sym
53
+ end
54
+
55
+ #
56
+ # @return [String]
57
+ #
58
+ def rule_id
59
+ @rule_id ||= data[:rule_id]
60
+ end
61
+
62
+ #
63
+ # @return [Array<Mihari::Artifact>]
64
+ #
65
+ def artifacts
66
+ @artifacts ||= data[:artifacts].map do |data|
67
+ artifact = Artifact.new(data: data)
68
+ artifact.rule_id = rule_id
69
+ artifact
70
+ end.uniq(&:data).select(&:valid?)
71
+ end
72
+
73
+ #
74
+ # @return [Mihari::Services::RuleProxy]
75
+ #
76
+ def rule
77
+ @rule ||= Services::RuleProxy.from_model(Mihari::Rule.find(rule_id))
78
+ end
79
+
80
+ class << self
81
+ #
82
+ # Load rule from YAML string
83
+ #
84
+ # @param [String] yaml
85
+ #
86
+ # @return [Mihari::Services::Alert]
87
+ #
88
+ def from_yaml(yaml)
89
+ Services::AlertProxy.new YAML.safe_load(yaml, permitted_classes: [Date, Symbol])
90
+ rescue Psych::SyntaxError => e
91
+ raise YAMLSyntaxError, e.message
92
+ end
93
+
94
+ # @param [String] path
95
+ #
96
+ # @return [Mihari::Services::Alert, nil]
97
+ #
98
+ def from_path(path)
99
+ return nil unless Pathname(path).exist?
100
+
101
+ from_yaml File.read(path)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Services
5
+ class AlertRunner
6
+ # @return [Mihari::Services::AlertProxy]
7
+ attr_reader :alert
8
+
9
+ def initialize(alert)
10
+ @alert = alert
11
+ end
12
+
13
+ #
14
+ # @return [Mihari::Alert]
15
+ #
16
+ def run
17
+ emitter = Mihari::Emitters::Database.new(artifacts: alert.artifacts, rule: alert.rule)
18
+ emitter.emit
19
+ end
20
+ end
21
+ end
22
+ end
@@ -9,7 +9,11 @@ require "yaml"
9
9
 
10
10
  module Mihari
11
11
  module Services
12
- class Rule
12
+ #
13
+ # proxy (or converter) class for rule
14
+ # proxying rule schema data into analyzer & model
15
+ #
16
+ class RuleProxy
13
17
  include Mixins::FalsePositive
14
18
 
15
19
  # @return [Hash]
@@ -141,7 +145,7 @@ module Mihari
141
145
  #
142
146
  # @return [Mihari::Rule]
143
147
  #
144
- def to_model
148
+ def model
145
149
  rule = Mihari::Rule.find(id)
146
150
 
147
151
  rule.title = title
@@ -161,7 +165,7 @@ module Mihari
161
165
  #
162
166
  # @return [Mihari::Analyzers::Rule]
163
167
  #
164
- def to_analyzer
168
+ def analyzer
165
169
  Mihari::Analyzers::Rule.new self
166
170
  end
167
171
 
@@ -174,7 +178,7 @@ module Mihari
174
178
  # @return [Mihari::Services::Rule]
175
179
  #
176
180
  def from_yaml(yaml)
177
- Services::Rule.new YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
181
+ Services::RuleProxy.new YAML.safe_load(ERB.new(yaml).result, permitted_classes: [Date, Symbol])
178
182
  rescue Psych::SyntaxError => e
179
183
  raise YAMLSyntaxError, e.message
180
184
  end
@@ -185,7 +189,7 @@ module Mihari
185
189
  # @return [Mihari::Services::Rule]
186
190
  #
187
191
  def from_model(model)
188
- Services::Rule.new model.data
192
+ Services::RuleProxy.new model.data
189
193
  end
190
194
 
191
195
  #
@@ -211,7 +215,7 @@ module Mihari
211
215
  def from_id(id)
212
216
  return nil unless Mihari::Rule.exists?(id)
213
217
 
214
- Services::Rule.from_model Mihari::Rule.find(id)
218
+ Services::RuleProxy.from_model Mihari::Rule.find(id)
215
219
  end
216
220
 
217
221
  #
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Services
5
+ class RuleRunner
6
+ include Mixins::ErrorNotification
7
+
8
+ # @return [Mihari::Services::RuleProxy]
9
+ attr_reader :rule
10
+
11
+ # @return [Boolean]
12
+ attr_reader :force_overwrite
13
+
14
+ def initialize(rule, force_overwrite:)
15
+ @rule = rule
16
+ @force_overwrite = force_overwrite
17
+ end
18
+
19
+ def force_overwrite?
20
+ force_overwrite
21
+ end
22
+
23
+ #
24
+ # @return [Boolean]
25
+ #
26
+ def diff?
27
+ model = Mihari::Rule.find(rule.id)
28
+ model.data != rule.data.deep_stringify_keys
29
+ rescue ActiveRecord::RecordNotFound
30
+ false
31
+ end
32
+
33
+ def update_or_create
34
+ rule.model.save
35
+ end
36
+
37
+ #
38
+ # @return [Mihari::Alert, nil]
39
+ #
40
+ def run
41
+ analyzer = rule.analyzer
42
+
43
+ with_error_notification do
44
+ analyzer.run
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -18,7 +18,7 @@ module Mihari
18
18
  #
19
19
  # @return [Mihari::AutonomousSystem]
20
20
  #
21
- def to_as
21
+ def as
22
22
  Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
23
23
  end
24
24
 
@@ -58,7 +58,7 @@ module Mihari
58
58
  #
59
59
  # @return [Mihari::Geolocation] <description>
60
60
  #
61
- def to_geolocation
61
+ def geolocation
62
62
  # sometimes Censys overlooks country
63
63
  # then set geolocation as nil
64
64
  return nil if country.nil?
@@ -98,7 +98,7 @@ module Mihari
98
98
  #
99
99
  # @return [Mihari::Port]
100
100
  #
101
- def to_port
101
+ def _port
102
102
  Port.new(port: port)
103
103
  end
104
104
 
@@ -162,20 +162,20 @@ module Mihari
162
162
  #
163
163
  # @return [Array<Mihari::Port>]
164
164
  #
165
- def to_ports
166
- services.map(&:to_port)
165
+ def ports
166
+ services.map(&:_port)
167
167
  end
168
168
 
169
169
  #
170
170
  # @return [Mihari::Artifact]
171
171
  #
172
- def to_artifact
172
+ def artifact
173
173
  Artifact.new(
174
174
  data: ip,
175
175
  metadata: metadata,
176
- autonomous_system: autonomous_system.to_as,
177
- geolocation: location.to_geolocation,
178
- ports: to_ports
176
+ autonomous_system: autonomous_system.as,
177
+ geolocation: location.geolocation,
178
+ ports: ports
179
179
  )
180
180
  end
181
181
 
@@ -269,8 +269,8 @@ module Mihari
269
269
  #
270
270
  # @return [Array<Mihari::Artifact>]
271
271
  #
272
- def to_artifacts
273
- hits.map(&:to_artifact)
272
+ def artifacts
273
+ hits.map(&:artifact)
274
274
  end
275
275
 
276
276
  class << self
@@ -34,14 +34,14 @@ module Mihari
34
34
  #
35
35
  # @return [Mihari::AutonomousSystem]
36
36
  #
37
- def to_as
37
+ def as
38
38
  Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
39
39
  end
40
40
 
41
41
  #
42
42
  # @return [Mihari::Geolocation]
43
43
  #
44
- def to_geolocation
44
+ def geolocation
45
45
  Mihari::Geolocation.new(
46
46
  country: country,
47
47
  country_code: country_code
@@ -94,12 +94,12 @@ module Mihari
94
94
  #
95
95
  # @return [Mihari::Artifact]
96
96
  #
97
- def to_artifact
97
+ def artifact
98
98
  Mihari::Artifact.new(
99
99
  data: ip,
100
100
  metadata: metadata_,
101
- autonomous_system: metadata.to_as,
102
- geolocation: metadata.to_geolocation
101
+ autonomous_system: metadata.as,
102
+ geolocation: metadata.geolocation
103
103
  )
104
104
  end
105
105
 
@@ -126,6 +126,7 @@ module Mihari
126
126
  attribute :data, Types.Array(Datum)
127
127
  attribute :message, Types::String
128
128
  attribute :query, Types::String
129
+ attribute :scroll, Types::String.optional
129
130
 
130
131
  #
131
132
  # @return [Boolean]
@@ -162,11 +163,18 @@ module Mihari
162
163
  attributes[:query]
163
164
  end
164
165
 
166
+ #
167
+ # @return [String, nil]
168
+ #
169
+ def scroll
170
+ attributes[:scroll]
171
+ end
172
+
165
173
  #
166
174
  # @return [Array<Mihari::Artifact>]
167
175
  #
168
- def to_artifacts
169
- data.map { |datum| datum.to_artifact }
176
+ def artifacts
177
+ data.map(&:artifact)
170
178
  end
171
179
 
172
180
  class << self
@@ -182,7 +190,8 @@ module Mihari
182
190
  count: d.fetch("count"),
183
191
  data: d.fetch("data").map { |x| Datum.from_dynamic!(x) },
184
192
  message: d.fetch("message"),
185
- query: d.fetch("query")
193
+ query: d.fetch("query"),
194
+ scroll: d["scroll"]
186
195
  )
187
196
  end
188
197
  end
@@ -42,19 +42,19 @@ module Mihari
42
42
  #
43
43
  # @return [Mihari::Artifact]
44
44
  #
45
- def to_artifact
45
+ def artifact
46
46
  Mihari::Artifact.new(
47
47
  data: ip,
48
48
  metadata: metadata,
49
- autonomous_system: to_as,
50
- geolocation: to_geolocation
49
+ autonomous_system: as,
50
+ geolocation: geolocation
51
51
  )
52
52
  end
53
53
 
54
54
  #
55
55
  # @return [Mihari::Geolocation, nil]
56
56
  #
57
- def to_geolocation
57
+ def geolocation
58
58
  return nil if country_code.nil?
59
59
 
60
60
  Mihari::Geolocation.new(
@@ -66,7 +66,7 @@ module Mihari
66
66
  #
67
67
  # @return [Mihari::AutonomousSystem]
68
68
  #
69
- def to_as
69
+ def as
70
70
  Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
71
71
  end
72
72
 
@@ -150,8 +150,8 @@ module Mihari
150
150
  #
151
151
  # @return [Array<Mihari::Artifact>]
152
152
  #
153
- def to_artifacts
154
- results.map(&:to_artifact)
153
+ def artifacts
154
+ results.map(&:artifact)
155
155
  end
156
156
 
157
157
  class << self
@@ -24,7 +24,7 @@ module Mihari
24
24
  #
25
25
  # @return [Mihari::Geolocation, nil]
26
26
  #
27
- def to_geolocation
27
+ def geolocation
28
28
  return nil if country_name.nil? && country_code.nil?
29
29
 
30
30
  Mihari::Geolocation.new(
@@ -105,7 +105,7 @@ module Mihari
105
105
  #
106
106
  # @return [Mihari::AutonomousSystem, nil]
107
107
  #
108
- def to_asn
108
+ def _asn
109
109
  return nil if asn.nil?
110
110
 
111
111
  Mihari::AutonomousSystem.new(asn: normalize_asn(asn))
@@ -194,7 +194,7 @@ module Mihari
194
194
  #
195
195
  # @return [Array<Mihari::Artifact>]
196
196
  #
197
- def to_artifacts
197
+ def artifacts
198
198
  matches.map do |match|
199
199
  metadata = collect_metadata_by_ip(match.ip_str)
200
200
  ports = collect_ports_by_ip(match.ip_str).map do |port|
@@ -207,8 +207,8 @@ module Mihari
207
207
  Mihari::Artifact.new(
208
208
  data: match.ip_str,
209
209
  metadata: metadata,
210
- autonomous_system: match.to_asn,
211
- geolocation: match.location.to_geolocation,
210
+ autonomous_system: match._asn,
211
+ geolocation: match.location.geolocation,
212
212
  ports: ports,
213
213
  reverse_dns_names: reverse_dns_names
214
214
  )
@@ -83,7 +83,7 @@ module Mihari
83
83
  #
84
84
  # @return [Array<Mihari::Artifact>]
85
85
  #
86
- def to_artifacts
86
+ def artifacts
87
87
  values = [page.url, page.domain, page.ip].compact
88
88
  values.map do |value|
89
89
  Mihari::Artifact.new(data: value, metadata: metadata)
@@ -129,8 +129,8 @@ module Mihari
129
129
  #
130
130
  # @return [Array<Mihari::Artifact>]
131
131
  #
132
- def to_artifacts
133
- results.map(&:to_artifacts).flatten
132
+ def artifacts
133
+ results.map(&:artifacts).flatten
134
134
  end
135
135
 
136
136
  class << self
@@ -81,7 +81,7 @@ module Mihari
81
81
  #
82
82
  # @return [Mihari::Artifact]
83
83
  #
84
- def to_artifact
84
+ def artifact
85
85
  Artifact.new(data: value, metadata: metadata)
86
86
  end
87
87
 
@@ -155,8 +155,8 @@ module Mihari
155
155
  #
156
156
  # @return [Array<Mihari::Artifact>]
157
157
  #
158
- def to_artifacts
159
- data.map(&:to_artifact)
158
+ def artifacts
159
+ data.map(&:artifact)
160
160
  end
161
161
 
162
162
  class << self
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "5.4.1"
4
+ VERSION = "5.4.3"
5
5
  end
@@ -67,6 +67,28 @@ module Mihari
67
67
  status 204
68
68
  present({ message: "" }, with: Entities::Message)
69
69
  end
70
+
71
+ desc "Create an alert", {
72
+ success: Entities::Alert,
73
+ summary: "Create an alert"
74
+ }
75
+ params do
76
+ requires :ruleId, type: String, documentation: { param_type: "body" }
77
+ requires :artifacts, type: Array, documentation: { type: String, is_array: true, param_type: "body" }
78
+ end
79
+ post "/" do
80
+ proxy = Services::AlertProxy.new(params.to_snake_keys)
81
+ runner = Services::AlertRunner.new(proxy)
82
+
83
+ begin
84
+ alert = runner.run
85
+ rescue ActiveRecord::RecordNotFound
86
+ error!({ message: "Rule:#{params["ruleId"]} is not found" }, 404)
87
+ end
88
+
89
+ status 201
90
+ present alert, with: Entities::Alert
91
+ end
70
92
  end
71
93
  end
72
94
  end
@@ -83,12 +83,12 @@ module Mihari
83
83
  id = params["id"].to_s
84
84
 
85
85
  begin
86
- rule = Mihari::Services::Rule.from_model(Mihari::Rule.find(id))
86
+ rule = Mihari::Services::RuleProxy.from_model(Mihari::Rule.find(id))
87
87
  rescue ActiveRecord::RecordNotFound
88
88
  error!({ message: "ID:#{id} is not found" }, 404)
89
89
  end
90
90
 
91
- analyzer = rule.to_analyzer
91
+ analyzer = rule.analyzer
92
92
  analyzer.run
93
93
 
94
94
  status 201
@@ -106,7 +106,7 @@ module Mihari
106
106
  yaml = params[:yaml]
107
107
 
108
108
  begin
109
- rule = Services::Rule.from_yaml(yaml)
109
+ rule = Services::RuleProxy.from_yaml(yaml)
110
110
  rescue YAMLSyntaxError => e
111
111
  error!({ message: e.message }, 400)
112
112
  end
@@ -129,13 +129,13 @@ module Mihari
129
129
  end
130
130
 
131
131
  begin
132
- rule.to_model.save
132
+ rule.model.save
133
133
  rescue ActiveRecord::RecordNotUnique
134
134
  error!({ message: "ID:#{rule.id} is already registered" }, 400)
135
135
  end
136
136
 
137
137
  status 201
138
- present rule.to_model, with: Entities::Rule
138
+ present rule.model, with: Entities::Rule
139
139
  end
140
140
 
141
141
  desc "Update a rule", {
@@ -157,7 +157,7 @@ module Mihari
157
157
  end
158
158
 
159
159
  begin
160
- rule = Services::Rule.from_yaml(yaml)
160
+ rule = Services::RuleProxy.from_yaml(yaml)
161
161
  rescue YAMLSyntaxError => e
162
162
  error!({ message: e.message }, 400)
163
163
  end
@@ -172,13 +172,13 @@ module Mihari
172
172
  end
173
173
 
174
174
  begin
175
- rule.to_model.save
175
+ rule.model.save
176
176
  rescue ActiveRecord::RecordNotUnique
177
177
  error!({ message: "ID:#{id} is already registered" }, 400)
178
178
  end
179
179
 
180
180
  status 201
181
- present rule.to_model, with: Entities::Rule
181
+ present rule.model, with: Entities::Rule
182
182
  end
183
183
 
184
184
  desc "Delete a rule", {
@@ -938,7 +938,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
938
938
  .ace-tm .ace_indent-guide-active {
939
939
  background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAAZSURBVHjaYvj///9/hivKyv8BAAAA//8DACLqBhbvk+/eAAAAAElFTkSuQmCC") right repeat-y;
940
940
  }
941
- `}),ace.define("ace/theme/textmate",["require","exports","module","ace/theme/textmate-css","ace/lib/dom"],function(n,r,A){r.isDark=!1,r.cssClass="ace-tm",r.cssText=n("./textmate-css"),r.$id="ace/theme/textmate";var S=n("../lib/dom");S.importCssString(r.cssText,r.cssClass,!1)}),ace.define("ace/config",["require","exports","module","ace/lib/lang","ace/lib/net","ace/lib/dom","ace/lib/app_config","ace/theme/textmate"],function(n,r,A){"no use strict";var S=n("./lib/lang"),E=n("./lib/net"),T=n("./lib/dom"),c=n("./lib/app_config").AppConfig;A.exports=r=new c;var C={packaged:!1,workerPath:null,modePath:null,themePath:null,basePath:"",suffix:".js",$moduleUrls:{},loadWorkerFromBlob:!0,sharedPopups:!1,useStrictCSP:null};r.get=function(l){if(!C.hasOwnProperty(l))throw new Error("Unknown config key: "+l);return C[l]},r.set=function(l,f){if(C.hasOwnProperty(l))C[l]=f;else if(this.setDefaultValue("",l,f)==!1)throw new Error("Unknown config key: "+l);l=="useStrictCSP"&&T.useStrictCSP(f)},r.all=function(){return S.copyObject(C)},r.$modes={},r.moduleUrl=function(l,f){if(C.$moduleUrls[l])return C.$moduleUrls[l];var R=l.split("/");f=f||R[R.length-2]||"";var x=f=="snippets"?"/":"-",L=R[R.length-1];if(f=="worker"&&x=="-"){var M=new RegExp("^"+f+"[\\-_]|[\\-_]"+f+"$","g");L=L.replace(M,"")}(!L||L==f)&&R.length>1&&(L=R[R.length-2]);var V=C[f+"Path"];return V==null?V=C.basePath:x=="/"&&(f=x=""),V&&V.slice(-1)!="/"&&(V+="/"),V+f+x+L+this.get("suffix")},r.setModuleUrl=function(l,f){return C.$moduleUrls[l]=f};var o=function(l,f){if(l==="ace/theme/textmate"||l==="./theme/textmate")return f(null,n("./theme/textmate"));if(a)return a(l,f);console.error("loader is not configured")},a;r.setLoader=function(l){a=l},r.dynamicModules=Object.create(null),r.$loading={},r.$loaded={},r.loadModule=function(l,f){var R,x;Array.isArray(l)&&(x=l[0],l=l[1]);var L=function(M){if(M&&!r.$loading[l])return f&&f(M);if(r.$loading[l]||(r.$loading[l]=[]),r.$loading[l].push(f),!(r.$loading[l].length>1)){var V=function(){o(l,function(D,F){F&&(r.$loaded[l]=F),r._emit("load.module",{name:l,module:F});var P=r.$loading[l];r.$loading[l]=null,P.forEach(function(X){X&&X(F)})})};if(!r.get("packaged"))return V();E.loadScript(r.moduleUrl(l,x),V),$()}};if(r.dynamicModules[l])r.dynamicModules[l]().then(function(M){M.default?L(M.default):L(M)});else{try{R=this.$require(l)}catch{}L(R||r.$loaded[l])}},r.$require=function(l){if(typeof A.require=="function"){var f="require";return A[f](l)}},r.setModuleLoader=function(l,f){r.dynamicModules[l]=f};var $=function(){!C.basePath&&!C.workerPath&&!C.modePath&&!C.themePath&&!Object.keys(C.$moduleUrls).length&&(console.error("Unable to infer path to ace from script src,","use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes","or with webpack use ace/webpack-resolver"),$=function(){})};r.version="1.24.0"}),ace.define("ace/loader_build",["require","exports","module","ace/lib/fixoldbrowsers","ace/config"],function(n,r,A){n("./lib/fixoldbrowsers");var S=n("./config");S.setLoader(function(C,o){n([C],function(a){o(null,a)})});var E=function(){return this||typeof window<"u"&&window}();A.exports=function(C){S.init=T,S.$require=n,C.require=n},T(!0);function T(C){if(!(!E||!E.document)){S.set("packaged",C||n.packaged||A.packaged||E.define&&(void 0).packaged);var o={},a="",$=document.currentScript||document._currentScript,l=$&&$.ownerDocument||document;$&&$.src&&(a=$.src.split(/[?#]/)[0].split("/").slice(0,-1).join("/")||"");for(var f=l.getElementsByTagName("script"),R=0;R<f.length;R++){var x=f[R],L=x.src||x.getAttribute("src");if(L){for(var M=x.attributes,V=0,D=M.length;V<D;V++){var F=M[V];F.name.indexOf("data-ace-")===0&&(o[c(F.name.replace(/^data-ace-/,""))]=F.value)}var P=L.match(/^(.*)\/ace([\-.]\w+)?\.js(\?|$)/);P&&(a=P[1])}}a&&(o.base=o.base||a,o.packaged=!0),o.basePath=o.base,o.workerPath=o.workerPath||o.base,o.modePath=o.modePath||o.base,o.themePath=o.themePath||o.base,delete o.base;for(var X in o)typeof o[X]<"u"&&S.set(X,o[X])}}function c(C){return C.replace(/-(.)/g,function(o,a){return a.toUpperCase()})}}),ace.define("ace/range",["require","exports","module"],function(n,r,A){var S=function(T,c){return T.row-c.row||T.column-c.column},E=function(){function T(c,C,o,a){this.start={row:c,column:C},this.end={row:o,column:a}}return T.prototype.isEqual=function(c){return this.start.row===c.start.row&&this.end.row===c.end.row&&this.start.column===c.start.column&&this.end.column===c.end.column},T.prototype.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},T.prototype.contains=function(c,C){return this.compare(c,C)==0},T.prototype.compareRange=function(c){var C,o=c.end,a=c.start;return C=this.compare(o.row,o.column),C==1?(C=this.compare(a.row,a.column),C==1?2:C==0?1:0):C==-1?-2:(C=this.compare(a.row,a.column),C==-1?-1:C==1?42:0)},T.prototype.comparePoint=function(c){return this.compare(c.row,c.column)},T.prototype.containsRange=function(c){return this.comparePoint(c.start)==0&&this.comparePoint(c.end)==0},T.prototype.intersects=function(c){var C=this.compareRange(c);return C==-1||C==0||C==1},T.prototype.isEnd=function(c,C){return this.end.row==c&&this.end.column==C},T.prototype.isStart=function(c,C){return this.start.row==c&&this.start.column==C},T.prototype.setStart=function(c,C){typeof c=="object"?(this.start.column=c.column,this.start.row=c.row):(this.start.row=c,this.start.column=C)},T.prototype.setEnd=function(c,C){typeof c=="object"?(this.end.column=c.column,this.end.row=c.row):(this.end.row=c,this.end.column=C)},T.prototype.inside=function(c,C){return this.compare(c,C)==0?!(this.isEnd(c,C)||this.isStart(c,C)):!1},T.prototype.insideStart=function(c,C){return this.compare(c,C)==0?!this.isEnd(c,C):!1},T.prototype.insideEnd=function(c,C){return this.compare(c,C)==0?!this.isStart(c,C):!1},T.prototype.compare=function(c,C){return!this.isMultiLine()&&c===this.start.row?C<this.start.column?-1:C>this.end.column?1:0:c<this.start.row?-1:c>this.end.row?1:this.start.row===c?C>=this.start.column?0:-1:this.end.row===c?C<=this.end.column?0:1:0},T.prototype.compareStart=function(c,C){return this.start.row==c&&this.start.column==C?-1:this.compare(c,C)},T.prototype.compareEnd=function(c,C){return this.end.row==c&&this.end.column==C?1:this.compare(c,C)},T.prototype.compareInside=function(c,C){return this.end.row==c&&this.end.column==C?1:this.start.row==c&&this.start.column==C?-1:this.compare(c,C)},T.prototype.clipRows=function(c,C){if(this.end.row>C)var o={row:C+1,column:0};else if(this.end.row<c)var o={row:c,column:0};if(this.start.row>C)var a={row:C+1,column:0};else if(this.start.row<c)var a={row:c,column:0};return T.fromPoints(a||this.start,o||this.end)},T.prototype.extend=function(c,C){var o=this.compare(c,C);if(o==0)return this;if(o==-1)var a={row:c,column:C};else var $={row:c,column:C};return T.fromPoints(a||this.start,$||this.end)},T.prototype.isEmpty=function(){return this.start.row===this.end.row&&this.start.column===this.end.column},T.prototype.isMultiLine=function(){return this.start.row!==this.end.row},T.prototype.clone=function(){return T.fromPoints(this.start,this.end)},T.prototype.collapseRows=function(){return this.end.column==0?new T(this.start.row,0,Math.max(this.start.row,this.end.row-1),0):new T(this.start.row,0,this.end.row,0)},T.prototype.toScreenRange=function(c){var C=c.documentToScreenPosition(this.start),o=c.documentToScreenPosition(this.end);return new T(C.row,C.column,o.row,o.column)},T.prototype.moveBy=function(c,C){this.start.row+=c,this.start.column+=C,this.end.row+=c,this.end.column+=C},T}();E.fromPoints=function(T,c){return new E(T.row,T.column,c.row,c.column)},E.comparePoints=S,E.comparePoints=function(T,c){return T.row-c.row||T.column-c.column},r.Range=E}),ace.define("ace/lib/keys",["require","exports","module","ace/lib/oop"],function(n,r,A){/*! @license
941
+ `}),ace.define("ace/theme/textmate",["require","exports","module","ace/theme/textmate-css","ace/lib/dom"],function(n,r,A){r.isDark=!1,r.cssClass="ace-tm",r.cssText=n("./textmate-css"),r.$id="ace/theme/textmate";var S=n("../lib/dom");S.importCssString(r.cssText,r.cssClass,!1)}),ace.define("ace/config",["require","exports","module","ace/lib/lang","ace/lib/net","ace/lib/dom","ace/lib/app_config","ace/theme/textmate"],function(n,r,A){"no use strict";var S=n("./lib/lang"),E=n("./lib/net"),T=n("./lib/dom"),c=n("./lib/app_config").AppConfig;A.exports=r=new c;var C={packaged:!1,workerPath:null,modePath:null,themePath:null,basePath:"",suffix:".js",$moduleUrls:{},loadWorkerFromBlob:!0,sharedPopups:!1,useStrictCSP:null};r.get=function(l){if(!C.hasOwnProperty(l))throw new Error("Unknown config key: "+l);return C[l]},r.set=function(l,f){if(C.hasOwnProperty(l))C[l]=f;else if(this.setDefaultValue("",l,f)==!1)throw new Error("Unknown config key: "+l);l=="useStrictCSP"&&T.useStrictCSP(f)},r.all=function(){return S.copyObject(C)},r.$modes={},r.moduleUrl=function(l,f){if(C.$moduleUrls[l])return C.$moduleUrls[l];var R=l.split("/");f=f||R[R.length-2]||"";var x=f=="snippets"?"/":"-",L=R[R.length-1];if(f=="worker"&&x=="-"){var M=new RegExp("^"+f+"[\\-_]|[\\-_]"+f+"$","g");L=L.replace(M,"")}(!L||L==f)&&R.length>1&&(L=R[R.length-2]);var V=C[f+"Path"];return V==null?V=C.basePath:x=="/"&&(f=x=""),V&&V.slice(-1)!="/"&&(V+="/"),V+f+x+L+this.get("suffix")},r.setModuleUrl=function(l,f){return C.$moduleUrls[l]=f};var o=function(l,f){if(l==="ace/theme/textmate"||l==="./theme/textmate")return f(null,n("./theme/textmate"));if(a)return a(l,f);console.error("loader is not configured")},a;r.setLoader=function(l){a=l},r.dynamicModules=Object.create(null),r.$loading={},r.$loaded={},r.loadModule=function(l,f){var R,x;Array.isArray(l)&&(x=l[0],l=l[1]);var L=function(M){if(M&&!r.$loading[l])return f&&f(M);if(r.$loading[l]||(r.$loading[l]=[]),r.$loading[l].push(f),!(r.$loading[l].length>1)){var V=function(){o(l,function(D,F){F&&(r.$loaded[l]=F),r._emit("load.module",{name:l,module:F});var P=r.$loading[l];r.$loading[l]=null,P.forEach(function(X){X&&X(F)})})};if(!r.get("packaged"))return V();E.loadScript(r.moduleUrl(l,x),V),$()}};if(r.dynamicModules[l])r.dynamicModules[l]().then(function(M){M.default?L(M.default):L(M)});else{try{R=this.$require(l)}catch{}L(R||r.$loaded[l])}},r.$require=function(l){if(typeof A.require=="function"){var f="require";return A[f](l)}},r.setModuleLoader=function(l,f){r.dynamicModules[l]=f};var $=function(){!C.basePath&&!C.workerPath&&!C.modePath&&!C.themePath&&!Object.keys(C.$moduleUrls).length&&(console.error("Unable to infer path to ace from script src,","use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes","or with webpack use ace/webpack-resolver"),$=function(){})};r.version="1.24.1"}),ace.define("ace/loader_build",["require","exports","module","ace/lib/fixoldbrowsers","ace/config"],function(n,r,A){n("./lib/fixoldbrowsers");var S=n("./config");S.setLoader(function(C,o){n([C],function(a){o(null,a)})});var E=function(){return this||typeof window<"u"&&window}();A.exports=function(C){S.init=T,S.$require=n,C.require=n},T(!0);function T(C){if(!(!E||!E.document)){S.set("packaged",C||n.packaged||A.packaged||E.define&&(void 0).packaged);var o={},a="",$=document.currentScript||document._currentScript,l=$&&$.ownerDocument||document;$&&$.src&&(a=$.src.split(/[?#]/)[0].split("/").slice(0,-1).join("/")||"");for(var f=l.getElementsByTagName("script"),R=0;R<f.length;R++){var x=f[R],L=x.src||x.getAttribute("src");if(L){for(var M=x.attributes,V=0,D=M.length;V<D;V++){var F=M[V];F.name.indexOf("data-ace-")===0&&(o[c(F.name.replace(/^data-ace-/,""))]=F.value)}var P=L.match(/^(.*)\/ace([\-.]\w+)?\.js(\?|$)/);P&&(a=P[1])}}a&&(o.base=o.base||a,o.packaged=!0),o.basePath=o.base,o.workerPath=o.workerPath||o.base,o.modePath=o.modePath||o.base,o.themePath=o.themePath||o.base,delete o.base;for(var X in o)typeof o[X]<"u"&&S.set(X,o[X])}}function c(C){return C.replace(/-(.)/g,function(o,a){return a.toUpperCase()})}}),ace.define("ace/range",["require","exports","module"],function(n,r,A){var S=function(T,c){return T.row-c.row||T.column-c.column},E=function(){function T(c,C,o,a){this.start={row:c,column:C},this.end={row:o,column:a}}return T.prototype.isEqual=function(c){return this.start.row===c.start.row&&this.end.row===c.end.row&&this.start.column===c.start.column&&this.end.column===c.end.column},T.prototype.toString=function(){return"Range: ["+this.start.row+"/"+this.start.column+"] -> ["+this.end.row+"/"+this.end.column+"]"},T.prototype.contains=function(c,C){return this.compare(c,C)==0},T.prototype.compareRange=function(c){var C,o=c.end,a=c.start;return C=this.compare(o.row,o.column),C==1?(C=this.compare(a.row,a.column),C==1?2:C==0?1:0):C==-1?-2:(C=this.compare(a.row,a.column),C==-1?-1:C==1?42:0)},T.prototype.comparePoint=function(c){return this.compare(c.row,c.column)},T.prototype.containsRange=function(c){return this.comparePoint(c.start)==0&&this.comparePoint(c.end)==0},T.prototype.intersects=function(c){var C=this.compareRange(c);return C==-1||C==0||C==1},T.prototype.isEnd=function(c,C){return this.end.row==c&&this.end.column==C},T.prototype.isStart=function(c,C){return this.start.row==c&&this.start.column==C},T.prototype.setStart=function(c,C){typeof c=="object"?(this.start.column=c.column,this.start.row=c.row):(this.start.row=c,this.start.column=C)},T.prototype.setEnd=function(c,C){typeof c=="object"?(this.end.column=c.column,this.end.row=c.row):(this.end.row=c,this.end.column=C)},T.prototype.inside=function(c,C){return this.compare(c,C)==0?!(this.isEnd(c,C)||this.isStart(c,C)):!1},T.prototype.insideStart=function(c,C){return this.compare(c,C)==0?!this.isEnd(c,C):!1},T.prototype.insideEnd=function(c,C){return this.compare(c,C)==0?!this.isStart(c,C):!1},T.prototype.compare=function(c,C){return!this.isMultiLine()&&c===this.start.row?C<this.start.column?-1:C>this.end.column?1:0:c<this.start.row?-1:c>this.end.row?1:this.start.row===c?C>=this.start.column?0:-1:this.end.row===c?C<=this.end.column?0:1:0},T.prototype.compareStart=function(c,C){return this.start.row==c&&this.start.column==C?-1:this.compare(c,C)},T.prototype.compareEnd=function(c,C){return this.end.row==c&&this.end.column==C?1:this.compare(c,C)},T.prototype.compareInside=function(c,C){return this.end.row==c&&this.end.column==C?1:this.start.row==c&&this.start.column==C?-1:this.compare(c,C)},T.prototype.clipRows=function(c,C){if(this.end.row>C)var o={row:C+1,column:0};else if(this.end.row<c)var o={row:c,column:0};if(this.start.row>C)var a={row:C+1,column:0};else if(this.start.row<c)var a={row:c,column:0};return T.fromPoints(a||this.start,o||this.end)},T.prototype.extend=function(c,C){var o=this.compare(c,C);if(o==0)return this;if(o==-1)var a={row:c,column:C};else var $={row:c,column:C};return T.fromPoints(a||this.start,$||this.end)},T.prototype.isEmpty=function(){return this.start.row===this.end.row&&this.start.column===this.end.column},T.prototype.isMultiLine=function(){return this.start.row!==this.end.row},T.prototype.clone=function(){return T.fromPoints(this.start,this.end)},T.prototype.collapseRows=function(){return this.end.column==0?new T(this.start.row,0,Math.max(this.start.row,this.end.row-1),0):new T(this.start.row,0,this.end.row,0)},T.prototype.toScreenRange=function(c){var C=c.documentToScreenPosition(this.start),o=c.documentToScreenPosition(this.end);return new T(C.row,C.column,o.row,o.column)},T.prototype.moveBy=function(c,C){this.start.row+=c,this.start.column+=C,this.end.row+=c,this.end.column+=C},T}();E.fromPoints=function(T,c){return new E(T.row,T.column,c.row,c.column)},E.comparePoints=S,E.comparePoints=function(T,c){return T.row-c.row||T.column-c.column},r.Range=E}),ace.define("ace/lib/keys",["require","exports","module","ace/lib/oop"],function(n,r,A){/*! @license
942
942
  ==========================================================================
943
943
  SproutCore -- JavaScript Application Framework
944
944
  copyright 2006-2009, Sprout Systems Inc., Apple Inc. and contributors.
@@ -6,7 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
7
7
  <link rel="icon" href="/favicon.ico" />
8
8
  <title>Mihari</title>
9
- <script type="module" crossorigin src="/assets/index-61dc587c.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-4d7eda9f.js"></script>
10
10
  <link rel="stylesheet" href="/assets/index-33165282.css">
11
11
  </head>
12
12
  <body>