mihari 5.4.2 → 5.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/frontend/package-lock.json +2399 -1504
  3. data/frontend/package.json +22 -22
  4. data/lib/mihari/analyzers/base.rb +25 -14
  5. data/lib/mihari/analyzers/binaryedge.rb +2 -48
  6. data/lib/mihari/analyzers/censys.rb +4 -20
  7. data/lib/mihari/analyzers/circl.rb +3 -27
  8. data/lib/mihari/analyzers/crtsh.rb +2 -17
  9. data/lib/mihari/analyzers/dnstwister.rb +2 -4
  10. data/lib/mihari/analyzers/greynoise.rb +5 -4
  11. data/lib/mihari/analyzers/hunterhow.rb +8 -23
  12. data/lib/mihari/analyzers/onyphe.rb +5 -39
  13. data/lib/mihari/analyzers/otx.rb +3 -39
  14. data/lib/mihari/analyzers/passivetotal.rb +4 -42
  15. data/lib/mihari/analyzers/pulsedive.rb +1 -1
  16. data/lib/mihari/analyzers/rule.rb +18 -13
  17. data/lib/mihari/analyzers/securitytrails.rb +4 -42
  18. data/lib/mihari/analyzers/shodan.rb +7 -39
  19. data/lib/mihari/analyzers/urlscan.rb +3 -39
  20. data/lib/mihari/analyzers/virustotal.rb +1 -1
  21. data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -25
  22. data/lib/mihari/analyzers/zoomeye.rb +18 -84
  23. data/lib/mihari/clients/base.rb +9 -1
  24. data/lib/mihari/clients/binaryedge.rb +26 -4
  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 +7 -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/publsedive.rb +1 -1
  35. data/lib/mihari/clients/securitytrails.rb +44 -0
  36. data/lib/mihari/clients/shodan.rb +31 -3
  37. data/lib/mihari/clients/urlscan.rb +32 -6
  38. data/lib/mihari/clients/virustotal.rb +29 -4
  39. data/lib/mihari/clients/zoomeye.rb +53 -2
  40. data/lib/mihari/commands/alert.rb +42 -13
  41. data/lib/mihari/commands/rule.rb +11 -7
  42. data/lib/mihari/commands/search.rb +54 -22
  43. data/lib/mihari/commands/web.rb +1 -1
  44. data/lib/mihari/config.rb +6 -1
  45. data/lib/mihari/emitters/base.rb +9 -3
  46. data/lib/mihari/emitters/slack.rb +1 -1
  47. data/lib/mihari/enrichers/base.rb +13 -0
  48. data/lib/mihari/enrichers/google_public_dns.rb +16 -1
  49. data/lib/mihari/enrichers/ipinfo.rb +9 -13
  50. data/lib/mihari/enrichers/shodan.rb +1 -2
  51. data/lib/mihari/enrichers/whois.rb +2 -2
  52. data/lib/mihari/errors.rb +16 -10
  53. data/lib/mihari/feed/parser.rb +2 -2
  54. data/lib/mihari/models/artifact.rb +1 -1
  55. data/lib/mihari/models/autonomous_system.rb +11 -5
  56. data/lib/mihari/models/cpe.rb +10 -4
  57. data/lib/mihari/models/dns.rb +11 -16
  58. data/lib/mihari/models/geolocation.rb +11 -5
  59. data/lib/mihari/models/port.rb +10 -4
  60. data/lib/mihari/models/reverse_dns.rb +10 -4
  61. data/lib/mihari/models/whois.rb +4 -1
  62. data/lib/mihari/schemas/analyzer.rb +1 -0
  63. data/lib/mihari/services/alert_builder.rb +43 -0
  64. data/lib/mihari/services/alert_proxy.rb +7 -25
  65. data/lib/mihari/services/alert_runner.rb +9 -0
  66. data/lib/mihari/services/rule_builder.rb +47 -0
  67. data/lib/mihari/services/rule_proxy.rb +5 -61
  68. data/lib/mihari/services/rule_runner.rb +9 -4
  69. data/lib/mihari/structs/binaryedge.rb +89 -0
  70. data/lib/mihari/structs/censys.rb +11 -11
  71. data/lib/mihari/structs/greynoise.rb +17 -8
  72. data/lib/mihari/structs/onyphe.rb +7 -7
  73. data/lib/mihari/structs/shodan.rb +7 -6
  74. data/lib/mihari/structs/urlscan.rb +4 -6
  75. data/lib/mihari/structs/virustotal_intelligence.rb +4 -6
  76. data/lib/mihari/type_checker.rb +1 -1
  77. data/lib/mihari/version.rb +1 -1
  78. data/lib/mihari/web/endpoints/alerts.rb +33 -15
  79. data/lib/mihari/web/endpoints/artifacts.rb +53 -25
  80. data/lib/mihari/web/endpoints/configs.rb +2 -2
  81. data/lib/mihari/web/endpoints/ip_addresses.rb +3 -5
  82. data/lib/mihari/web/endpoints/rules.rb +97 -71
  83. data/lib/mihari/web/endpoints/tags.rb +15 -5
  84. data/lib/mihari/web/public/assets/index-ef33a6cd.js +1738 -0
  85. data/lib/mihari/web/public/index.html +1 -1
  86. data/lib/mihari/web/public/redoc-static.html +419 -382
  87. data/lib/mihari.rb +4 -0
  88. data/mihari.gemspec +10 -9
  89. metadata +38 -21
  90. data/lib/mihari/web/public/assets/index-4d7eda9f.js +0 -1738
