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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -6
  3. data/README.md +20 -15
  4. data/RELEASING.md +2 -2
  5. data/lib/whois/parser/version.rb +1 -1
  6. data/lib/whois/parsers/registry_response_safety.rb +7 -5
  7. data/lib/whois/parsers/whois.auda.org.au.rb +43 -1
  8. data/lib/whois/parsers/whois.cctld.uz.rb +6 -0
  9. data/lib/whois/parsers/whois.eu.rb +33 -7
  10. data/lib/whois/parsers/whois.id.rb +22 -2
  11. data/lib/whois/parsers/whois.nic.africa.rb +23 -0
  12. data/lib/whois/parsers/whois.nic.biz.rb +38 -0
  13. data/lib/whois/parsers/whois.nic.cl.rb +32 -8
  14. data/lib/whois/parsers/whois.nic.foundation.rb +4 -0
  15. data/lib/whois/parsers/whois.nic.ge.rb +6 -0
  16. data/lib/whois/parsers/whois.nic.gifts.rb +13 -0
  17. data/lib/whois/parsers/whois.nic.gives.rb +46 -0
  18. data/lib/whois/parsers/whois.nic.giving.rb +44 -0
  19. data/lib/whois/parsers/whois.nic.help.rb +12 -0
  20. data/lib/whois/parsers/whois.nic.homes.rb +12 -0
  21. data/lib/whois/parsers/whois.nic.icu.rb +11 -1
  22. data/lib/whois/parsers/whois.nic.lat.rb +15 -1
  23. data/lib/whois/parsers/whois.nic.love.rb +19 -0
  24. data/lib/whois/parsers/whois.nic.mn.rb +51 -0
  25. data/lib/whois/parsers/whois.nic.mx.rb +47 -7
  26. data/lib/whois/parsers/whois.nic.net.sa.rb +6 -0
  27. data/lib/whois/parsers/whois.nic.news.rb +2 -0
  28. data/lib/whois/parsers/whois.nic.one.rb +7 -1
  29. data/lib/whois/parsers/whois.nic.online.rb +25 -1
  30. data/lib/whois/parsers/whois.nic.open.rb +95 -0
  31. data/lib/whois/parsers/whois.nic.quest.rb +17 -0
  32. data/lib/whois/parsers/whois.nic.sbs.rb +28 -0
  33. data/lib/whois/parsers/whois.nic.store.rb +28 -0
  34. data/lib/whois/parsers/whois.nic.top.rb +25 -3
  35. data/lib/whois/parsers/whois.nic.tv.rb +21 -0
  36. data/lib/whois/parsers/whois.nic.uk.rb +84 -23
  37. data/lib/whois/parsers/whois.nic.vip.rb +21 -0
  38. data/lib/whois/parsers/whois.nic.wiki.rb +21 -0
  39. data/lib/whois/parsers/whois.nic.win.rb +27 -0
  40. data/lib/whois/parsers/whois.publicinterestregistry.org.rb +52 -0
  41. data/lib/whois/parsers/whois.punktum.dk.rb +53 -0
  42. data/lib/whois/parsers/whois.registry.click.rb +9 -0
  43. data/lib/whois/parsers/whois.registry.gift.rb +10 -0
  44. data/lib/whois/parsers/whois.sr.rb +44 -0
  45. data/lib/whois/parsers/whois.tcinet.ru.rb +83 -8
  46. data/lib/whois/parsers/whois.tld.mu.rb +20 -0
  47. data/lib/whois/parsers/whois.tonicregistry.to.rb +55 -1
  48. data/lib/whois/parsers/whois.uniregistry.net.rb +15 -2
  49. data/lib/whois/parsers/whois.weare.ie.rb +12 -0
  50. metadata +16 -1
@@ -0,0 +1,12 @@
1
+ require_relative 'base_identity_digital'
2
+
3
+ module Whois
4
+ class Parsers
5
+ # CentralNic's .HOMES registry endpoint uses its exact no-object marker.
6
+ class WhoisNicHomes < BaseIdentityDigital
7
+ self.scanner = Scanners::BaseIcannCompliant, {
8
+ pattern_available: /^The queried object does not exist: DOMAIN NOT FOUND\s*$/i,
9
+ }
10
+ end
11
+ end
12
+ end
@@ -4,9 +4,19 @@ module Whois
4
4
  class Parsers
