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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5351ca8b47d24f7a194be90595c52c18210e24682bf223134f075cc66c223b04
|
|
4
|
+
data.tar.gz: 4befccbf200b2c77f9338c91d2cf122eef11e25883916e070f887db1bdbe1bb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9b65eaac1e8ebcaac940563ca1c9748fa7607f900dc30e5977844323b1799dcd4f365166dc54920a2b2eb8eda094cd68eb42c62920b8a76495ffaab0eb9e45d
|
|
7
|
+
data.tar.gz: f2a5197d3b4e7a78f486a43d76b34a7ebd91da46fe3485f4bc3fcaa117c67b78b0c202a645ae11f4ebd1a7c755d24305d180ddd442b0b67c0acf75cda4431fd7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
This project uses [Semantic Versioning 2.0.0](http://semver.org/).
|
|
4
4
|
|
|
5
|
+
#### Release 0.4.0 (2026-09-29)
|
|
6
|
+
|
|
7
|
+
- ADDED: Expanded and tightened first-class parsing for current official WHOIS
|
|
8
|
+
hosts used by the pinned DomScan high-demand cohort. New parser examples
|
|
9
|
+
include `.biz`, `.click`, `.gift`, `.help`, `.homes`, `.ie`, `.org`, and
|
|
10
|
+
`.dk`; updated parser examples include `.mn`, `.cl`, `.eu`, `.giving`, `.id`,
|
|
11
|
+
`.mx`, `.love`, `.link`, `.icu`, `.lat`, `.to`, and `.open`. The audit tracks
|
|
12
|
+
all 153 suffixes with more than five observed endpoint requests (44,402
|
|
13
|
+
request items), with 94 registered/absence regression pairs and no
|
|
14
|
+
parser-pending rows. Restricted registry cases use minimal synthetic
|
|
15
|
+
fixtures. The audit keeps RDAP-only, retired, unsupported, and restricted
|
|
16
|
+
cases as separate outcomes; see the [full audit](docs/audits/domscan-whois-usage-2026-09-28.md)
|
|
17
|
+
for per-suffix evidence and route limitations.
|
|
18
|
+
- SAFETY: Registration and absence require exact registry evidence. Denied,
|
|
19
|
+
throttled, malformed, incomplete, or contradictory responses do not become
|
|
20
|
+
positive registration or availability results. Unknown and unsupported
|
|
21
|
+
responses remain distinct from authoritative absence.
|
|
22
|
+
- CLARIFIED: `.uk` remains a temporary WHOIS exception based on Nominet's
|
|
23
|
+
guidance that `whois.nic.uk` continues through 2027-02-09, despite IANA's
|
|
24
|
+
RDAP-only listing. The release tests current registered and absence response
|
|
25
|
+
shapes; DomScan's audited 30-day snapshot contained no traditional `.uk`
|
|
26
|
+
WHOIS calls.
|
|
27
|
+
- SAFETY: `.open` registration is restricted by the registry's
|
|
28
|
+
[registration policy](https://web.aexp-static.com/content/dam/amex/us/staticassets/pdf/nic/Registry-Policies-OPEN.pdf).
|
|
29
|
+
A synthetic complete-record test checks the exact `nic.open` registered
|
|
30
|
+
shape. `No Data Found` remains unknown, and this release makes no
|
|
31
|
+
public-availability claim for `.open`.
|
|
32
|
+
- TESTED: The integrated parser suite passes 7,001 examples with 0 failures;
|
|
33
|
+
the pinned-cohort coverage audit passes 3 examples with 0 failures. The
|
|
34
|
+
linked audit records the separate DomScan route checks and their local,
|
|
35
|
+
unreleased status.
|
|
36
|
+
|
|
5
37
|
#### Release 0.3.2 (2026-09-25)
|
|
6
38
|
|
|
7
39
|
- ADDED: Added host-keyed parsers for the current IANA-listed WHOIS hosts for
|
data/README.md
CHANGED
|
@@ -38,7 +38,7 @@ of current WHOIS responses, explicit unknown states, registry-specific
|
|
|
38
38
|
fixtures, and safe handling of malformed, rate-limited, retired, or changed
|
|
39
39
|
server responses.
|
|
40
40
|
|
|
41
|
-
The current source version is `0.
|
|
41
|
+
The current source version is `0.4.0`. Check the [RubyGems project
|
|
42
42
|
page](https://rubygems.org/gems/whois-parser-next) for its publication status
|
|
43
43
|
and the latest published version.
|
|
44
44
|
|
|
@@ -54,10 +54,10 @@ Whois::Parser
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Existing applications can retain `require "whois-parser"` and their
|
|
57
|
-
`Whois::Parser` integrations. To install version 0.
|
|
57
|
+
`Whois::Parser` integrations. To install version 0.4.0, pin it as follows:
|
|
58
58
|
|
|
59
59
|
```ruby
|
|
60
|
-
gem "whois-parser-next", "= 0.
|
|
60
|
+
gem "whois-parser-next", "= 0.4.0", require: "whois-parser"
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
## Requirements
|
data/RELEASING.md
CHANGED
|
@@ -21,8 +21,8 @@ RubyGems API key to the repository or its Actions secrets.
|
|
|
21
21
|
Create and push the annotated release tag only after the checks above pass:
|
|
22
22
|
|
|
23
23
|
```shell
|
|
24
|
-
git tag -a v0.
|
|
25
|
-
git push origin v0.
|
|
24
|
+
git tag -a v0.4.0 -m "whois-parser-next 0.4.0"
|
|
25
|
+
git push origin v0.4.0
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The release workflow repeats the full suite and package smoke test before it
|
data/lib/whois/parser/version.rb
CHANGED
|
@@ -9,19 +9,21 @@ module Whois
|
|
|
9
9
|
|
|
10
10
|
def response_throttled?
|
|
11
11
|
content_for_scanner.match?(
|
|
12
|
-
/^(
|
|
12
|
+
/^(?:[ \t]*(?:%|#)[ \t]*)?(?:whois\s+)?query\s+(?:rate\s+)?limit\s+exceeded\b/i
|
|
13
13
|
) || content_for_scanner.match?(
|
|
14
|
-
/^(
|
|
14
|
+
/^(?:[ \t]*(?:%|#)[ \t]*)?maximum\s+query\s+rate\s+reached\b/i
|
|
15
15
|
) || content_for_scanner.match?(
|
|
16
|
-
/^(
|
|
16
|
+
/^(?:[ \t]*(?:%|#)[ \t]*)?excessive\s+querying\b/i
|
|
17
17
|
) || content_for_scanner.match?(
|
|
18
|
-
|
|
18
|
+
/^[ \t]*(?:%|#)?[ \t]*whois\s+limit\s+exceeded\b/i
|
|
19
|
+
) || content_for_scanner.match?(
|
|
20
|
+
/^[ \t]*(?:%|#)?[ \t]*(?:too\s+many\s+requests|rate\s+limit\s+exceeded)\b/i
|
|
19
21
|
)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def response_unavailable?
|
|
23
25
|
response_incomplete? || content_for_scanner.match?(
|
|
24
|
-
/^(
|
|
26
|
+
/^(?:[ \t]*(?:%|#)[ \t]*)?(?:requests of this client are not permitted|access to the whois service is denied|whois service is unavailable|access denied|permission denied|not authori[sz]ed|unauthori[sz]ed)\b/i
|
|
25
27
|
) || content_for_scanner.match?(
|
|
26
28
|
/\ATLD is not supported\.[ \t]*(?:\n|\z)/i
|
|
27
29
|
)
|
|
@@ -1,11 +1,53 @@
|
|
|
1
1
|
require_relative 'whois.audns.net.au'
|
|
2
|
+
require_relative 'registry_response_safety'
|
|
2
3
|
|
|
3
4
|
module Whois
|
|
4
5
|
class Parsers
|
|
5
6
|
# Parser for the whois.auda.org.au server.
|
|
6
7
|
class WhoisAudaOrgAu < WhoisAudnsNetAu
|
|
8
|
+
include RegistryResponseSafety
|
|
9
|
+
|
|
10
|
+
KNOWN_STATUSES = %w[
|
|
11
|
+
clientDeleteProhibited clientHold clientRenewProhibited clientTransferProhibited
|
|
12
|
+
clientUpdateProhibited inactive ok pendingCreate pendingDelete pendingRenew
|
|
13
|
+
pendingRestore pendingTransfer pendingUpdate serverDeleteProhibited serverHold
|
|
14
|
+
serverRenewProhibited serverTransferProhibited serverUpdateProhibited
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
property_supported :status do
|
|
18
|
+
classify_status
|
|
19
|
+
end
|
|
20
|
+
|
|
7
21
|
property_supported :available? do
|
|
8
|
-
|
|
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
|
+
domain_values = content_for_scanner.scan(/^Domain Name:[ \t]*([^\r\n]+?)[ \t]*$/i).flatten
|
|
34
|
+
domain = domain_values.length == 1 && domain_values.first.match?(/\A(?:[a-z0-9-]+\.)+au\z/i)
|
|
35
|
+
statuses = content_for_scanner.scan(/^Status:[ \t]*([^\r\n]*)$/i).flatten.map(&:strip).reject(&:empty?)
|
|
36
|
+
known_statuses = statuses.map { |value| value.split(/[ \t]/, 2).first }.all? do |value|
|
|
37
|
+
KNOWN_STATUSES.include?(value)
|
|
38
|
+
end
|
|
39
|
+
record_fields = content_for_scanner.scan(/^(?:Registry Domain ID|Registrar Name|Name Server):[ \t]*\S[^\r\n]*$/i)
|
|
40
|
+
has_record_fields = content_for_scanner.match?(/^(?:Domain Name|Registry Domain ID|Registrar Name|Status|Name Server):[ \t]*\S/i)
|
|
41
|
+
available_marker = content_for_scanner.match?(/^Domain not found\.[ \t]*$/i)
|
|
42
|
+
|
|
43
|
+
if available_marker && !has_record_fields
|
|
44
|
+
:available
|
|
45
|
+
elsif domain && !statuses.empty? && known_statuses && record_fields.length >= 2 && !available_marker
|
|
46
|
+
:registered
|
|
47
|
+
else
|
|
48
|
+
:unknown
|
|
49
|
+
end
|
|
50
|
+
end
|
|
9
51
|
end
|
|
10
52
|
end
|
|
11
53
|
end
|
|
@@ -69,6 +69,12 @@ module Whois
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
71
|
def classify_status
|
|
72
|
+
cached_properties_fetch(:classified_status) do
|
|
73
|
+
classify_status_from_content
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def classify_status_from_content
|
|
72
78
|
return :unknown if absence_conflicts_with_record?
|
|
73
79
|
|
|
74
80
|
evidence = []
|
|
@@ -27,19 +27,15 @@ module Whois
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
property_supported :status do
|
|
30
|
-
|
|
31
|
-
:available
|
|
32
|
-
else
|
|
33
|
-
:registered
|
|
34
|
-
end
|
|
30
|
+
classify_status
|
|
35
31
|
end
|
|
36
32
|
|
|
37
33
|
property_supported :available? do
|
|
38
|
-
|
|
34
|
+
classify_status == :available
|
|
39
35
|
end
|
|
40
36
|
|
|
41
37
|
property_supported :registered? do
|
|
42
|
-
|
|
38
|
+
classify_status == :registered
|
|
43
39
|
end
|
|
44
40
|
|
|
45
41
|
|
|
@@ -124,6 +120,36 @@ module Whois
|
|
|
124
120
|
!!(content_for_scanner =~ /Still in grace period/)
|
|
125
121
|
end
|
|
126
122
|
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def classify_status
|
|
126
|
+
cached_properties_fetch(:classified_status) do
|
|
127
|
+
status_values = content_for_scanner.scan(/^Status:[ \t]*([^\r\n]*)$/i).flatten.map(&:strip)
|
|
128
|
+
available = status_values == ['AVAILABLE']
|
|
129
|
+
domain_values = content_for_scanner.scan(/^Domain:[ \t]*([^\r\n]+?)[ \t]*$/i).flatten.map(&:downcase)
|
|
130
|
+
domain = domain_values.length == 1 && domain_values.first.match?(/\A(?:[a-z0-9-]+\.)+eu\z/i)
|
|
131
|
+
echoed_queries = content_for_scanner.scan(/^%?[ \t]*WHOIS[ \t]+((?:[a-z0-9-]+\.)+eu)[ \t]*$/i).flatten.map(&:downcase)
|
|
132
|
+
identity_matches = echoed_queries.empty? || (echoed_queries.length == 1 && echoed_queries.first == domain_values.first)
|
|
133
|
+
registrar = content_for_scanner.match?(/^Registrar:[ \t]*\r?\n[ \t]+Name:[ \t]*\S[^\r\n]*$/i)
|
|
134
|
+
nameservers = content_for_scanner.match?(
|
|
135
|
+
/^Name servers:[ \t]*\r?\n(?:[ \t]+(?:[a-z0-9-]+\.)+[a-z0-9-]+(?:[ \t]+\([^\r\n()]+\))?[ \t]*\r?\n?)+/i
|
|
136
|
+
)
|
|
137
|
+
record = registrar || nameservers
|
|
138
|
+
record_fields = content_for_scanner.match?(/^(?:Registrar|Name servers|Registrant|Onsite(?:\(s\))?|Technical|Created|Creation date|Updated|Last updated|Expires|Expiration date):/i)
|
|
139
|
+
denied_or_throttled = content_for_scanner.match?(
|
|
140
|
+
/\b(?:access\s+denied|permission\s+denied|not\s+authori[sz]ed|unauthori[sz]ed|not\s+allowed|too\s+many\s+requests|rate[- ]?limit(?:ed)?|temporarily\s+unavailable|service\s+unavailable|try\s+again\s+later|still\s+in\s+grace\s+period)\b/i
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if !denied_or_throttled && available && domain && identity_matches && !record_fields
|
|
144
|
+
:available
|
|
145
|
+
elsif !denied_or_throttled && domain && identity_matches && record && (status_values.empty? || status_values == ['REGISTERED'])
|
|
146
|
+
:registered
|
|
147
|
+
else
|
|
148
|
+
:unknown
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
127
153
|
end
|
|
128
154
|
|
|
129
155
|
end
|
|
@@ -9,17 +9,37 @@ module Whois
|
|
|
9
9
|
|
|
10
10
|
# Parser for the current whois.id registry service.
|
|
11
11
|
class WhoisId < BaseIcannCompliant
|
|
12
|
+
NO_RECORD_MARKER = /^The queried object does not exist: (?:DOMAIN NOT FOUND|(?:[a-z0-9-]+\.)+id)\s*$/i
|
|
13
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Registrar|Domain Status|Name Server):[ \t]*\S/i
|
|
12
14
|
|
|
13
|
-
self.scanner = Scanners::BaseIcannCompliant
|
|
15
|
+
self.scanner = Scanners::BaseIcannCompliant, {
|
|
16
|
+
pattern_available: NO_RECORD_MARKER,
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
property_supported :available? do
|
|
16
|
-
|
|
20
|
+
raise ResponseIsThrottled if response_throttled?
|
|
21
|
+
raise ResponseIsUnavailable if response_unavailable?
|
|
22
|
+
|
|
23
|
+
no_record_marker_count == 1
|
|
17
24
|
end
|
|
18
25
|
|
|
19
26
|
property_supported :expires_on do
|
|
20
27
|
node("Registry Expiry Date") { |value| parse_time(value) }
|
|
21
28
|
end
|
|
22
29
|
|
|
30
|
+
def response_unavailable?
|
|
31
|
+
body = content_for_scanner
|
|
32
|
+
marker_count = no_record_marker_count
|
|
33
|
+
|
|
34
|
+
super || (marker_count.positive? && (marker_count != 1 || body.match?(RECORD_EVIDENCE)))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def no_record_marker_count
|
|
40
|
+
content_for_scanner.lines.count { |line| line.match?(NO_RECORD_MARKER) }
|
|
41
|
+
end
|
|
42
|
+
|
|
23
43
|
end
|
|
24
44
|
|
|
25
45
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require_relative 'whois.biz'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# The current IANA delegation points .biz to whois.nic.biz. Its record and
|
|
6
|
+
# absence formats match the existing .biz registry parser.
|
|
7
|
+
class WhoisNicBiz < WhoisBiz
|
|
8
|
+
DOMAIN_LINE = /^Domain Name:[ \t]*(\S+)[ \t]*$/i
|
|
9
|
+
REGISTERED_EVIDENCE = /^(?:Registry Domain ID|Creation Date|Updated Date|Registry Expiry Date|Registrar(?: IANA ID)?):[ \t]*\S/im
|
|
10
|
+
ABSENCE_LINE = /^No Data Found[ \t]*\r?$/i
|
|
11
|
+
RECORD_EVIDENCE = /^(?:Domain Name|Registry Domain ID|Creation Date|Updated Date|Registry Expiry Date|Registrar(?: IANA ID)?|Domain Status|Name Server):[ \t]*\S/im
|
|
12
|
+
|
|
13
|
+
property_supported :status do
|
|
14
|
+
body = content_for_scanner
|
|
15
|
+
absence_count = body.lines.count { |line| line.match?(ABSENCE_LINE) }
|
|
16
|
+
|
|
17
|
+
if absence_count.positive?
|
|
18
|
+
absence_count == 1 && !body.match?(RECORD_EVIDENCE) ? :available : :unknown
|
|
19
|
+
else
|
|
20
|
+
domains = body.scan(DOMAIN_LINE).flatten
|
|
21
|
+
if domains.length == 1 && domains.first.downcase.end_with?('.biz') && body.match?(REGISTERED_EVIDENCE)
|
|
22
|
+
:registered
|
|
23
|
+
else
|
|
24
|
+
:unknown
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
property_supported :available? do
|
|
30
|
+
status == :available
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
property_supported :registered? do
|
|
34
|
+
status == :registered
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -27,20 +27,15 @@ module Whois
|
|
|
27
27
|
class WhoisNicCl < Base
|
|
28
28
|
|
|
29
29
|
property_supported :status do
|
|
30
|
-
|
|
31
|
-
:available
|
|
32
|
-
else
|
|
33
|
-
:registered
|
|
34
|
-
end
|
|
30
|
+
classify_status
|
|
35
31
|
end
|
|
36
32
|
|
|
37
33
|
property_supported :available? do
|
|
38
|
-
|
|
39
|
-
content_for_scanner =~ /^.+:\s+no entries found\.$/i)
|
|
34
|
+
classify_status == :available
|
|
40
35
|
end
|
|
41
36
|
|
|
42
37
|
property_supported :registered? do
|
|
43
|
-
|
|
38
|
+
classify_status == :registered
|
|
44
39
|
end
|
|
45
40
|
|
|
46
41
|
|
|
@@ -70,6 +65,35 @@ module Whois
|
|
|
70
65
|
end
|
|
71
66
|
end
|
|
72
67
|
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# The current response layout in the DomScan corpus does not echo the
|
|
71
|
+
# queried domain. This parser can require a record-shaped response, but
|
|
72
|
+
# cannot independently verify that the returned record matches the query.
|
|
73
|
+
def classify_status
|
|
74
|
+
cached_properties_fetch(:classified_status) do
|
|
75
|
+
available = content_for_scanner.match?(/^(?:[a-z0-9-]+\.)+cl:[ \t]*(?:no existe|no entries found\.)[ \t]*$/i)
|
|
76
|
+
current_record = content_for_scanner.match?(/^Registrar name:[ \t]*\S[^\r\n]*$/i) &&
|
|
77
|
+
content_for_scanner.match?(/^Creation date:[ \t]*\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])(?:[ \t]+\d{2}:\d{2}:\d{2}[ \t]+[A-Z]{2,5})?[ \t]*$/i) &&
|
|
78
|
+
content_for_scanner.match?(/^Expiration date:[ \t]*\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])(?:[ \t]+\d{2}:\d{2}:\d{2}[ \t]+[A-Z]{2,5})?[ \t]*$/i) &&
|
|
79
|
+
content_for_scanner.match?(/^Name server:[ \t]*(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?[ \t]*$/i)
|
|
80
|
+
legacy_record = content_for_scanner.match?(/^ACE:[ \t]*(?:[a-z0-9-]+\.)+cl[ \t]+\(RFC-3490, RFC-3491, RFC-3492\)/i) &&
|
|
81
|
+
content_for_scanner.match?(/^Servidores de nombre \(Domain servers\):/i)
|
|
82
|
+
record = current_record || legacy_record
|
|
83
|
+
record_marker = content_for_scanner.match?(/^(?:Registrant(?: name| organisation| organization| email| address)?|Registrar(?: name| URL)?|Creation date|Expiration date|Name server|ACE):/i) ||
|
|
84
|
+
content_for_scanner.match?(/^Servidores de nombre \(Domain servers\):/i)
|
|
85
|
+
denied = content_for_scanner.match?(/\b(?:not\s+authori[sz]ed|unauthori[sz]ed|access\s+denied|permission\s+denied|too\s+many\s+requests|rate[- ]?limited?)\b|\bno\s+autorizad[oa]\b|\bacceso\s+denegado\b/i)
|
|
86
|
+
|
|
87
|
+
if !denied && available && !record_marker
|
|
88
|
+
:available
|
|
89
|
+
elsif !denied && record && !available
|
|
90
|
+
:registered
|
|
91
|
+
else
|
|
92
|
+
:unknown
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
73
97
|
end
|
|
74
98
|
|
|
75
99
|
end
|
|
@@ -34,6 +34,12 @@ module Whois
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
36
|
def classify_status
|
|
37
|
+
cached_properties_fetch(:classified_status) do
|
|
38
|
+
classify_status_from_content
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def classify_status_from_content
|
|
37
43
|
if registered_evidence?
|
|
38
44
|
:registered
|
|
39
45
|
elsif available_evidence?
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require_relative 'base_unsupported_registry'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# Identity Digital's shared endpoint explicitly reports that .GIFTS is
|
|
6
|
+
# unsupported over WHOIS. The TLD's authoritative registry data is RDAP.
|
|
7
|
+
class WhoisNicGifts < BaseUnsupportedRegistry
|
|
8
|
+
def response_unavailable?
|
|
9
|
+
content_for_scanner.match?(/\A[ \t]*TLD is not supported\.[ \t]*(?:\r?\n|\z)/i)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require_relative 'base_icann_compliant'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# Parser for the PIR WHOIS service serving .GIVES.
|
|
6
|
+
class WhoisNicGives < BaseIcannCompliant
|
|
7
|
+
self.scanner = Scanners::BaseIcannCompliant, {
|
|
8
|
+
pattern_available: /^[ \t]*Domain not found\.[ \t\r]*$/i,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
property_supported :status do
|
|
12
|
+
classify_status
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
property_supported :available? do
|
|
16
|
+
classify_status == :available
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
property_supported :registered? do
|
|
20
|
+
classify_status == :registered
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def classify_status
|
|
26
|
+
cached_properties_fetch(:classified_status) do
|
|
27
|
+
response = content_for_scanner
|
|
28
|
+
missing = response.match?(/\A[ \t]*Domain not found\.[ \t]*(?:\r?\n|\z)/i)
|
|
29
|
+
denial = response.match?(/^[ \t]*(?:[%#][ \t]*)?(?:access denied|permission denied|request denied|forbidden)\b/i)
|
|
30
|
+
domains = response.scan(/^[ \t]*Domain Name:[ \t]*((?:[a-z0-9-]+\.)+gives)[ \t\r]*$/i).flatten.uniq
|
|
31
|
+
has_registrar = response.match?(/^[ \t]*Registrar:[ \t]*\S/i)
|
|
32
|
+
has_record_detail = response.match?(/^[ \t]*(?:Creation Date|Updated Date|Registry Expiry Date|Name Server):[ \t]*\S/i)
|
|
33
|
+
has_record = domains.one? && has_registrar && has_record_detail
|
|
34
|
+
|
|
35
|
+
if missing && domains.empty? && !denial && !response_throttled?
|
|
36
|
+
:available
|
|
37
|
+
elsif has_record && !missing && !denial && !response_throttled?
|
|
38
|
+
:registered
|
|
39
|
+
else
|
|
40
|
+
:unknown
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
require_relative 'base_icann_compliant'
|
|
2
|
+
require_relative 'registry_response_safety'
|
|
2
3
|
|
|
3
4
|
module Whois
|
|
4
5
|
class Parsers
|
|
5
6
|
class WhoisNicGiving < BaseIcannCompliant
|
|
7
|
+
include RegistryResponseSafety
|
|
8
|
+
|
|
9
|
+
DOMAIN_PATTERN = /\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+giving\z/i
|
|
10
|
+
NO_RECORD_MARKER = /^[ \t]*Domain not found\.[ \t]*$/i
|
|
11
|
+
|
|
12
|
+
self.scanner = Scanners::BaseIcannCompliant, {
|
|
13
|
+
pattern_available: NO_RECORD_MARKER,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
property_supported :status do
|
|
17
|
+
classify_status
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
property_supported :available? do
|
|
21
|
+
classify_status == :available
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
property_supported :registered? do
|
|
25
|
+
classify_status == :registered
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def classify_status
|
|
31
|
+
cached_properties_fetch(:classified_status) do
|
|
32
|
+
response = content_for_scanner
|
|
33
|
+
missing = response.lines.one? { |line| line.match?(NO_RECORD_MARKER) }
|
|
34
|
+
denial = response.match?(/^[ \t]*(?:[%#][ \t]*)?(?:access denied|permission denied|request denied|forbidden)\b/i)
|
|
35
|
+
domain_lines = response.scan(/^[ \t]*Domain Name:[ \t]*(.*?)[ \t\r]*$/i).flatten
|
|
36
|
+
domains = domain_lines.grep(DOMAIN_PATTERN)
|
|
37
|
+
has_registrar = response.match?(/^[ \t]*Registrar:[ \t]*\S/i)
|
|
38
|
+
has_record_detail = response.match?(/^[ \t]*(?:Creation Date|Updated Date|Registry Expiry Date|Name Server):[ \t]*\S/i)
|
|
39
|
+
has_record = domain_lines.one? && domains.one? && has_registrar && has_record_detail
|
|
40
|
+
|
|
41
|
+
if missing && domain_lines.empty? && !denial && !response_unavailable? && !response_throttled?
|
|
42
|
+
:available
|
|
43
|
+
elsif has_record && !missing && !denial && !response_unavailable? && !response_throttled?
|
|
44
|
+
:registered
|
|
45
|
+
else
|
|
46
|
+
:unknown
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
6
50
|
end
|
|
7
51
|
end
|
|
8
52
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require_relative 'base_identity_digital'
|
|
2
|
+
|
|
3
|
+
module Whois
|
|
4
|
+
class Parsers
|
|
5
|
+
# CentralNic's .HELP registry endpoint uses its exact no-object marker.
|
|
6
|
+
class WhoisNicHelp < 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
|
|
@@ -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:
|
|
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
|
-
|
|
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
|