whois-parser-next 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +66 -6
- data/README.md +20 -15
- data/RELEASING.md +2 -2
- data/lib/whois/parser/version.rb +1 -1
- data/lib/whois/parsers/registry_response_safety.rb +7 -5
- data/lib/whois/parsers/whois.auda.org.au.rb +43 -1
- data/lib/whois/parsers/whois.cctld.uz.rb +6 -0
- data/lib/whois/parsers/whois.eu.rb +33 -7
- data/lib/whois/parsers/whois.id.rb +22 -2
- data/lib/whois/parsers/whois.nic.africa.rb +23 -0
- data/lib/whois/parsers/whois.nic.biz.rb +38 -0
- data/lib/whois/parsers/whois.nic.cl.rb +32 -8
- data/lib/whois/parsers/whois.nic.foundation.rb +4 -0
- data/lib/whois/parsers/whois.nic.ge.rb +6 -0
- data/lib/whois/parsers/whois.nic.gifts.rb +13 -0
- data/lib/whois/parsers/whois.nic.gives.rb +46 -0
- data/lib/whois/parsers/whois.nic.giving.rb +44 -0
- data/lib/whois/parsers/whois.nic.help.rb +12 -0
- data/lib/whois/parsers/whois.nic.homes.rb +12 -0
- data/lib/whois/parsers/whois.nic.icu.rb +11 -1
- data/lib/whois/parsers/whois.nic.lat.rb +15 -1
- data/lib/whois/parsers/whois.nic.love.rb +19 -0
- data/lib/whois/parsers/whois.nic.mn.rb +51 -0
- data/lib/whois/parsers/whois.nic.mx.rb +47 -7
- data/lib/whois/parsers/whois.nic.net.sa.rb +6 -0
- data/lib/whois/parsers/whois.nic.news.rb +2 -0
- data/lib/whois/parsers/whois.nic.one.rb +7 -1
- data/lib/whois/parsers/whois.nic.online.rb +25 -1
- data/lib/whois/parsers/whois.nic.open.rb +95 -0
- data/lib/whois/parsers/whois.nic.quest.rb +17 -0
- data/lib/whois/parsers/whois.nic.sbs.rb +28 -0
- data/lib/whois/parsers/whois.nic.store.rb +28 -0
- data/lib/whois/parsers/whois.nic.top.rb +25 -3
- data/lib/whois/parsers/whois.nic.tv.rb +21 -0
- data/lib/whois/parsers/whois.nic.uk.rb +84 -23
- data/lib/whois/parsers/whois.nic.vip.rb +21 -0
- data/lib/whois/parsers/whois.nic.wiki.rb +21 -0
- data/lib/whois/parsers/whois.nic.win.rb +27 -0
- data/lib/whois/parsers/whois.publicinterestregistry.org.rb +52 -0
- data/lib/whois/parsers/whois.punktum.dk.rb +53 -0
- data/lib/whois/parsers/whois.registry.click.rb +9 -0
- data/lib/whois/parsers/whois.registry.gift.rb +10 -0
- data/lib/whois/parsers/whois.sr.rb +44 -0
- data/lib/whois/parsers/whois.tcinet.ru.rb +83 -8
- data/lib/whois/parsers/whois.tld.mu.rb +20 -0
- data/lib/whois/parsers/whois.tonicregistry.to.rb +55 -1
- data/lib/whois/parsers/whois.uniregistry.net.rb +15 -2
- data/lib/whois/parsers/whois.weare.ie.rb +12 -0
- metadata +16 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
require_relative 'base'
|
|
11
|
+
require_relative 'registry_response_safety'
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
module Whois
|
|
@@ -22,6 +23,22 @@ module Whois
|
|
|
22
23
|
# @see http://www.nominet.org.uk/other/whois/detailedinstruct/
|
|
23
24
|
#
|
|
24
25
|
class WhoisNicUk < Base
|
|
26
|
+
include RegistryResponseSafety
|
|
27
|
+
|
|
28
|
+
DOMAIN_NAME_LINE = /^[ \t]*Domain name:[ \t]*(.*?)[ \t]*\r?$/i
|
|
29
|
+
NO_MATCH_LINE = /^[ \t]*No match for "([^"]+)"\.[ \t]*\r?$/i
|
|
30
|
+
NOT_REGISTERED_LINE = /^[ \t]*This domain name has not been registered\.[ \t]*\r?$/i
|
|
31
|
+
REGISTRATION_STATUS_LINE = /\A[ \t]*Registration status:[ \t]*(.*?)[ \t]*\r?\n?\z/i
|
|
32
|
+
REGISTRATION_SUPPORT_LINE = /^[ \t]*(?:Registered on|Expiry date|Last updated|Registrar):/i
|
|
33
|
+
REGISTRATION_EVIDENCE_LINE = /^[ \t]*(?:Domain name|Registration status|Registered on|Expiry date|Last updated|Registrar|Name servers):/i
|
|
34
|
+
REGISTRATION_STATUSES = {
|
|
35
|
+
'registered until expiry date.' => :registered,
|
|
36
|
+
'registration request being processed.' => :registered,
|
|
37
|
+
'renewal request being processed.' => :registered,
|
|
38
|
+
'no longer required' => :registered,
|
|
39
|
+
'renewal required.' => :registered,
|
|
40
|
+
'no registration status listed.' => :reserved,
|
|
41
|
+
}.freeze
|
|
25
42
|
|
|
26
43
|
# == Values for Status
|
|
27
44
|
#
|
|
@@ -29,37 +46,37 @@ module Whois
|
|
|
29
46
|
# @see http://www.nominet.org.uk/registrants/maintain/renew/status/
|
|
30
47
|
#
|
|
31
48
|
property_supported :status do
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
when "no longer required"
|
|
41
|
-
:registered
|
|
42
|
-
when "no registration status listed."
|
|
43
|
-
:reserved
|
|
44
|
-
# NEWSTATUS redemption (https://github.com/weppos/whois/issues/5)
|
|
45
|
-
when "renewal required."
|
|
46
|
-
:registered
|
|
47
|
-
else
|
|
48
|
-
Whois::Parser.bug!(ParserError, "Unknown status `#{::Regexp.last_match(1)}'.")
|
|
49
|
-
end
|
|
50
|
-
elsif invalid?
|
|
49
|
+
body = content_for_scanner
|
|
50
|
+
absence_markers = body.lines.count do |line|
|
|
51
|
+
line.match?(NO_MATCH_LINE) || line.match?(NOT_REGISTERED_LINE)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if absence_markers.positive?
|
|
55
|
+
authoritative_absence?(body) ? :available : :unknown
|
|
56
|
+
elsif invalid? && !registration_evidence?(body)
|
|
51
57
|
:invalid
|
|
52
58
|
else
|
|
53
|
-
|
|
59
|
+
domains = domain_name_values(body)
|
|
60
|
+
statuses = registration_status_values(body)
|
|
61
|
+
if domains.length != 1 || !domains.first.downcase.end_with?('.uk') || statuses.length != 1
|
|
62
|
+
:unknown
|
|
63
|
+
else
|
|
64
|
+
status = REGISTRATION_STATUSES[statuses.first.downcase]
|
|
65
|
+
if status == :registered && !body.match?(REGISTRATION_SUPPORT_LINE)
|
|
66
|
+
:unknown
|
|
67
|
+
else
|
|
68
|
+
status || :unknown
|
|
69
|
+
end
|
|
70
|
+
end
|
|
54
71
|
end
|
|
55
72
|
end
|
|
56
73
|
|
|
57
74
|
property_supported :available? do
|
|
58
|
-
|
|
75
|
+
status == :available
|
|
59
76
|
end
|
|
60
77
|
|
|
61
78
|
property_supported :registered? do
|
|
62
|
-
|
|
79
|
+
status == :registered
|
|
63
80
|
end
|
|
64
81
|
|
|
65
82
|
|
|
@@ -149,7 +166,51 @@ module Whois
|
|
|
149
166
|
# and will be replenished in 50 seconds.
|
|
150
167
|
#
|
|
151
168
|
def response_throttled?
|
|
152
|
-
|
|
169
|
+
content_for_scanner.match?(/^[ \t]*The WHOIS query quota for .+ has been exceeded\b/i) ||
|
|
170
|
+
RegistryResponseSafety.instance_method(:response_throttled?).bind_call(self)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def registration_status_values(body)
|
|
174
|
+
lines = body.lines
|
|
175
|
+
lines.each_with_index.filter_map do |line, index|
|
|
176
|
+
match = REGISTRATION_STATUS_LINE.match(line)
|
|
177
|
+
next unless match
|
|
178
|
+
|
|
179
|
+
value = match[1].strip
|
|
180
|
+
if value.empty?
|
|
181
|
+
next_index = index + 1
|
|
182
|
+
next_index += 1 while next_index < lines.length && lines[next_index].strip.empty?
|
|
183
|
+
value = lines[next_index].to_s.strip
|
|
184
|
+
end
|
|
185
|
+
value unless value.empty?
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def domain_name_values(body)
|
|
190
|
+
lines = body.lines
|
|
191
|
+
lines.each_with_index.filter_map do |line, index|
|
|
192
|
+
match = DOMAIN_NAME_LINE.match(line)
|
|
193
|
+
next unless match
|
|
194
|
+
|
|
195
|
+
value = match[1].strip
|
|
196
|
+
if value.empty?
|
|
197
|
+
next_index = index + 1
|
|
198
|
+
next_index += 1 while next_index < lines.length && lines[next_index].strip.empty?
|
|
199
|
+
value = lines[next_index].to_s.strip.split(/\s+/, 2).first.to_s
|
|
200
|
+
end
|
|
201
|
+
value unless value.empty?
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def authoritative_absence?(body)
|
|
206
|
+
no_match = body.lines.filter_map { |line| NO_MATCH_LINE.match(line)&.captures&.first }
|
|
207
|
+
not_registered = body.lines.count { |line| line.match?(NOT_REGISTERED_LINE) }
|
|
208
|
+
no_match.length == 1 && no_match.first.downcase.end_with?('.uk') &&
|
|
209
|
+
not_registered == 1 && !registration_evidence?(body) && !invalid?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def registration_evidence?(body)
|
|
213
|
+
body.match?(REGISTRATION_EVIDENCE_LINE)
|
|
153
214
|
end
|
|
154
215
|
|
|
155
216
|
|
|
@@ -5,9 +5,30 @@ module Whois
|
|
|
5
5
|
# Identity Digital's current .vip endpoint uses the ICANN key/value
|
|
6
6
|
# layout and reports an absent name as `No Data Found`.
|
|
7
7
|
class WhoisNicVip < BaseIcannCompliant
|
|
8
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status):[ \t]*\S/i
|
|
9
|
+
|
|
8
10
|
self.scanner = Scanners::BaseIcannCompliant, {
|
|
9
11
|
pattern_available: /^No Data Found\s*$/,
|
|
10
12
|
}
|
|
13
|
+
|
|
14
|
+
property_supported :available? do
|
|
15
|
+
raise ResponseIsThrottled if response_throttled?
|
|
16
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
17
|
+
|
|
18
|
+
super()
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def response_unavailable?
|
|
22
|
+
body = content_for_scanner
|
|
23
|
+
ambiguous_absence = body.match?(/^No Data Found\s*$/i) && body.match?(RECORD_EVIDENCE)
|
|
24
|
+
|
|
25
|
+
super || body.match?(/^\s*access denied\b/i) ||
|
|
26
|
+
ambiguous_absence
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def response_throttled?
|
|
30
|
+
super || content_for_scanner.match?(/^\s*too many requests\b/i)
|
|
31
|
+
end
|
|
11
32
|
end
|
|
12
33
|
end
|
|
13
34
|
end
|
|
@@ -2,7 +2,28 @@ require_relative 'base_identity_digital'
|
|
|
2
2
|
module Whois
|
|
3
3
|
class Parsers
|
|
4
4
|
class WhoisNicWiki < BaseIdentityDigital
|
|
5
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status):[ \t]*\S/i
|
|
6
|
+
|
|
5
7
|
self.scanner = Scanners::BaseIcannCompliant, { pattern_available: /^No Data Found\s*$/i }
|
|
8
|
+
|
|
9
|
+
property_supported :available? do
|
|
10
|
+
raise ResponseIsThrottled if response_throttled?
|
|
11
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
12
|
+
|
|
13
|
+
super()
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def response_unavailable?
|
|
17
|
+
body = content_for_scanner
|
|
18
|
+
ambiguous_absence = body.match?(/^No Data Found\s*$/i) && body.match?(RECORD_EVIDENCE)
|
|
19
|
+
|
|
20
|
+
super || body.match?(/^\s*access denied\b/i) ||
|
|
21
|
+
ambiguous_absence
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def response_throttled?
|
|
25
|
+
super || content_for_scanner.match?(/^\s*too many requests\b/i)
|
|
26
|
+
end
|
|
6
27
|
end
|
|
7
28
|
end
|
|
8
29
|
end
|
|
@@ -5,6 +5,33 @@ module Whois
|
|
|
5
5
|
# The current .win endpoint uses the same CentralNic/MarkMonitor layout
|
|
6
6
|
# as .design, including the `No Data Found` absence response.
|
|
7
7
|
class WhoisNicWin < WhoisNicDesign
|
|
8
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status):[ \t]*\S/i
|
|
9
|
+
|
|
10
|
+
property_supported :status do
|
|
11
|
+
raise ResponseIsThrottled if response_throttled?
|
|
12
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
13
|
+
|
|
14
|
+
super()
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
property_supported :available? do
|
|
18
|
+
raise ResponseIsThrottled if response_throttled?
|
|
19
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
20
|
+
|
|
21
|
+
super()
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def response_unavailable?
|
|
25
|
+
body = content_for_scanner
|
|
26
|
+
ambiguous_absence = body.match?(/^No Data Found\s*$/i) && body.match?(RECORD_EVIDENCE)
|
|
27
|
+
|
|
28
|
+
super || body.match?(/^\s*access denied\b/i) ||
|
|
29
|
+
ambiguous_absence
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def response_throttled?
|
|
33
|
+
super || content_for_scanner.match?(/^\s*too many requests\b/i)
|
|
34
|
+
end
|
|
8
35
|
end
|
|
9
36
|
end
|
|
10
37
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require_relative 'whois.pir.org'
|
|
2
|
+
require_relative 'registry_response_safety'
|
|
3
|
+
|
|
4
|
+
module Whois
|
|
5
|
+
class Parsers
|
|
6
|
+
# IANA currently delegates .org WHOIS to whois.publicinterestregistry.org.
|
|
7
|
+
# Its field format matches the legacy whois.pir.org parser.
|
|
8
|
+
class WhoisPublicinterestregistryOrg < WhoisPirOrg
|
|
9
|
+
include RegistryResponseSafety
|
|
10
|
+
|
|
11
|
+
DOMAIN_LINE = /^Domain Name:[ \t]*(\S+)[ \t]*\r?$/i
|
|
12
|
+
ABSENCE_LINE = /\ADomain not found\.[ \t]*\z/i
|
|
13
|
+
REGISTERED_EVIDENCE = /^(Registry Domain ID|Creation Date|Updated Date|Registry Expiry Date|Registrar(?: IANA ID)?|Domain Status):[ \t]*\S/im
|
|
14
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Updated Date|Registry Expiry Date|Registrar(?: IANA ID)?|Domain Status|Name Server):[ \t]*\S/im
|
|
15
|
+
LEGACY_THROTTLE_LINE = /^[ \t]*WHOIS LIMIT EXCEEDED\b/i
|
|
16
|
+
|
|
17
|
+
# WhoisPirOrg has its own scanner-backed throttle check. Call the shared
|
|
18
|
+
# line guard directly as that inherited override parses the whole body.
|
|
19
|
+
# Preserve its established PIR marker without parsing unrelated lines.
|
|
20
|
+
def response_throttled?
|
|
21
|
+
body = content_for_scanner
|
|
22
|
+
body.match?(LEGACY_THROTTLE_LINE) ||
|
|
23
|
+
RegistryResponseSafety.instance_method(:response_throttled?).bind_call(self)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
property_supported :status do
|
|
27
|
+
body = content_for_scanner
|
|
28
|
+
absence_count = body.lines.count { |line| line.chomp.match?(ABSENCE_LINE) }
|
|
29
|
+
|
|
30
|
+
if absence_count.positive?
|
|
31
|
+
absence_count == 1 && !body.match?(RECORD_EVIDENCE) ? :available : :unknown
|
|
32
|
+
else
|
|
33
|
+
domains = body.scan(DOMAIN_LINE).flatten
|
|
34
|
+
evidence_fields = body.scan(REGISTERED_EVIDENCE).flatten.map(&:downcase).uniq
|
|
35
|
+
if domains.length == 1 && domains.first.downcase.end_with?('.org') && evidence_fields.length >= 2
|
|
36
|
+
:registered
|
|
37
|
+
else
|
|
38
|
+
:unknown
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
property_supported :available? do
|
|
44
|
+
status == :available
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
property_supported :registered? do
|
|
48
|
+
status == :registered
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require_relative 'whois.dk-hostmaster.dk'
|
|
2
|
+
require_relative 'registry_response_safety'
|
|
3
|
+
|
|
4
|
+
module Whois
|
|
5
|
+
class Parsers
|
|
6
|
+
# Direct parser for IANA's current .dk WHOIS host. The older
|
|
7
|
+
# whois.dk-hostmaster.dk parser remains available for its legacy route.
|
|
8
|
+
class WhoisPunktumDk < WhoisDkHostmasterDk
|
|
9
|
+
include RegistryResponseSafety
|
|
10
|
+
|
|
11
|
+
DOMAIN_LINE = /^Domain:[ \t]*(\S+)[ \t]*\r?$/i
|
|
12
|
+
STATUS_LINE = /^Status:[ \t]*(\S+)[ \t]*\r?$/i
|
|
13
|
+
ABSENCE_LINE = /\ANo entries found for the selected source\.[ \t]*\z/i
|
|
14
|
+
REGISTERED_EVIDENCE = /^(Registered|Expires|Registrar|Hostname):[ \t]*\S/im
|
|
15
|
+
RECORD_EVIDENCE = /^(?:Domain|DNS|Registered|Expires|Registrar|Registration period|VID|DNSSEC|Status|Hostname):[ \t]*\S/im
|
|
16
|
+
|
|
17
|
+
property_supported :domain do
|
|
18
|
+
content_for_scanner.scan(DOMAIN_LINE).flatten.first&.downcase
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
property_supported :status do
|
|
22
|
+
body = content_for_scanner
|
|
23
|
+
absence_count = body.lines.count { |line| line.chomp.match?(ABSENCE_LINE) }
|
|
24
|
+
|
|
25
|
+
if absence_count.positive?
|
|
26
|
+
absence_count == 1 && !body.match?(RECORD_EVIDENCE) ? :available : :unknown
|
|
27
|
+
else
|
|
28
|
+
domains = body.scan(DOMAIN_LINE).flatten
|
|
29
|
+
statuses = body.scan(STATUS_LINE).flatten
|
|
30
|
+
evidence_fields = body.scan(REGISTERED_EVIDENCE).flatten.map(&:downcase).uniq
|
|
31
|
+
if domains.length == 1 && domains.first.downcase.end_with?('.dk') && statuses.length == 1 && evidence_fields.length >= 2
|
|
32
|
+
case statuses.first.downcase
|
|
33
|
+
when 'active' then :registered
|
|
34
|
+
when 'deactivated' then :expired
|
|
35
|
+
when 'reserved' then :reserved
|
|
36
|
+
else :unknown
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
:unknown
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
property_supported :available? do
|
|
45
|
+
status == :available
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
property_supported :registered? do
|
|
49
|
+
status == :registered
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require_relative 'base_top1000_icann'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# Parser for the IANA-listed WHOIS server for .SR.
|
|
6
|
+
#
|
|
7
|
+
# IANA's delegation record and current server responses were checked on
|
|
8
|
+
# 2026-09-25. The generated-name response uses an exact Message marker;
|
|
9
|
+
# other incomplete responses must remain unknown.
|
|
10
|
+
class WhoisSr < BaseTop1000Icann
|
|
11
|
+
property_supported :domain do
|
|
12
|
+
node("Domain", &:downcase)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
property_supported :status do
|
|
16
|
+
if reserved?
|
|
17
|
+
:reserved
|
|
18
|
+
elsif available?
|
|
19
|
+
:available
|
|
20
|
+
elsif !node("Domain").to_s.strip.empty? &&
|
|
21
|
+
node("Status").to_s.casecmp("active").zero? &&
|
|
22
|
+
!node("Creation Date").to_s.strip.empty?
|
|
23
|
+
:registered
|
|
24
|
+
else
|
|
25
|
+
:unknown
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
property_supported :available? do
|
|
30
|
+
content_for_scanner.match?(/^Message: No Object Found\s*$/i)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
property_supported :registered? do
|
|
34
|
+
status == :registered
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
property_supported :nameservers do
|
|
38
|
+
Array.wrap(node("Name server")).reject(&:empty?).map do |name|
|
|
39
|
+
Parser::Nameserver.new(name: name.downcase)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
require_relative 'base'
|
|
11
|
+
require_relative 'registry_response_safety'
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
module Whois
|
|
@@ -23,30 +24,35 @@ module Whois
|
|
|
23
24
|
# The Example parser for the list of all available methods.
|
|
24
25
|
#
|
|
25
26
|
class WhoisTcinetRu < Base
|
|
27
|
+
include RegistryResponseSafety
|
|
28
|
+
|
|
29
|
+
DOMAIN_PATTERN = /\A(?:[[:alnum:]](?:[[:alnum:]-]{0,61}[[:alnum:]])?\.)+[[:alnum:]](?:[[:alnum:]-]{0,61}[[:alnum:]])?\z/i
|
|
30
|
+
KNOWN_STATES = %w[REGISTERED DELEGATED NOT\ DELEGATED VERIFIED UNVERIFIED].freeze
|
|
26
31
|
|
|
27
32
|
property_supported :domain do
|
|
28
|
-
|
|
29
|
-
::Regexp.last_match(1).downcase
|
|
30
|
-
end
|
|
33
|
+
domain_value&.downcase
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
property_not_supported :domain_id
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
property_supported :status do
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
case classify_status
|
|
41
|
+
when :registered
|
|
42
|
+
state_values
|
|
43
|
+
when :available
|
|
40
44
|
[]
|
|
45
|
+
else
|
|
46
|
+
:unknown
|
|
41
47
|
end
|
|
42
48
|
end
|
|
43
49
|
|
|
44
50
|
property_supported :available? do
|
|
45
|
-
|
|
51
|
+
classify_status == :available
|
|
46
52
|
end
|
|
47
53
|
|
|
48
54
|
property_supported :registered? do
|
|
49
|
-
|
|
55
|
+
classify_status == :registered
|
|
50
56
|
end
|
|
51
57
|
|
|
52
58
|
|
|
@@ -108,6 +114,75 @@ module Whois
|
|
|
108
114
|
end
|
|
109
115
|
end
|
|
110
116
|
|
|
117
|
+
def response_unavailable?
|
|
118
|
+
super || content_for_scanner.match?(/^\s*access denied\b/i)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def response_throttled?
|
|
122
|
+
super || content_for_scanner.match?(/^\s*too many requests\b/i)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def classify_status
|
|
129
|
+
cached_properties_fetch(:classified_status) do
|
|
130
|
+
response = content_for_scanner
|
|
131
|
+
domains = domain_lines
|
|
132
|
+
state_rows = state_lines
|
|
133
|
+
states = state_values
|
|
134
|
+
normalized_states = states.map(&:upcase)
|
|
135
|
+
valid_states = states.any? && normalized_states.all? { |state| KNOWN_STATES.include?(state) }
|
|
136
|
+
conflicting_delegation = normalized_states.include?("DELEGATED") &&
|
|
137
|
+
normalized_states.include?("NOT DELEGATED")
|
|
138
|
+
conflicting_verification = normalized_states.include?("VERIFIED") &&
|
|
139
|
+
normalized_states.include?("UNVERIFIED")
|
|
140
|
+
safe_response = !response_unavailable? && !response_throttled?
|
|
141
|
+
absence_count = response.lines.count do |line|
|
|
142
|
+
line.chomp.match?(/\ANo entries found for the selected source\(s\)\.[ \t]*\z/i)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if safe_response && absence_count == 1 && domains.empty? && state_rows.empty?
|
|
146
|
+
:available
|
|
147
|
+
elsif safe_response && domains.one? && domain_value && state_rows.one? &&
|
|
148
|
+
normalized_states.include?("REGISTERED") && valid_states &&
|
|
149
|
+
!conflicting_delegation && !conflicting_verification && absence_count.zero?
|
|
150
|
+
:registered
|
|
151
|
+
else
|
|
152
|
+
:unknown
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def domain_lines
|
|
158
|
+
content_for_scanner.lines.filter_map do |line|
|
|
159
|
+
line.chomp.match(/\Adomain:[ \t]*(.*?)[ \t]*\z/i)&.[](1)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def domain_value
|
|
164
|
+
domains = domain_lines
|
|
165
|
+
return unless domains.one? && domains.first.match?(DOMAIN_PATTERN)
|
|
166
|
+
|
|
167
|
+
domains.first
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def state_lines
|
|
171
|
+
content_for_scanner.lines.filter_map do |line|
|
|
172
|
+
line.chomp.match(/\Astate:[ \t]*(.*?)[ \t]*\z/i)&.[](1)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def state_values
|
|
177
|
+
rows = state_lines
|
|
178
|
+
return [] unless rows.one?
|
|
179
|
+
|
|
180
|
+
values = rows.first.split(",", -1).map(&:strip)
|
|
181
|
+
return [] if values.any?(&:empty?)
|
|
182
|
+
|
|
183
|
+
values
|
|
184
|
+
end
|
|
185
|
+
|
|
111
186
|
end
|
|
112
187
|
|
|
113
188
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require_relative 'base_icann_compliant'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# Parser for the IANA-listed .mu WHOIS server, whois.tld.mu.
|
|
6
|
+
#
|
|
7
|
+
# Captures from 2026-09-25 expose ICANN-style records and the exact
|
|
8
|
+
# `Domain not found.` absence marker. This host is distinct from the
|
|
9
|
+
# legacy whois.nic.mu mapping in whois 6.0.3.
|
|
10
|
+
class WhoisTldMu < BaseIcannCompliant
|
|
11
|
+
self.scanner = Scanners::BaseIcannCompliant, {
|
|
12
|
+
pattern_available: /^[ \t]*Domain not found\.[ \t]*$/i,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
property_supported :expires_on do
|
|
16
|
+
node("Registry Expiry Date") { |value| parse_time(value) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -4,9 +4,63 @@ module Whois
|
|
|
4
4
|
class Parsers
|
|
5
5
|
# Parser for the whois.tonicregistry.to server.
|
|
6
6
|
class WhoisTonicregistryTo < BaseIcannCompliant
|
|
7
|
+
AVAILABLE_DOMAIN_LINE = /^>>> Domain (?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+to is available for registration$/i
|
|
8
|
+
DOMAIN_PATTERN = /\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+to\z/i
|
|
9
|
+
REGISTRATION_FIELD = /^[ \t]*(?:Domain Name|Registry Domain ID|Updated Date|Creation Date|Registry Expiry Date|Registrar(?: URL| IANA ID| Abuse Contact (?:Email|Phone))?|Domain Status|Name Server|DNSSEC):[ \t]*\S/i
|
|
10
|
+
|
|
7
11
|
self.scanner = Scanners::BaseIcannCompliant, {
|
|
8
|
-
pattern_available:
|
|
12
|
+
pattern_available: AVAILABLE_DOMAIN_LINE,
|
|
9
13
|
}
|
|
14
|
+
|
|
15
|
+
property_supported :status do
|
|
16
|
+
if available_marker_count == 1
|
|
17
|
+
:available
|
|
18
|
+
elsif registered_record?
|
|
19
|
+
:registered
|
|
20
|
+
else
|
|
21
|
+
:unknown
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
property_supported :available? do
|
|
26
|
+
status == :available
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
property_supported :registered? do
|
|
30
|
+
status == :registered
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def response_unavailable?
|
|
34
|
+
super || ambiguous_availability_response?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def available_marker_count
|
|
40
|
+
content_for_scanner.lines.count { |line| line.match?(AVAILABLE_DOMAIN_LINE) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ambiguous_availability_response?
|
|
44
|
+
marker_count = available_marker_count
|
|
45
|
+
marker_count.positive? && (
|
|
46
|
+
marker_count != 1 || content_for_scanner.match?(REGISTRATION_FIELD)
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def registered_record?
|
|
51
|
+
response = content_for_scanner
|
|
52
|
+
domains = response.scan(/^[ \t]*Domain Name:[ \t]*(.*?)[ \t]*$/i).flatten
|
|
53
|
+
registry_ids = response.scan(/^[ \t]*Registry Domain ID:[ \t]*(.*?)[ \t]*$/i).flatten
|
|
54
|
+
creation_dates = response.scan(/^[ \t]*Creation Date:[ \t]*(.*?)[ \t]*$/i).flatten
|
|
55
|
+
registrars = response.scan(/^[ \t]*Registrar:[ \t]*(.*?)[ \t]*$/i).flatten
|
|
56
|
+
nameservers = response.scan(/^[ \t]*Name Server:[ \t]*(.*?)[ \t]*$/i).flatten
|
|
57
|
+
|
|
58
|
+
domains.one? && domains.first.match?(DOMAIN_PATTERN) &&
|
|
59
|
+
registry_ids.one? && !registry_ids.first.empty? &&
|
|
60
|
+
creation_dates.one? && parse_time(creation_dates.first) &&
|
|
61
|
+
registrars.one? && !registrars.first.empty? &&
|
|
62
|
+
nameservers.any? { |nameserver| !nameserver.empty? }
|
|
63
|
+
end
|
|
10
64
|
end
|
|
11
65
|
end
|
|
12
66
|
end
|
|
@@ -19,8 +19,11 @@ module Whois
|
|
|
19
19
|
# The Example parser for the list of all available methods.
|
|
20
20
|
#
|
|
21
21
|
class WhoisUniregistryNet < BaseIcannCompliant
|
|
22
|
+
AVAILABLE_DOMAIN_LINE = /^>>> Domain (?:"[^"\r\n]+"|[^\s\r\n]+) is available for registration[ \t]*$/
|
|
23
|
+
RECORD_EVIDENCE = /^[ \t]*(?:Domain Name|Domain ID|Registry Domain ID|Creation Date|Registrar|Sponsoring Registrar|Name Server):[ \t]*\S/i
|
|
24
|
+
|
|
22
25
|
self.scanner = Scanners::BaseIcannCompliant, {
|
|
23
|
-
pattern_available:
|
|
26
|
+
pattern_available: AVAILABLE_DOMAIN_LINE,
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
|
|
@@ -52,12 +55,22 @@ module Whois
|
|
|
52
55
|
def response_unavailable?
|
|
53
56
|
super || content_for_scanner.match?(
|
|
54
57
|
/^(?:>>> Tld not supported by this registry interface|TLD is not supported\.)$/i
|
|
55
|
-
)
|
|
58
|
+
) || ambiguous_availability_response?
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
|
|
59
62
|
private
|
|
60
63
|
|
|
64
|
+
def ambiguous_availability_response?
|
|
65
|
+
marker_count = content_for_scanner.lines.count do |line|
|
|
66
|
+
line.match?(AVAILABLE_DOMAIN_LINE)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
marker_count.positive? && (
|
|
70
|
+
marker_count != 1 || content_for_scanner.match?(RECORD_EVIDENCE)
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
61
74
|
def build_contact(element, type)
|
|
62
75
|
if (contact = super)
|
|
63
76
|
contact.id = node("#{element} ID")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require_relative 'base_icann_compliant'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# Parser for the current IANA-listed .IE WHOIS host, whois.weare.ie.
|
|
6
|
+
class WhoisWeareIe < BaseIcannCompliant
|
|
7
|
+
self.scanner = Scanners::BaseIcannCompliant, {
|
|
8
|
+
pattern_available: /^Not found: [a-z0-9-]+\.ie\s*$/i,
|
|
9
|
+
}
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|