5
5
  # Parser for the Identity Digital endpoint serving .ICU.
6
6
  class WhoisNicIcu < BaseIcannCompliant
7
+ NO_RECORD_MARKER = /^The queried object does not exist: DOMAIN NOT FOUND\s*$/i
8
+ RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status|Name Server):[ \t]*\S/i
9
+
7
10
  self.scanner = Scanners::BaseIcannCompliant, {
8
- pattern_available: /^The queried object does not exist: DOMAIN NOT FOUND$/i,
11
+ pattern_available: NO_RECORD_MARKER,
9
12
  }
13
+
14
+ def response_unavailable?
15
+ body = content_for_scanner
16
+ marker_count = body.lines.count { |line| line.match?(NO_RECORD_MARKER) }
17
+
18
+ super || (marker_count.positive? && (marker_count != 1 || body.match?(RECORD_EVIDENCE)))
19
+ end
10
20
  end
11
21
  end
12
22
  end
@@ -2,7 +2,21 @@ require_relative 'base_identity_digital'
2
2
  module Whois
3
3
  class Parsers
4
4
  class WhoisNicLat < BaseIdentityDigital
5
- self.scanner = Scanners::BaseIcannCompliant, { pattern_available: /^The queried object does not exist: DOMAIN NOT FOUND\s*$/i }
5
+ NO_RECORD_MARKER = /^The queried object does not exist: DOMAIN NOT FOUND\s*$/i
6
+ RECORD_EVIDENCE = /^[ \t]*(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status|Name Server):[ \t]*\S/i
7
+
8
+ self.scanner = Scanners::BaseIcannCompliant, { pattern_available: NO_RECORD_MARKER }
9
+
10
+ property_supported :available? do
11
+ content_for_scanner.lines.one? { |line| line.match?(NO_RECORD_MARKER) }
12
+ end
13
+
14
+ def response_unavailable?
15
+ body = content_for_scanner
16
+ marker_count = body.lines.count { |line| line.match?(NO_RECORD_MARKER) }
17
+
18
+ super || (marker_count.positive? && (marker_count != 1 || body.match?(RECORD_EVIDENCE)))
19
+ end
6
20
  end
7
21
  end
8
22
  end
@@ -3,6 +3,25 @@ require_relative 'base_top1000_icann'
3
3
  module Whois
4
4
  class Parsers
5
5
  class WhoisNicLove < BaseTop1000Icann
6
+ AVAILABLE_DOMAIN_LINE = /^>>> Domain (?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+love is available for registration[ \t]*$/i
7
+ RECORD_EVIDENCE = /^[ \t]*(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Sponsoring Registrar|Domain Status|Name Server):[ \t]*\S/i
8
+
9
+ property_supported :available? do
10
+ available_marker_count == 1
11
+ end
12
+
13
+ def response_unavailable?
14
+ body = content_for_scanner
15
+ marker_count = available_marker_count
16
+
17
+ super || (marker_count.positive? && (marker_count != 1 || body.match?(RECORD_EVIDENCE)))
18
+ end
19
+
20
+ private
21
+
22
+ def available_marker_count
23
+ content_for_scanner.lines.count { |line| line.match?(AVAILABLE_DOMAIN_LINE) }
24
+ end
6
25
  end
7
26
  end
8
27
  end
@@ -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
- if available?
31
- :available
32
- else
33
- :registered
34
- end
36
+ classify_status
35
37
  end
36
38
 
37
39
  property_supported :available? do
38
- !!(content_for_scanner =~ /Object_Not_Found/)
40
+ classify_status == :available
39
41
  end
40
42
 
41
43
  property_supported :registered? do
42
- !available?
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 no_data_found?
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
- super() || content_for_scanner.match?(
10
- /^The queried object does not exist:\s+(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)*[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.top\s*$/i
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