@@ -3,32 +3,59 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class GreyNoise < Base
6
+ PAGE_SIZE = 10_000
7
+
6
8
  #
7
9
  # @param [String] base_url
8
10
  # @param [String, nil] api_key
9
11
  # @param [Hash] headers
12
+ # @param [Integer, nil] interval
10
13
  #
11
- def initialize(base_url = "https://api.greynoise.io", api_key:, headers: {})
14
+ def initialize(base_url = "https://api.greynoise.io", api_key:, headers: {}, interval: nil)
12
15
  raise(ArgumentError, "'api_key' argument is required") unless api_key
13
16
 
14
17
  headers["key"] = api_key
15
- super(base_url, headers: headers)
18
+ super(base_url, headers: headers, interval: interval)
16
19
  end
17
20
 
18
21
  #
19
22
  # GNQL (GreyNoise Query Language) is a domain-specific query language that uses Lucene deep under the hood
20
23
  #
21
24
  # @param [String] query GNQL query string
22
- # @param [Integer, nil] size Maximum amount of results to grab
25
+ # @param [Integer] size Maximum amount of results to grab
23
26
  # @param [Integer, nil] scroll Scroll token to paginate through results
24
27
  #
25
28
  # @return [Hash]
26
29
  #
27
- def gnql_search(query, size: nil, scroll: nil)
30
+ def gnql_search(query, size: PAGE_SIZE, scroll: nil)
28
31
  params = { query: query, size: size, scroll: scroll }.compact
29
32
  res = get("/v2/experimental/gnql", params: params)
30
33
  Structs::GreyNoise::Response.from_dynamic! JSON.parse(res.body.to_s)
31
34
  end
35
+
36
+ #
37
+ # @param [String] query
38
+ # @param [Integer] size
39
+ # @param [Integer] pagination_limit
40
+ #
41
+ # @return [Enumerable<Structs::GreyNoise::Response>]
42
+ #
43
+ def gnql_search_with_pagination(query, size: PAGE_SIZE, pagination_limit: Mihari.config.pagination_limit)
44
+ scroll = nil
45
+
46
+ Enumerator.new do |y|
47
+ pagination_limit.times do
48
+ res = gnql_search(query, size: size, scroll: scroll)
49
+
50
+ y.yield res
51
+
52
+ scroll = res.scroll
53
+ break if scroll.nil?
54
+
55
+ sleep_interval
56
+ end
57
+ end
58
+ end
32
59
  end
33
60
  end
34
61
  end
@@ -5,6 +5,8 @@ require "base64"
5
5
  module Mihari
6
6
  module Clients
7
7
  class HunterHow < Base
8
+ PAGE_SIZE = 100
9
+
8
10
  # @return [String]
9
11
  attr_reader :api_key
10
12
 
@@ -12,11 +14,12 @@ module Mihari
12
14
  # @param [String] base_url
13
15
  # @param [String, nil] api_key
14
16
  # @param [Hash] headers
17
+ # @param [Integer, nil] interval
15
18
  #
16
- def initialize(base_url = "https://api.hunter.how/", api_key:, headers: {})
19
+ def initialize(base_url = "https://api.hunter.how/", api_key:, headers: {}, interval: nil)
17
20
  raise(ArgumentError, "'api_key' argument is required") unless api_key
18
21
 
19
- super(base_url, headers: headers)
22
+ super(base_url, headers: headers, interval: interval)
20
23
 
21
24
  @api_key = api_key
