whois-parser-next 0.3.2 → 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 +32 -0
- data/README.md +3 -3
- 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.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.tcinet.ru.rb +83 -8
- 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 +13 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#--
|
|
2
|
+
# Ruby Whois
|
|
3
|
+
#
|
|
4
|
+
# An intelligent pure Ruby WHOIS client and parser.
|
|
5
|
+
#++
|
|
6
|
+
|
|
7
|
+
require_relative 'base_icann_compliant'
|
|
8
|
+
|
|
9
|
+
module Whois
|
|
10
|
+
class Parsers
|
|
11
|
+
# Parser for the IANA-listed .mn WHOIS host, whois.nic.mn.
|
|
12
|
+
class WhoisNicMn < BaseIcannCompliant
|
|
13
|
+
self.scanner = Scanners::BaseIcannCompliant, {
|
|
14
|
+
pattern_available: /^[ \t]*Domain not found\.[ \t\r]*$/i,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
property_supported :status do
|
|
18
|
+
classify_status
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
property_supported :available? do
|
|
22
|
+
classify_status == :available
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
property_supported :registered? do
|
|
26
|
+
classify_status == :registered
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def classify_status
|
|
32
|
+
cached_properties_fetch(:classified_status) do
|
|
33
|
+
missing = content_for_scanner.match?(/\A[ \t]*Domain not found\.[ \t\r]*(?:\n|\z)/i)
|
|
34
|
+
denial = content_for_scanner.match?(/^[ \t]*(?:[%#][ \t]*)?(?:access denied|permission denied|request denied|forbidden)\b/i)
|
|
35
|
+
domains = content_for_scanner.scan(/^[ \t]*Domain Name:[ \t]*((?:[a-z0-9-]+\.)+mn)[ \t\r]*$/i).flatten.uniq
|
|
36
|
+
has_registrar = content_for_scanner.match?(/^[ \t]*Registrar:[ \t]*\S/i)
|
|
37
|
+
has_record_detail = content_for_scanner.match?(/^[ \t]*(?:Creation Date|Updated Date|Registry Expiry Date|Name Server):[ \t]*\S/i)
|
|
38
|
+
has_record = domains.one? && has_registrar && has_record_detail
|
|
39
|
+
|
|
40
|
+
if missing && domains.empty? && !denial && !response_throttled?
|
|
41
|
+
:available
|
|
42
|
+
elsif has_record && !missing && !denial && !response_throttled?
|
|
43
|
+
:registered
|
|
44
|
+
else
|
|
45
|
+
:unknown
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
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
|
|
@@ -25,21 +26,22 @@ module Whois
|
|
|
25
26
|
# and examples.
|
|
26
27
|
#
|
|
27
28
|
class WhoisNicMx < Base
|
|
29
|
+
include RegistryResponseSafety
|
|
30
|
+
|
|
31
|
+
DOMAIN_PATTERN = /\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+mx\z/i
|
|
32
|
+
NO_RECORD_MARKER = %r{^[ \t]*No_Se_Encontro_El_Objeto/Object_Not_Found[ \t]*$}i
|
|
33
|
+
RECORD_EVIDENCE = /^[ \t]*(?:Created On|Expiration Date|Registrar|Name Servers|DNS):/i
|
|
28
34
|
|
|
29
35
|
property_supported :status do
|
|
30
|
-
|
|
31
|
-
:available
|
|
32
|
-
else
|
|
33
|
-
:registered
|
|
34
|
-
end
|
|
36
|
+
classify_status
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
property_supported :available? do
|
|
38
|
-
|
|
40
|
+
classify_status == :available
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
property_supported :registered? do
|
|
42
|
-
|
|
44
|
+
classify_status == :registered
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
|
|
@@ -73,6 +75,44 @@ module Whois
|
|
|
73
75
|
end
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
def response_unavailable?
|
|
79
|
+
super || content_for_scanner.match?(
|
|
80
|
+
/^\s*(?:access denied|permission denied|request denied|forbidden)\b/i
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def classify_status
|
|
87
|
+
cached_properties_fetch(:classified_status) do
|
|
88
|
+
response = content_for_scanner
|
|
89
|
+
absent = response.lines.one? { |line| line.match?(NO_RECORD_MARKER) }
|
|
90
|
+
domain_rows = response.scan(/^[ \t]*Domain Name:[ \t]*(.*?)[ \t\r]*$/i).flatten
|
|
91
|
+
domains = domain_rows.grep(DOMAIN_PATTERN)
|
|
92
|
+
has_created = response.match?(/^[ \t]*Created On:[ \t]*\S/i)
|
|
93
|
+
has_expires = response.match?(/^[ \t]*Expiration Date:[ \t]*\S/i)
|
|
94
|
+
has_registrar = response.match?(/^[ \t]*Registrar:[ \t]*\S/i)
|
|
95
|
+
registered = [
|
|
96
|
+
domain_rows.one?,
|
|
97
|
+
domains.one?,
|
|
98
|
+
has_created,
|
|
99
|
+
has_expires,
|
|
100
|
+
has_registrar,
|
|
101
|
+
!response_unavailable?,
|
|
102
|
+
!response_throttled?,
|
|
103
|
+
].all?
|
|
104
|
+
has_record_evidence = response.match?(RECORD_EVIDENCE)
|
|
105
|
+
|
|
106
|
+
if absent && domain_rows.empty? && !has_record_evidence && !response_unavailable? && !response_throttled?
|
|
107
|
+
:available
|
|
108
|
+
elsif registered && !absent
|
|
109
|
+
:registered
|
|
110
|
+
else
|
|
111
|
+
:unknown
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
76
116
|
end
|
|
77
117
|
|
|
78
118
|
end
|
|
@@ -65,6 +65,12 @@ module Whois
|
|
|
65
65
|
private
|
|
66
66
|
|
|
67
67
|
def classify_status
|
|
68
|
+
cached_properties_fetch(:classified_status) do
|
|
69
|
+
classify_status_from_content
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def classify_status_from_content
|
|
68
74
|
registered = registered_domain_names
|
|
69
75
|
absent = absence_domain_names
|
|
70
76
|
|
|
@@ -2,6 +2,8 @@ require_relative 'base_unsupported_registry'
|
|
|
2
2
|
|
|
3
3
|
module Whois
|
|
4
4
|
class Parsers
|
|
5
|
+
# IANA currently lists RDAP only for .news. Keep this adapter only so a
|
|
6
|
+
# legacy upstream WHOIS route fails explicitly instead of claiming status.
|
|
5
7
|
class WhoisNicNews < BaseUnsupportedRegistry
|
|
6
8
|
end
|
|
7
9
|
end
|
|
@@ -8,7 +8,9 @@ module Whois
|
|
|
8
8
|
# footer. Keep that exact evidence separate from an unparseable response.
|
|
9
9
|
class WhoisNicOne < BaseIcannCompliant
|
|
10
10
|
property_supported :status do
|
|
11
|
-
if
|
|
11
|
+
if contradictory_registration_and_absence?
|
|
12
|
+
:unknown
|
|
13
|
+
elsif no_data_found?
|
|
12
14
|
:available
|
|
13
15
|
elsif registered_response?
|
|
14
16
|
:registered
|
|
@@ -31,6 +33,10 @@ module Whois
|
|
|
31
33
|
content_for_scanner.lines.first.to_s.strip.casecmp?("No Data Found")
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
def contradictory_registration_and_absence?
|
|
37
|
+
no_data_found? && registered_response?
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def registered_response?
|
|
35
41
|
content_for_scanner.match?(/^Domain Name:\s+\S+/) &&
|
|
36
42
|
content_for_scanner.match?(/^Domain Status:\s+\S+/)
|
|
@@ -12,8 +12,25 @@ module Whois
|
|
|
12
12
|
|
|
13
13
|
self.scanner = Scanners::BaseIcannCompliant
|
|
14
14
|
|
|
15
|
+
property_supported :status do
|
|
16
|
+
if contradictory_registration_and_absence?
|
|
17
|
+
:unknown
|
|
18
|
+
elsif available?
|
|
19
|
+
:available
|
|
20
|
+
elsif node('Domain Name').to_s.strip.empty? ||
|
|
21
|
+
[node('Registry Domain ID'), node('Creation Date'), node('Registrar')].all? { |value| value.to_s.strip.empty? }
|
|
22
|
+
:unknown
|
|
23
|
+
else
|
|
24
|
+
:registered
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
15
28
|
property_supported :available? do
|
|
16
|
-
!!(content_for_scanner =~ /^>>> Domain \S+ is available for registration\s*$/i)
|
|
29
|
+
!contradictory_registration_and_absence? && !!(content_for_scanner =~ /^>>> Domain \S+ is available for registration\s*$/i)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
property_supported :registered? do
|
|
33
|
+
status == :registered
|
|
17
34
|
end
|
|
18
35
|
|
|
19
36
|
# Radix can answer with a limit or client-denial notice that contains no
|
|
@@ -33,6 +50,13 @@ module Whois
|
|
|
33
50
|
node("Registry Expiry Date") { |value| parse_time(value) }
|
|
34
51
|
end
|
|
35
52
|
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def contradictory_registration_and_absence?
|
|
56
|
+
!node('Domain Name').to_s.strip.empty? &&
|
|
57
|
+
content_for_scanner.match?(/^>>> Domain \S+ is available for registration\s*$/i)
|
|
58
|
+
end
|
|
59
|
+
|
|
36
60
|
end
|
|
37
61
|
|
|
38
62
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require_relative 'base_icann_compliant'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# Conservative parser for American Express's restricted .OPEN registry.
|
|
6
|
+
# The registry's exact "No Data Found" response is not public availability:
|
|
7
|
+
# the published policy restricts registrations to the operator, qualified
|
|
8
|
+
# affiliates, and trademark licensees.
|
|
9
|
+
class WhoisNicOpen < BaseIcannCompliant
|
|
10
|
+
DOMAIN_PATTERN = /\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+open\z/i
|
|
11
|
+
DOMAIN_ID_PATTERN = /\AD\d+-OPEN\z/i
|
|
12
|
+
NO_DATA_MARKER = /^[ \t]*No Data Found[ \t]*$/i
|
|
13
|
+
RECORD_FIELD = /^[ \t]*(?:Domain Name|Registry Domain ID|Updated Date|Creation Date|Registry Expiry Date|Registrar|Registrar IANA ID|Domain Status|Name Server|DNSSEC):[ \t]*\S/i
|
|
14
|
+
REGISTRAR_NAME = /\AAmerican Express Travel Related Services, Inc\.\z/i
|
|
15
|
+
NAMESERVER_PATTERN = /\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.?\z/i
|
|
16
|
+
|
|
17
|
+
property_supported :domain do
|
|
18
|
+
values = field_values('Domain Name')
|
|
19
|
+
values.one? && values.first.match?(DOMAIN_PATTERN) ? values.first.downcase : nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
property_supported :domain_id do
|
|
23
|
+
values = field_values('Registry Domain ID')
|
|
24
|
+
values.one? && values.first.match?(DOMAIN_ID_PATTERN) ? values.first : nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
property_supported :status do
|
|
28
|
+
classify_status
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
property_supported :available? do
|
|
32
|
+
classify_status == :available
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
property_supported :registered? do
|
|
36
|
+
classify_status == :registered
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
property_supported :expires_on do
|
|
40
|
+
values = field_values('Registry Expiry Date')
|
|
41
|
+
parse_time(values.first) if values.one?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def response_unavailable?
|
|
45
|
+
body = content_for_scanner
|
|
46
|
+
marker_count = body.lines.count { |line| line.match?(NO_DATA_MARKER) }
|
|
47
|
+
|
|
48
|
+
super || (marker_count.positive? && (marker_count != 1 || body.match?(RECORD_FIELD)))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def classify_status
|
|
54
|
+
cached_properties_fetch(:classified_status) do
|
|
55
|
+
complete_registered_record? ? :registered : :unknown
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def complete_registered_record?
|
|
60
|
+
domains = field_values('Domain Name')
|
|
61
|
+
ids = field_values('Registry Domain ID')
|
|
62
|
+
registrars = field_values('Registrar')
|
|
63
|
+
registrar_ids = field_values('Registrar IANA ID')
|
|
64
|
+
|
|
65
|
+
domains.one? && domains.first.match?(DOMAIN_PATTERN) &&
|
|
66
|
+
ids.one? && ids.first.match?(DOMAIN_ID_PATTERN) &&
|
|
67
|
+
registrars.one? && registrars.first.match?(REGISTRAR_NAME) &&
|
|
68
|
+
registrar_ids == ['9999'] &&
|
|
69
|
+
valid_date_field?('Creation Date') &&
|
|
70
|
+
valid_date_field?('Updated Date') &&
|
|
71
|
+
valid_date_field?('Registry Expiry Date') &&
|
|
72
|
+
valid_nameservers? && field_values('Domain Status').any?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def valid_date_field?(name)
|
|
76
|
+
values = field_values(name)
|
|
77
|
+
return false unless values.one?
|
|
78
|
+
|
|
79
|
+
Time.iso8601(values.first)
|
|
80
|
+
true
|
|
81
|
+
rescue ArgumentError
|
|
82
|
+
false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def valid_nameservers?
|
|
86
|
+
values = field_values('Name Server')
|
|
87
|
+
values.any? && values.all? { |value| value.match?(NAMESERVER_PATTERN) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def field_values(name)
|
|
91
|
+
content_for_scanner.scan(/^[ \t]*#{Regexp.escape(name)}:[ \t]*(.*?)[ \t\r]*$/i).flatten
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -3,6 +3,23 @@ require_relative 'whois.centralnic.com'
|
|
|
3
3
|
module Whois
|
|
4
4
|
class Parsers
|
|
5
5
|
class WhoisNicQuest < WhoisCentralnicCom
|
|
6
|
+
property_supported :available? do
|
|
7
|
+
!contradictory_registration_and_absence? && !!node('status:available')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
property_supported :registered? do
|
|
11
|
+
!contradictory_registration_and_absence? &&
|
|
12
|
+
!available? && !Array.wrap(node('Status') || node('Domain Status')).empty? &&
|
|
13
|
+
!node('Domain Name').to_s.strip.empty? &&
|
|
14
|
+
Array.wrap(node('Status') || node('Domain Status')).none? { |value| value.to_s.strip.casecmp('unknown').zero? }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def contradictory_registration_and_absence?
|
|
20
|
+
!node('Domain Name').to_s.strip.empty? &&
|
|
21
|
+
content_for_scanner.match?(/^(?:DOMAIN NOT FOUND|The queried object does not exist: DOMAIN NOT FOUND|>>> Domain \S+ is available for registration)\s*$/i)
|
|
22
|
+
end
|
|
6
23
|
end
|
|
7
24
|
end
|
|
8
25
|
end
|
|
@@ -3,6 +3,34 @@ module Whois
|
|
|
3
3
|
class Parsers
|
|
4
4
|
class WhoisNicSbs < BaseIdentityDigital
|
|
5
5
|
self.scanner = Scanners::BaseIcannCompliant, { pattern_available: /^The queried object does not exist: DOMAIN NOT FOUND\s*$/i }
|
|
6
|
+
|
|
7
|
+
property_supported :status do
|
|
8
|
+
if contradictory_registration_and_absence?
|
|
9
|
+
:unknown
|
|
10
|
+
elsif available?
|
|
11
|
+
:available
|
|
12
|
+
elsif node('Domain Name').to_s.strip.empty? ||
|
|
13
|
+
[node('Registry Domain ID'), node('Creation Date'), node('Registrar')].all? { |value| value.to_s.strip.empty? }
|
|
14
|
+
:unknown
|
|
15
|
+
else
|
|
16
|
+
:registered
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
property_supported :available? do
|
|
21
|
+
!contradictory_registration_and_absence? && !!node('status:available')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
property_supported :registered? do
|
|
25
|
+
status == :registered
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def contradictory_registration_and_absence?
|
|
31
|
+
!node('Domain Name').to_s.strip.empty? &&
|
|
32
|
+
content_for_scanner.match?(/^The queried object does not exist: DOMAIN NOT FOUND\s*$/i)
|
|
33
|
+
end
|
|
6
34
|
end
|
|
7
35
|
end
|
|
8
36
|
end
|
|
@@ -10,6 +10,34 @@ module Whois
|
|
|
10
10
|
self.scanner = Scanners::BaseIcannCompliant, {
|
|
11
11
|
pattern_available: /^>>> Domain \S+ is available for registration\s*$/i,
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
property_supported :status do
|
|
15
|
+
if contradictory_registration_and_absence?
|
|
16
|
+
:unknown
|
|
17
|
+
elsif available?
|
|
18
|
+
:available
|
|
19
|
+
elsif node('Domain Name').to_s.strip.empty? ||
|
|
20
|
+
[node('Registry Domain ID'), node('Creation Date'), node('Registrar')].all? { |value| value.to_s.strip.empty? }
|
|
21
|
+
:unknown
|
|
22
|
+
else
|
|
23
|
+
:registered
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
property_supported :available? do
|
|
28
|
+
!contradictory_registration_and_absence? && !!node('status:available')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
property_supported :registered? do
|
|
32
|
+
status == :registered
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def contradictory_registration_and_absence?
|
|
38
|
+
!node('Domain Name').to_s.strip.empty? &&
|
|
39
|
+
content_for_scanner.match?(/^>>> Domain \S+ is available for registration\s*$/i)
|
|
40
|
+
end
|
|
13
41
|
end
|
|
14
42
|
end
|
|
15
43
|
end
|
|
@@ -5,10 +5,32 @@ module Whois
|
|
|
5
5
|
# The .top registry uses the shared ICANN record shape and returns the full
|
|
6
6
|
# queried .top name in its exact no-record response.
|
|
7
7
|
class WhoisNicTop < BaseTop1000Icann
|
|
8
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status):[ \t]*\S/i
|
|
9
|
+
NO_RECORD_MARKER = /
|
|
10
|
+
^The\ queried\ object\ does\ not\ exist:\s+
|
|
11
|
+
(?:DOMAIN\ NOT\ FOUND|no\ matching\ objects\ found|
|
|
12
|
+
(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)*
|
|
13
|
+
[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.top)
|
|
14
|
+
\s*$
|
|
15
|
+
/ix
|
|
16
|
+
|
|
8
17
|
property_supported :available? do
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
raise ResponseIsThrottled if response_throttled?
|
|
19
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
20
|
+
|
|
21
|
+
super() || content_for_scanner.match?(NO_RECORD_MARKER)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def response_unavailable?
|
|
25
|
+
body = content_for_scanner
|
|
26
|
+
ambiguous_absence = body.match?(NO_RECORD_MARKER) && 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)
|
|
12
34
|
end
|
|
13
35
|
end
|
|
14
36
|
end
|
|
@@ -7,9 +7,30 @@ module Whois
|
|
|
7
7
|
class WhoisNicTv < BaseIcannCompliant
|
|
8
8
|
include RegistryResponseSafety
|
|
9
9
|
|
|
10
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status):[ \t]*\S/i
|
|
11
|
+
|
|
10
12
|
self.scanner = Scanners::BaseIcannCompliant, {
|
|
11
13
|
pattern_available: /^No Data Found\n/,
|
|
12
14
|
}
|
|
15
|
+
|
|
16
|
+
property_supported :available? do
|
|
17
|
+
raise ResponseIsThrottled if response_throttled?
|
|
18
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
19
|
+
|
|
20
|
+
super()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def response_unavailable?
|
|
24
|
+
body = content_for_scanner
|
|
25
|
+
ambiguous_absence = body.match?(/^No Data Found\s*$/i) && body.match?(RECORD_EVIDENCE)
|
|
26
|
+
|
|
27
|
+
super || body.match?(/^\s*access denied\b/i) ||
|
|
28
|
+
ambiguous_absence
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def response_throttled?
|
|
32
|
+
super || content_for_scanner.match?(/^\s*too many requests\b/i)
|
|
33
|
+
end
|
|
13
34
|
end
|
|
14
35
|
end
|
|
15
36
|
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
|
|
@@ -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
|