monovm-whois-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +75 -0
  3. data/LICENSE +21 -0
  4. data/README.md +348 -0
  5. data/data/rdap_bootstrap.json +5337 -0
  6. data/data/whois_servers.json +1460 -0
  7. data/exe/monovm-whois +6 -0
  8. data/lib/monovm/whois/availability/analyzer.rb +91 -0
  9. data/lib/monovm/whois/availability/context.rb +137 -0
  10. data/lib/monovm/whois/availability/patterns.rb +415 -0
  11. data/lib/monovm/whois/availability/rule.rb +57 -0
  12. data/lib/monovm/whois/availability/rule_set.rb +137 -0
  13. data/lib/monovm/whois/availability/rules/availability_keywords.rb +32 -0
  14. data/lib/monovm/whois/availability/rules/explicit_unavailability.rb +43 -0
  15. data/lib/monovm/whois/availability/rules/no_match.rb +31 -0
  16. data/lib/monovm/whois/availability/rules/premium_name.rb +35 -0
  17. data/lib/monovm/whois/availability/rules/rdap_object.rb +94 -0
  18. data/lib/monovm/whois/availability/rules/recordless.rb +45 -0
  19. data/lib/monovm/whois/availability/rules/registration_fields.rb +37 -0
  20. data/lib/monovm/whois/availability/rules/registry_marker.rb +38 -0
  21. data/lib/monovm/whois/availability/rules/server_refusal.rb +46 -0
  22. data/lib/monovm/whois/availability/rules/status_field.rb +42 -0
  23. data/lib/monovm/whois/availability/rules/tld_specific.rb +38 -0
  24. data/lib/monovm/whois/availability/rules/wrong_registry.rb +48 -0
  25. data/lib/monovm/whois/availability/verdict.rb +100 -0
  26. data/lib/monovm/whois/checker.rb +165 -0
  27. data/lib/monovm/whois/cli.rb +250 -0
  28. data/lib/monovm/whois/client.rb +227 -0
  29. data/lib/monovm/whois/configuration.rb +160 -0
  30. data/lib/monovm/whois/domain_name.rb +168 -0
  31. data/lib/monovm/whois/endpoint.rb +131 -0
  32. data/lib/monovm/whois/errors.rb +63 -0
  33. data/lib/monovm/whois/parser/base.rb +126 -0
  34. data/lib/monovm/whois/parser/icann_rdd.rb +79 -0
  35. data/lib/monovm/whois/parser/key_value.rb +169 -0
  36. data/lib/monovm/whois/parser/rdap_json.rb +170 -0
  37. data/lib/monovm/whois/parser/record.rb +165 -0
  38. data/lib/monovm/whois/parser/selector.rb +74 -0
  39. data/lib/monovm/whois/paths.rb +31 -0
  40. data/lib/monovm/whois/punycode.rb +206 -0
  41. data/lib/monovm/whois/referral/follower.rb +90 -0
  42. data/lib/monovm/whois/registry/definition.rb +119 -0
  43. data/lib/monovm/whois/registry/resolution.rb +57 -0
  44. data/lib/monovm/whois/registry/server_registry.rb +164 -0
  45. data/lib/monovm/whois/registry/sources/base.rb +58 -0
  46. data/lib/monovm/whois/registry/sources/iana_bootstrap.rb +142 -0
  47. data/lib/monovm/whois/registry/sources/json_file.rb +137 -0
  48. data/lib/monovm/whois/response.rb +89 -0
  49. data/lib/monovm/whois/result.rb +114 -0
  50. data/lib/monovm/whois/transport/base.rb +51 -0
  51. data/lib/monovm/whois/transport/factory.rb +51 -0
  52. data/lib/monovm/whois/transport/middleware/base.rb +55 -0
  53. data/lib/monovm/whois/transport/middleware/cache.rb +92 -0
  54. data/lib/monovm/whois/transport/middleware/instrumentation.rb +63 -0
  55. data/lib/monovm/whois/transport/middleware/retry.rb +56 -0
  56. data/lib/monovm/whois/transport/middleware/throttle.rb +62 -0
  57. data/lib/monovm/whois/transport/rdap_http.rb +146 -0
  58. data/lib/monovm/whois/transport/whois_socket.rb +130 -0
  59. data/lib/monovm/whois/version.rb +7 -0
  60. data/lib/monovm/whois/whois_handler.rb +157 -0
  61. data/lib/monovm/whois.rb +142 -0
  62. data/lib/monovm-whois-ruby.rb +5 -0
  63. data/lib/monovm-whois.rb +5 -0
  64. metadata +114 -0
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require_relative "base"
5
+
6
+ module MonoVM
7
+ module Whois
8
+ module Transport
9
+ # WHOIS over TCP port 43 (RFC 3912).
10
+ #
11
+ # The protocol is as thin as protocols get: connect, send the query and a
12
+ # CRLF, read until the server closes. What needs care is everything around
13
+ # that — a registry that accepts the connection and then never answers, one
14
+ # that sends half a record and hangs up, one that replies in Latin-1.
15
+ class WhoisSocket < Base
16
+ DEFAULT_CONNECT_TIMEOUT = 10.0
17
+ DEFAULT_READ_TIMEOUT = 15.0
18
+ CHUNK_SIZE = 16_384
19
+
20
+ # Registries that answer in Latin-1. Trying UTF-8 first and falling back
21
+ # keeps accented registrant names readable rather than replacing them with
22
+ # U+FFFD.
23
+ FALLBACK_ENCODING = Encoding::ISO_8859_1
24
+
25
+ # IO::TimeoutError only exists from Ruby 3.2. Naming it directly in a rescue
26
+ # clause would raise NameError on 3.1 at the moment an exception occurred —
27
+ # turning a timeout into a crash — so the list is built once, here.
28
+ CONNECT_TIMEOUTS = [
29
+ Errno::ETIMEDOUT,
30
+ (IO::TimeoutError if defined?(IO::TimeoutError))
31
+ ].compact.freeze
32
+
33
+ attr_reader :connect_timeout, :read_timeout
34
+
35
+ def initialize(connect_timeout: DEFAULT_CONNECT_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT)
36
+ @connect_timeout = connect_timeout
37
+ @read_timeout = read_timeout
38
+ super()
39
+ end
40
+
41
+ def supports?(endpoint)
42
+ endpoint.socket?
43
+ end
44
+
45
+ # @return [Response]
46
+ # @raise [ConnectionError, TimeoutError, EmptyResponseError]
47
+ def fetch(query:, endpoint:)
48
+ body, elapsed = measure { exchange(query, endpoint) }
49
+
50
+ if body.strip.empty?
51
+ raise EmptyResponseError,
52
+ "#{endpoint.host} closed the connection without answering for #{query}"
53
+ end
54
+
55
+ Response.new(body: body, endpoint: endpoint, query: query, elapsed: elapsed)
56
+ end
57
+
58
+ private
59
+
60
+ def exchange(query, endpoint)
61
+ socket = connect(endpoint)
62
+
63
+ begin
64
+ socket.write("#{query}\r\n")
65
+ read_all(socket, endpoint)
66
+ ensure
67
+ begin
68
+ socket.close
69
+ rescue IOError, SystemCallError
70
+ # Already closed by the peer; nothing to salvage.
71
+ end
72
+ end
73
+ end
74
+
75
+ def connect(endpoint)
76
+ Socket.tcp(endpoint.host, endpoint.port, connect_timeout: connect_timeout)
77
+ rescue *CONNECT_TIMEOUTS => e
78
+ raise TimeoutError, "timed out connecting to #{endpoint.host}:#{endpoint.port} (#{e.class})"
79
+ rescue SocketError, SystemCallError => e
80
+ raise ConnectionError, "cannot reach #{endpoint.host}:#{endpoint.port}: #{e.message}"
81
+ end
82
+
83
+ # Read until the peer closes, bounded by a single deadline for the whole
84
+ # response rather than per chunk — a server that dribbles one byte per
85
+ # second should not be able to hold the caller indefinitely.
86
+ def read_all(socket, endpoint)
87
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + read_timeout
88
+ chunks = []
89
+
90
+ loop do
91
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
92
+ return partial(chunks, endpoint, "read timed out", TimeoutError) if remaining <= 0
93
+
94
+ unless socket.wait_readable(remaining)
95
+ return partial(chunks, endpoint, "read timed out",
96
+ TimeoutError)
97
+ end
98
+
99
+ begin
100
+ chunks << socket.readpartial(CHUNK_SIZE)
101
+ rescue EOFError
102
+ break
103
+ rescue SystemCallError => e
104
+ return partial(chunks, endpoint, e.message, ConnectionError)
105
+ end
106
+ end
107
+
108
+ decode(chunks.join)
109
+ end
110
+
111
+ # A truncated record still classifies correctly far more often than not, so
112
+ # whatever arrived is kept. Nothing at all is a failure, not a verdict.
113
+ def partial(chunks, endpoint, reason, error_class)
114
+ raise error_class, "#{endpoint.host}:#{endpoint.port} #{reason}" if chunks.empty?
115
+
116
+ decode(chunks.join)
117
+ end
118
+
119
+ def decode(raw)
120
+ text = raw.dup.force_encoding(Encoding::UTF_8)
121
+ return text if text.valid_encoding?
122
+
123
+ raw.dup.force_encoding(FALLBACK_ENCODING).encode(Encoding::UTF_8)
124
+ rescue EncodingError
125
+ raw.dup.force_encoding(Encoding::UTF_8).scrub("")
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MonoVM
4
+ module Whois
5
+ VERSION = "1.0.0"
6
+ end
7
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "client"
4
+
5
+ module MonoVM
6
+ module Whois
7
+ # A single-domain handler that wraps one lookup in an object.
8
+ #
9
+ # Exists so that code written against camelCase WHOIS APIs reads the same way
10
+ # here — the camelCase method names are aliased at the bottom.
11
+ #
12
+ # handler = MonoVM::Whois::WhoisHandler.whois("monovm.com")
13
+ # handler.available? # => false
14
+ # handler.whois_message # => the raw registry response
15
+ # handler.record.expires_on
16
+ #
17
+ # One deliberate difference from boolean-style WHOIS APIs: there, an
18
+ # +isAvailable()+ returns false both for a registered domain and for a
19
+ # lookup that failed, and a permissive detector reports a rate-limited or
20
+ # unreachable server as *available*. Here {#available?} is true only when availability was
21
+ # positively established, and {#unknown?} exists to say "we could not find out".
22
+ # Code that branches on +available?+ alone is safe; code that treats
23
+ # +!available?+ as "registered" should ask {#registered?} instead.
24
+ class WhoisHandler
25
+ attr_reader :domain, :result
26
+
27
+ class << self
28
+ # @param domain [String]
29
+ # @param options [Hash] anything {Configuration} accepts
30
+ # @return [WhoisHandler]
31
+ def whois(domain, **options)
32
+ new(domain, **options)
33
+ end
34
+ end
35
+
36
+ def initialize(domain, client: nil, **options)
37
+ @domain = domain
38
+ @client = client || build_client(options)
39
+ @result = @client.lookup(domain)
40
+ end
41
+
42
+ # @return [String] the TLD, with its leading dot, or "" when none was resolved
43
+ def tld
44
+ result.tld.to_s
45
+ end
46
+
47
+ # @return [String] the second-level label, or "" when none was resolved
48
+ def sld
49
+ result.sld.to_s
50
+ end
51
+
52
+ # True only when the response positively established that the name is free.
53
+ def available?
54
+ result.available?
55
+ end
56
+
57
+ # True when the name exists. Distinct from +!available?+, which is also true
58
+ # when the lookup failed.
59
+ def registered?
60
+ result.registered?
61
+ end
62
+
63
+ # True when the registry flagged the name as premium or reserved: unregistered,
64
+ # but not obtainable at standard price.
65
+ def premium?
66
+ result.premium?
67
+ end
68
+
69
+ # True when no verdict could be reached — a refusal, a timeout, an unreadable
70
+ # response. Ask again later; do not treat as free.
71
+ def unknown?
72
+ result.unknown?
73
+ end
74
+
75
+ # True when the domain was well-formed and its TLD is one we can look up.
76
+ def valid?
77
+ !result.invalid?
78
+ end
79
+
80
+ # The raw registry response, or the explanatory message when none was obtained.
81
+ def whois_message
82
+ result.whois_message
83
+ end
84
+
85
+ # @return [Parser::Record, nil]
86
+ def record
87
+ result.record
88
+ end
89
+
90
+ # @return [Symbol] :available, :registered, :premium, :unknown or :invalid
91
+ def status
92
+ result.status
93
+ end
94
+
95
+ # Which rule decided, what it matched, and what every other rule said —
96
+ # the actual decision path rather than a fixed set of boolean flags.
97
+ #
98
+ # @return [Hash]
99
+ def availability_details
100
+ {
101
+ domain: result.name,
102
+ tld: result.tld,
103
+ sld: result.sld,
104
+ status: result.status,
105
+ decided_by: result.verdict&.rule,
106
+ reason: result.reason,
107
+ evidence: result.verdict&.evidence,
108
+ trace: result.verdict&.trace || [],
109
+ endpoint: result.response&.endpoint&.to_s,
110
+ response_length: result.response&.body&.length || 0,
111
+ response_preview: preview
112
+ }
113
+ end
114
+
115
+ def to_h
116
+ result.to_h
117
+ end
118
+
119
+ def inspect
120
+ "#<#{self.class.name} #{result.name.inspect} #{status}>"
121
+ end
122
+
123
+ # ------------------------------------------------------------------
124
+ # camelCase aliases, for callers coming from camelCase WHOIS APIs
125
+ # ------------------------------------------------------------------
126
+
127
+ alias isAvailable available?
128
+ alias isPremium premium?
129
+ alias isValid valid?
130
+ alias getTld tld
131
+ alias getSld sld
132
+ alias getWhoisMessage whois_message
133
+ alias getAvailabilityDetails availability_details
134
+
135
+ private
136
+
137
+ def build_client(options)
138
+ return Client.new if options.empty?
139
+
140
+ config = Configuration.new
141
+ options.each do |key, value|
142
+ setter = "#{key}="
143
+ raise ArgumentError, "unknown option #{key.inspect}" unless config.respond_to?(setter)
144
+
145
+ config.public_send(setter, value)
146
+ end
147
+
148
+ Client.new(config: config)
149
+ end
150
+
151
+ def preview(limit = 200)
152
+ text = whois_message.to_s.strip.gsub(/\s+/, " ")
153
+ text.length > limit ? "#{text[0, limit]}..." : text
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "whois/version"
4
+ require_relative "whois/errors"
5
+ require_relative "whois/paths"
6
+ require_relative "whois/punycode"
7
+ require_relative "whois/domain_name"
8
+ require_relative "whois/endpoint"
9
+ require_relative "whois/response"
10
+ require_relative "whois/availability/verdict"
11
+ require_relative "whois/result"
12
+ require_relative "whois/registry/server_registry"
13
+ require_relative "whois/transport/factory"
14
+ require_relative "whois/availability/analyzer"
15
+ require_relative "whois/parser/selector"
16
+ require_relative "whois/configuration"
17
+ require_relative "whois/referral/follower"
18
+ require_relative "whois/client"
19
+ require_relative "whois/checker"
20
+ require_relative "whois/whois_handler"
21
+
22
+ module MonoVM
23
+ # Domain WHOIS and RDAP lookups.
24
+ #
25
+ # The short way in:
26
+ #
27
+ # MonoVM::Whois.available?("monovm.com") # => false
28
+ # MonoVM::Whois.lookup("monovm.com") # => #<Result "monovm.com" registered>
29
+ # MonoVM::Whois.whois(["monovm", "bing.com"])
30
+ #
31
+ # Settings are global unless overridden per call:
32
+ #
33
+ # MonoVM::Whois.configure do |config|
34
+ # config.prefer = :whois # port 43 before RDAP
35
+ # config.throttle_interval = 1.0 # be gentler with registries
36
+ # config.instrumentation = ->(event) { Rails.logger.info(event) }
37
+ # end
38
+ module Whois
39
+ class << self
40
+ # The global configuration. Mutating it affects the shared {client}.
41
+ def config
42
+ @config ||= Configuration.new
43
+ end
44
+
45
+ # Yields {config} for editing, then discards the cached client so the next
46
+ # lookup picks the changes up.
47
+ #
48
+ # @yieldparam config [Configuration]
49
+ def configure
50
+ yield config if block_given?
51
+ reset_client!
52
+ config
53
+ end
54
+
55
+ # Throw away the global configuration and client. Mainly for tests.
56
+ def reset!
57
+ @config = nil
58
+ reset_client!
59
+ self
60
+ end
61
+
62
+ # The shared client. Built once, because it owns the definition tables and the
63
+ # caches and throttle clocks that make repeated lookups cheap and polite.
64
+ def client
65
+ @client || mutex.synchronize { @client ||= Client.new(config: config) }
66
+ end
67
+
68
+ # Look one domain up.
69
+ #
70
+ # @param domain [String]
71
+ # @return [Result]
72
+ def lookup(domain)
73
+ client.lookup(domain)
74
+ end
75
+
76
+ # @return [Boolean] true only when availability was positively established.
77
+ # A refusal, a timeout or an unreadable response is false, not true.
78
+ def available?(domain)
79
+ client.available?(domain)
80
+ end
81
+
82
+ # @return [Boolean] true when the name is registered
83
+ def registered?(domain)
84
+ lookup(domain).registered?
85
+ end
86
+
87
+ # Check one or many domains, expanding a bare name across popular TLDs.
88
+ #
89
+ # @param domains [String, Enumerable<String>]
90
+ # @param options [Hash]
91
+ # @return [Hash{String => Symbol}] name => status
92
+ def whois(domains, options = {})
93
+ return Checker.new(client: client, config: config).check(domains) if options.empty?
94
+
95
+ Checker.whois(domains, options)
96
+ end
97
+
98
+ # Full results rather than statuses.
99
+ #
100
+ # @return [Hash{String => Result}]
101
+ def check(domains, options = {})
102
+ return Checker.new(client: client, config: config).check_detailed(domains) if options.empty?
103
+
104
+ Checker.lookup(domains, options)
105
+ end
106
+
107
+ # A single-domain handler object, camelCase aliases included.
108
+ #
109
+ # @return [WhoisHandler]
110
+ def handler(domain, **options)
111
+ WhoisHandler.whois(domain, **options)
112
+ end
113
+
114
+ # Why a lookup came out the way it did, including every rule consulted.
115
+ #
116
+ # @return [Hash]
117
+ def explain(domain)
118
+ client.explain(domain)
119
+ end
120
+
121
+ # @return [Array<String>] every TLD that can be looked up, dotted and sorted
122
+ def supported_tlds
123
+ config.server_registry.supported_tlds
124
+ end
125
+
126
+ # @return [Boolean]
127
+ def supports?(tld)
128
+ config.server_registry.supports?(tld)
129
+ end
130
+
131
+ private
132
+
133
+ def reset_client!
134
+ mutex.synchronize { @client = nil }
135
+ end
136
+
137
+ def mutex
138
+ @mutex ||= Mutex.new
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The gem is named +monovm-whois-ruby+, so +require "monovm-whois-ruby"+ has to work.
4
+ # Everything lives under +monovm/whois+ to match the MonoVM::Whois namespace.
5
+ require_relative "monovm/whois"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Convenience alias so +require "monovm-whois"+ works alongside the canonical
4
+ # +require "monovm-whois-ruby"+.
5
+ require_relative "monovm/whois"
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monovm-whois-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - MonoVM
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: |
13
+ Look up domain registration data and check availability over RDAP and WHOIS
14
+ (port 43). Prefers RDAP where a registry offers it, falls back to port 43,
15
+ follows registrar referrals for thin registries, and parses records into
16
+ structured objects. Availability detection is a priority-ordered rule chain
17
+ that reports "unknown" instead of guessing when a server refuses to answer.
18
+ No runtime dependencies.
19
+ email:
20
+ - dev@monovm.com
21
+ executables:
22
+ - monovm-whois
23
+ extensions: []
24
+ extra_rdoc_files: []
25
+ files:
26
+ - CHANGELOG.md
27
+ - LICENSE
28
+ - README.md
29
+ - data/rdap_bootstrap.json
30
+ - data/whois_servers.json
31
+ - exe/monovm-whois
32
+ - lib/monovm-whois-ruby.rb
33
+ - lib/monovm-whois.rb
34
+ - lib/monovm/whois.rb
35
+ - lib/monovm/whois/availability/analyzer.rb
36
+ - lib/monovm/whois/availability/context.rb
37
+ - lib/monovm/whois/availability/patterns.rb
38
+ - lib/monovm/whois/availability/rule.rb
39
+ - lib/monovm/whois/availability/rule_set.rb
40
+ - lib/monovm/whois/availability/rules/availability_keywords.rb
41
+ - lib/monovm/whois/availability/rules/explicit_unavailability.rb
42
+ - lib/monovm/whois/availability/rules/no_match.rb
43
+ - lib/monovm/whois/availability/rules/premium_name.rb
44
+ - lib/monovm/whois/availability/rules/rdap_object.rb
45
+ - lib/monovm/whois/availability/rules/recordless.rb
46
+ - lib/monovm/whois/availability/rules/registration_fields.rb
47
+ - lib/monovm/whois/availability/rules/registry_marker.rb
48
+ - lib/monovm/whois/availability/rules/server_refusal.rb
49
+ - lib/monovm/whois/availability/rules/status_field.rb
50
+ - lib/monovm/whois/availability/rules/tld_specific.rb
51
+ - lib/monovm/whois/availability/rules/wrong_registry.rb
52
+ - lib/monovm/whois/availability/verdict.rb
53
+ - lib/monovm/whois/checker.rb
54
+ - lib/monovm/whois/cli.rb
55
+ - lib/monovm/whois/client.rb
56
+ - lib/monovm/whois/configuration.rb
57
+ - lib/monovm/whois/domain_name.rb
58
+ - lib/monovm/whois/endpoint.rb
59
+ - lib/monovm/whois/errors.rb
60
+ - lib/monovm/whois/parser/base.rb
61
+ - lib/monovm/whois/parser/icann_rdd.rb
62
+ - lib/monovm/whois/parser/key_value.rb
63
+ - lib/monovm/whois/parser/rdap_json.rb
64
+ - lib/monovm/whois/parser/record.rb
65
+ - lib/monovm/whois/parser/selector.rb
66
+ - lib/monovm/whois/paths.rb
67
+ - lib/monovm/whois/punycode.rb
68
+ - lib/monovm/whois/referral/follower.rb
69
+ - lib/monovm/whois/registry/definition.rb
70
+ - lib/monovm/whois/registry/resolution.rb
71
+ - lib/monovm/whois/registry/server_registry.rb
72
+ - lib/monovm/whois/registry/sources/base.rb
73
+ - lib/monovm/whois/registry/sources/iana_bootstrap.rb
74
+ - lib/monovm/whois/registry/sources/json_file.rb
75
+ - lib/monovm/whois/response.rb
76
+ - lib/monovm/whois/result.rb
77
+ - lib/monovm/whois/transport/base.rb
78
+ - lib/monovm/whois/transport/factory.rb
79
+ - lib/monovm/whois/transport/middleware/base.rb
80
+ - lib/monovm/whois/transport/middleware/cache.rb
81
+ - lib/monovm/whois/transport/middleware/instrumentation.rb
82
+ - lib/monovm/whois/transport/middleware/retry.rb
83
+ - lib/monovm/whois/transport/middleware/throttle.rb
84
+ - lib/monovm/whois/transport/rdap_http.rb
85
+ - lib/monovm/whois/transport/whois_socket.rb
86
+ - lib/monovm/whois/version.rb
87
+ - lib/monovm/whois/whois_handler.rb
88
+ homepage: https://github.com/monovm/whois-ruby
89
+ licenses:
90
+ - MIT
91
+ metadata:
92
+ homepage_uri: https://github.com/monovm/whois-ruby
93
+ bug_tracker_uri: https://github.com/monovm/whois-ruby/issues
94
+ changelog_uri: https://github.com/monovm/whois-ruby/blob/main/CHANGELOG.md
95
+ documentation_uri: https://rubydoc.info/gems/monovm-whois-ruby
96
+ rubygems_mfa_required: 'true'
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '3.1'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 4.0.16
112
+ specification_version: 4
113
+ summary: Domain WHOIS and RDAP lookups with reliable availability detection.
114
+ test_files: []