22
25
  end
@@ -30,7 +33,7 @@ module Mihari
30
33
  #
31
34
  # @return [Structs::HunterHow::Response]
32
35
  #
33
- def search(query, start_time:, end_time:, page: 1, page_size: 10)
36
+ def search(query, start_time:, end_time:, page: 1, page_size: PAGE_SIZE)
34
37
  params = {
35
38
  query: Base64.urlsafe_encode64(query),
36
39
  page: page,
@@ -42,6 +45,41 @@ module Mihari
42
45
  res = get("/search", params: params)
43
46
  Structs::HunterHow::Response.from_dynamic! JSON.parse(res.body.to_s)
44
47
  end
48
+
49
+ #
50
+ # @param [String] query String used to query our data
51
+ # @param [Integer] page_size Default 100, Maximum: 100
52
+ # @param [Integer] pagination_limit
53
+ # @param [String] start_time
54
+ # @param [String] end_time
55
+ #
56
+ # @return [Enumerable<Structs::HunterHow::Response>]
57
+ #
58
+ def search_with_pagination(
59
+ query,
60
+ start_time:,
61
+ end_time:,
62
+ page_size: PAGE_SIZE,
63
+ pagination_limit: Mihari.config.pagination_limit
64
+ )
65
+ Enumerator.new do |y|
66
+ (1..pagination_limit).each do |page|
67
+ res = search(
68
+ query,
69
+ start_time: start_time,
70
+ end_time: end_time,
71
+ page: page,
72
+ page_size: page_size
73
+ )
74
+
75
+ y.yield res
76
+
77
+ break if res.data.list.length < page_size
78
+
79
+ sleep_interval
80
+ end
81
+ end
82
+ end
45
83
  end
46
84
  end
47
85
  end
@@ -3,6 +3,8 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class Onyphe < Base
6
+ PAGE_SIZE = 10
7
+
6
8
  # @return [String]
7
9
  attr_reader :api_key
8
10
 
@@ -11,10 +13,10 @@ module Mihari
11
13
  # @param [String, nil] api_key
12
14
  # @param [Hash] headers
13
15
  #
14
- def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {})
16
+ def initialize(base_url = "https://www.onyphe.io", api_key:, headers: {}, interval: nil)
15
17
  raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
16
18
 
17
- super(base_url, headers: headers)
19
+ super(base_url, headers: headers, interval: interval)
18
20
 
19
21
  @api_key = api_key
20
22
  end
@@ -23,13 +25,33 @@ module Mihari
23
25
  # @param [String] query
24
26
  # @param [Integer] page
25
27
  #
26
- # @return [Hash]
28
+ # @return [Structs::Onyphe::Response]
27
29
  #
28
30
  def datascan(query, page: 1)
29
31
  params = { page: page, apikey: api_key }
30
32
  res = get("/api/v2/simple/datascan/#{query}", params: params)
31
33
  Structs::Onyphe::Response.from_dynamic! JSON.parse(res.body.to_s)
32
34
  end
35
+
36
+ #
37
+ # @param [String] query
38
+ # @param [Integer] pagination_limit
39
+ #
40
+ # @return [Enumerable<Structs::Onyphe::Response>]
41
+ #
42
+ def datascan_with_pagination(query, pagination_limit: Mihari.config.pagination_limit)
43
+ Enumerator.new do |y|
44
+ (1..pagination_limit).each do |page|
45
+ res = datascan(query, page: page)
46
+
47
+ y.yield res
48
+
49
+ break if res.total <= page * PAGE_SIZE
50
+
51
+ sleep_interval
52
+ end
53
+ end
54
+ end
33
55
  end
34
56
  end
35
57
  end
@@ -15,6 +15,46 @@ module Mihari
15
15
  super(base_url, headers: headers)
16
16
  end
17
17
 
18
+ #
19
+ # Domain search
20
+ #
21
+ # @param [String] query
22
+ #
23
+ # @return [Array<String>]
24
+ #
25
+ def domain_search(query)
26
+ res = query_by_domain(query)
27
+ return [] if res.nil?
28
+
29
+ records = res["passive_dns"] || []
30
+ records.filter_map do |record|
31
+ record_type = record["record_type"]
32
+ address = record["address"]
33
+
34
+ address if record_type == "A"
35
+ end.uniq
36
+ end
37
+
38
+ #
39
+ # IP search
40
+ #
41
+ # @param [String] query
42
+ #
43
+ # @return [Array<String>]
44
+ #
45
+ def ip_search(query)
46
+ res = query_by_ip(query)
47
+ return [] if res.nil?
48
+
49
+ records = res["passive_dns"] || []
50
+ records.filter_map do |record|
51
+ record_type = record["record_type"]
52
+ hostname = record["hostname"]
53
+
54
+ hostname if record_type == "A"
55
+ end.uniq
56
+ end
57
+
18
58
  #
19
59
  # @param [String] ip
20
60
  #
@@ -21,35 +21,53 @@ module Mihari
21
21
  end
22
22
 
23
23
  #
24
- # @param [String] query
25
- #
26
- def ssl_search(query)
27
- params = { query: query }
28
- _get("/v2/ssl-certificate/history", params: params)
29
- end
30
-
24
+ # Passive DNS search
31
25
  #
32
26
  # @param [String] query
33
27
  #
34
- # @return [Hash]
28
+ # @return [Array<String>]
35
29
  #
36
30
  def passive_dns_search(query)
37
31
  params = { query: query }
38
- _get("/v2/dns/passive/unique", params: params)
32
+ res = _get("/v2/dns/passive/unique", params: params)
33
+ res["results"] || []
39
34
  end
40
35
 
41
36
  #
42
- # @param [String] query the domain being queried
43
- # @param [String] field whether to return historical results
37
+ # Reverse whois search
44
38
  #
45
- # @return [Hash]
39
+ # @param [String] query
40
+ #
41
+ # @return [Array<Mihari::Artifact>]
46
42
  #
47
- def reverse_whois_search(query:, field:)
43
+ def reverse_whois_search(query)
48
44
  params = {
49
45
  query: query,
50
- field: field
46
+ field: "email"
51
47
  }.compact
52
- _get("/v2/whois/search", params: params)
48
+ res = _get("/v2/whois/search", params: params)
49
+ results = res["results"] || []
50
+ results.map do |result|
51
+ data = result["domain"]
52
+ Artifact.new(data: data, metadata: result)
53
+ end.flatten
54
+ end
55
+
56
+ #
57
+ # Passive SSL search
58
+ #
59
+ # @param [String] query
60
+ #
61
+ # @return [Array<Mihari::Artifact>]
62
+ #
63
+ def ssl_search(query)
64
+ params = { query: query }
65
+ res = _get("/v2/ssl-certificate/history", params: params)
66
+ results = res["results"] || []
67
+ results.map do |result|
68
+ data = result["ipAddresses"]
69
+ data.map { |d| Artifact.new(data: d, metadata: result) }
70
+ end.flatten
53
71
  end
54
72
 
55
73
  private
@@ -49,7 +49,7 @@ module Mihari
49
49
  params["key"] = api_key
50
50
 
51
51
  res = get(path, params: params)
52
- JSON.parse(res.body.to_s)
52
+ JSON.parse res.body.to_s
53
53
  end
54
54
  end
55
55
  end
@@ -15,6 +15,50 @@ module Mihari
15
15
  super(base_url, headers: headers)
16
16
  end
17
17
 
18
+ #
19
+ # Domain search
20
+ #
21
+ # @param [String] query
22
+ #
23
+ # @return [Array<String>]
24
+ #
25
+ def domain_search(query)
26
+ records = get_all_dns_history(query, type: "a")
27
+ records.map do |record|
28
+ (record["values"] || []).map { |value| value["ip"] }
29
+ end.flatten.compact.uniq
30
+ end
31
+
32
+ #
33
+ # IP search
34
+ #
35
+ # @param [String] query
36
+ #
37
+ # @return [Array<Mihari::Artifact>]
38
+ #
39
+ def ip_search(query)
40
+ records = search_by_ip(query)
41
+ records.filter_map do |record|
42
+ data = record["hostname"]
43
+ Artifact.new(data: data, metadata: record)
44
+ end
45
+ end
46
+
47
+ #
48
+ # Mail search
49
+ #
50
+ # @param [String] query
51
+ #
52
+ # @return [Array<String>]
53
+ #
54
+ def mail_search(query)
55
+ records = search_by_mail(query)
56
+ records.filter_map do |record|
57
+ data = record["hostname"]
58
+ Artifact.new(data: data, metadata: record)
59
+ end
60
+ end
61
+
18
62
  #
19
63
  # @param [String] mail
20
64
  #
@@ -3,6 +3,8 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class Shodan < Base
6
+ PAGE_SIZE = 100
7
+
6
8
  # @return [String]
7
9
  attr_reader :api_key
8
10
 
@@ -10,11 +12,12 @@ module Mihari
10
12
  # @param [String] base_url
11
13
  # @param [String, nil] api_key
12
14
  # @param [Hash] headers
15
+ # @param [Integer, nil] interval
13
16
  #
14
- def initialize(base_url = "https://api.shodan.io", api_key:, headers: {})
17
+ def initialize(base_url = "https://api.shodan.io", api_key:, headers: {}, interval: nil)
15
18
  raise(ArgumentError, "'api_key' argument is required") unless api_key
16
19
 
17
- super(base_url, headers: headers)
20
+ super(base_url, headers: headers, interval: interval)
18
21
 
19
22
  @api_key = api_key
20
23
  end
@@ -34,7 +37,32 @@ module Mihari
34
37
  key: api_key
35
38
  }
36
39
  res = get("/shodan/host/search", params: params)
37
- Structs::Shodan::Result.from_dynamic! JSON.parse(res.body.to_s)
40
+ Structs::Shodan::Response.from_dynamic! JSON.parse(res.body.to_s)
41
+ end
42
+
43
+ #
44
+ # @param [String] query
45
+ # @param [Boolean] minify
46
+ # @param [Integer] pagination_limit
47
+ #
48
+ # @return [Enumerable<Structs::Shodan::Response>]
49
+ #
50
+ def search_with_pagination(query, minify: true, pagination_limit: Mihari.config.pagination_limit)
51
+ Enumerator.new do |y|
52
+ (1..pagination_limit).each do |page|
53
+ res = search(query, page: page, minify: minify)
54
+
55
+ y.yield res
56
+
57
+ break if res.total <= page * PAGE_SIZE
58
+
59
+ sleep_interval
60
+ rescue JSON::ParserError
61
+ # ignore JSON::ParserError
62
+ # ref. https://github.com/ninoseki/mihari/issues/197
63
+ next
64
+ end
65
+ end
38
66
  end
39
67
  end
40
68
  end
@@ -7,26 +7,52 @@ module Mihari
7
7
  # @param [String] base_url
8
8
  # @param [String, nil] api_key
9
9
  # @param [Hash] headers
10
+ # @param [Interval, nil] interval
10
11
  #
11
- def initialize(base_url = "https://urlscan.io", api_key:, headers: {})
12
+ def initialize(base_url = "https://urlscan.io", api_key:, headers: {}, interval: nil)
12
13
  raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
13
14
 
14
15
  headers["api-key"] = api_key
15
16
 
16
- super(base_url, headers: headers)
17
+ super(base_url, headers: headers, interval: interval)
17
18
  end
18
19
 
19
20
  #
20
21
  # @param [String] q
21
- # @param [Integer] size
22
+ # @param [Integer, nil] size
22
23
  # @param [String, nil] search_after
23
24
  #
24
- # @return [Hash]
25
+ # @return [Structs::Urlscan::Response]
25
26
  #
26
- def search(q, size: 100, search_after: nil)
27
+ def search(q, size: nil, search_after: nil)
27
28
  params = { q: q, size: size, search_after: search_after }.compact
28
29
  res = get("/api/v1/search/", params: params)
29
- JSON.parse res.body.to_s
30
+ Structs::Urlscan::Response.from_dynamic! JSON.parse(res.body.to_s)
31
+ end
32
+
33
+ #
34
+ # @param [String] q
35
+ # @param [Integer, nil] size
36
+ # @param [Integer] pagination_limit
37
+ #
38
+ # @return [Enumerable<Structs::Urlscan::Response>]
39
+ #
40
+ def search_with_pagination(q, size: nil, pagination_limit: Mihari.config.pagination_limit)
41
+ search_after = nil
42
+
43
+ Enumerator.new do |y|
44
+ pagination_limit.times do
45
+ res = search(q, size: size, search_after: search_after)
46
+
47
+ y.yield res
48
+
49
+ break unless res.has_more
50
+
51
+ search_after = res.results.last.sort.join(",")
52
+
53
+ sleep_interval
54
+ end
55
+ end
30
56
  end
31
57
  end
32
58
  end
@@ -7,13 +7,14 @@ module Mihari
7
7
  # @param [String] base_url
8
8
  # @param [String, nil] api_key
9
9
  # @param [Hash] headers
10
+ # @param [Integer, nil] interval
10
11
  #
11
- def initialize(base_url = "https://www.virustotal.com", api_key:, headers: {})
12
+ def initialize(base_url = "https://www.virustotal.com", api_key:, headers: {}, interval: nil)
12
13
  raise(ArgumentError, "'api_key' argument is required") if api_key.nil?
13
14
 
14
15
  headers["x-apikey"] = api_key
15
16
 
16
- super(base_url, headers: headers)
17
+ super(base_url, headers: headers, interval: interval)
17
18
  end
18
19
 
19
20
  #
@@ -38,11 +39,35 @@ module Mihari
38
39
  # @param [String] query
39
40
  # @param [String, nil] cursor
40
41
  #
41
- # @return [Hash]
42
+ # @return [Structs::VirusTotalIntelligence::Response]
42
43
  #
43
44
  def intel_search(query, cursor: nil)
44
45
  params = { query: query, cursor: cursor }.compact
45
- _get("/api/v3/intelligence/search", params: params)
46
+ res = _get("/api/v3/intelligence/search", params: params)
47
+ Structs::VirusTotalIntelligence::Response.from_dynamic! res
48
+ end
49
+
50
+ #
51
+ # @param [String] query
52
+ # @param [Integer] pagination_limit
53
+ #
54
+ # @return [Enumerable<Structs::VirusTotalIntelligence::Response>]
55
+ #
56
+ def intel_search_with_pagination(query, pagination_limit: Mihari.config.pagination_limit)
57
+ cursor = nil
58
+
59
+ Enumerator.new do |y|
60
+ pagination_limit.times do
61
+ res = intel_search(query, cursor: cursor)
62
+
63
+ y.yield res
64
+
65
+ cursor = res.meta.cursor
66
+ break if cursor.nil?
67
+
68
+ sleep_interval
69
+ end
70
+ end
46
71
  end
47
72
 
48
73
  private
@@ -3,18 +3,21 @@
3
3
  module Mihari
4
4
  module Clients
5
5
  class ZoomEye < Base
6
+ PAGE_SIZE = 10
7
+
6
8
  attr_reader :api_key
7
9
 
8
10
  #
9
11
  # @param [String] base_url
10
12
  # @param [String, nil] api_key
11
13
  # @param [Hash] headers
14
+ # @param [Integer, nil] interval
12
15
  #
13
- def initialize(base_url = "https://api.zoomeye.org", api_key:, headers: {})
16
+ def initialize(base_url = "https://api.zoomeye.org", api_key:, headers: {}, interval: nil)
14
17
  raise(ArgumentError, "'api_key' argument is required") unless api_key
15
18
 
16
19
  headers["api-key"] = api_key
17
- super(base_url, headers: headers)
20
+ super(base_url, headers: headers, interval: interval)
18
21
  end
19
22
 
20
23
  #
@@ -36,6 +39,30 @@ module Mihari
36
39
  _get("/host/search", params: params)
37
40
  end
38
41
 
42
+ #
43
+ # @param [String] query
44
+ # @param [String, nil] facets
45
+ # @param [Integer] pagination_limit
46
+ #
47
+ # @return [Enumerable<Hash>]
48
+ #
49
+ def host_search_with_pagination(query, facets: nil, pagination_limit: Mihari.config.pagination_limit)
50
+ Enumerator.new do |y|
51
+ (1..pagination_limit).each do |page|
52
+ res = host_search(query, facets: facets, page: page)
53
+
54
+ break if res.nil?
55
+
56
+ y.yield res
57
+
58
+ total = res["total"].to_i
59
+ break if total <= page * PAGE_SIZE
60
+
61
+ sleep_interval
62
+ end
63
+ end
64
+ end
65
+
39
66
  #
40
67
  # Search the Web technologies
41
68
  #
@@ -55,6 +82,30 @@ module Mihari
55
82
  _get("/web/search", params: params)
56
83
  end
57
84
 
85
+ #
86
+ # @param [String] query
87
+ # @param [String, nil] facets
88
+ # @param [Integer] pagination_limit
89
+ #
90
+ # @return [Enumerable<Hash>]
91
+ #
92
+ def web_search_with_pagination(query, facets: nil, pagination_limit: Mihari.config.pagination_limit)
93
+ Enumerator.new do |y|
94
+ (1..pagination_limit).each do |page|
95
+ res = web_search(query, facets: facets, page: page)
96
+
97
+ break if res.nil?
98
+
99
+ y.yield res
100
+
101
+ total = res["total"].to_i
102
+ break if total <= page * PAGE_SIZE
103
+
104
+ sleep_interval
105
+ end
106
+ end
107
+ end
108
+
58
109
  private
59
110
 
60
111
  #