whois-parser-next 0.2.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54c1fc5d5bb3d8adda3cbe6b644a0f2982195267f46654c6025090b16b7f6643
4
- data.tar.gz: 8b8e0ae4f21a313e6fa65c1d4868bf28326bbc57df15e3bbfa8256c4a38f3217
3
+ metadata.gz: 1ac79159293403464f7e386fd283634642e6eda102d7e486e105b399089e34cf
4
+ data.tar.gz: 0fb55415df4c88415bac870059b30f25fbf5f488526241b1358ce15257b8bcb9
5
5
  SHA512:
6
- metadata.gz: c1be7e51963fefa6a8571faa4577bbdab9ca95009bd1ccf8413b65a7816a7c53539d5f05d22f456154124ec12e8d79b1b86df9f1cc1633beb4d7532dbaaf273f
7
- data.tar.gz: 4d07890e7c2e5bbf59a19f076820f276256453dd1bcdfb700df51d2a1c1581395125c12ab8c2a8d2c90864c960ca99bd9590932dca4c2fe71c04a85cda225203
6
+ metadata.gz: 58999ed26705e0ee3ff4e78428b70ee0f20f45e184693debdee4a899617274662ba2f1074f8ca0bb6109ac7c6c702a81ed88ee32138a29e68dd7a1391cd13ffa
7
+ data.tar.gz: c537956618e781da069518bc6428e39f7953368f001c833582b349f19c3d4d6a7cc7a73e1e85f93d3973c978083497c2fc424c616ad660dcd4ffaf5d8669be92
data/CHANGELOG.md CHANGED
@@ -2,6 +2,62 @@
2
2
 
3
3
  This project uses [Semantic Versioning 2.0.0](http://semver.org/).
4
4
 
5
+ #### Release 0.3.1 (2026-09-25)
6
+
7
+ - ADDED: Added paired registered and exact authoritative-absence parsing for
8
+ `.ma` and `.hn`. Empty and denied replies remain unavailable, rate limits
9
+ remain throttled, and conflicting or incomplete replies remain unknown.
10
+ - FIXED: Corrected `.pk` parsing for PKNIC's observed indented response
11
+ fields. Registration and availability still require explicit markers;
12
+ incomplete or conflicting replies remain unknown, and denied or throttled
13
+ replies remain non-positive.
14
+ - EVIDENCE: [Current `.ma` and `.hn` responses](docs/audits/top-126-150-tlds-2026-09-19.md)
15
+ and [production-shaped `.pk` responses](docs/audits/top1000-es-pk-shop-dev-app-2026-09-25.md).
16
+
17
+
18
+ #### Release 0.3.0 (2026-09-25)
19
+
20
+ - ADDED: Added evidence-backed `.top` registration and exact authoritative
21
+ absence parsing. `.us` now recognizes registration evidence while keeping
22
+ the registry's non-authoritative `No Data Found` response unknown.
23
+ - ADDED: Added conservative `.pk` parsing for exact PKNIC registered and
24
+ explicitly available responses. The standard `whois` 6.0.3 mapping selects
25
+ a legacy web adapter, and `Whois::Client.new(host: ...)` cannot override
26
+ that adapter. DomScan needs a fixed-host port-43 route through its managed
27
+ relay before `.pk` counts as effective coverage.
28
+ - ADDED: Tightened `.uz` and `.sa` parsing against current registered and
29
+ exact authoritative-absence responses. `.uz` reserved names remain
30
+ `:reserved`, not registered or available. Added a conservative `.ge` parser
31
+ for exact registered and absence responses from IANA's `whois.nic.ge` host;
32
+ `whois` 6.0.3 defaults `.ge` to `whois.registration.ge`, so a fixed-host
33
+ route is still required before counting it as effective coverage.
34
+ - SAFETY: Kept blocked, empty, and ambiguous `.ch` responses unknown, and
35
+ added unavailable-only handling for explicit unsupported responses from
36
+ `.digital`, `.email`, `.live`, `.media`, `.network`, and `.services`.
37
+ - DOCS: Added a scorecard for 1,000 observed TLD strings from the ICANN DNS
38
+ Magnitude snapshot dated 2026-09-12. Its strict count of 175 paired rows is
39
+ pinned to parser baseline `f2c822c` and excludes the Top 100, whose summary
40
+ labels do not expose row-level evidence pairs. The scorecard does not claim
41
+ 1,000 effective WHOIS integrations or a single effective-coverage rate.
42
+ Later `.uz`/`.sa` evidence and `.pk`/`.ge` parsers are follow-ups, not
43
+ additions to that pinned count.
44
+ - SOURCES: The audit notes link to the [ICANN DNS Magnitude
45
+ snapshot](https://magnitude.research.icann.org/historic/20260912.full.html),
46
+ [IANA `.top`](https://www.iana.org/domains/root/db/top.html),
47
+ [IANA `.us`](https://www.iana.org/domains/root/db/us.html),
48
+ [IANA `.ch`](https://www.iana.org/domains/root/db/ch.html),
49
+ [IANA `.pk`](https://www.iana.org/domains/root/db/pk.html), and the
50
+ [official PKNIC site](https://www.pknic.net.pk/). The
51
+ [rank 104-105 report](docs/audits/top-101-125-tlds-2026-09-19.md) cites
52
+ [IANA `.uz`](https://www.iana.org/domains/root/db/uz.html),
53
+ [UZINFOCOM](https://www.cctld.uz/),
54
+ [IANA `.sa`](https://www.iana.org/domains/root/db/sa.html), and
55
+ [IANA `.ge`](https://www.iana.org/domains/root/db/ge.html).
56
+ - EVIDENCE: [Top-1,000 scorecard](docs/audits/top-1000-effective-coverage-scorecard-2026-09-25.md),
57
+ [current `.top`/`.us` observations](docs/audits/top-us-current-2026-09-25.md),
58
+ [`.pk` routing and registry evidence](docs/audits/top1000-es-pk-shop-dev-app-2026-09-25.md),
59
+ and [rank 101-125 follow-up evidence](docs/audits/top-101-125-tlds-2026-09-19.md).
60
+
5
61
 
6
62
  #### Release 0.2.0 (2026-09-25)
7
63
 
data/README.md CHANGED
@@ -38,7 +38,9 @@ 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 latest published RubyGems release is `0.2.0`.
41
+ The current source version is `0.3.1`. Check the [RubyGems project
42
+ page](https://rubygems.org/gems/whois-parser-next) for its publication status
43
+ and the latest published version.
42
44
 
43
45
  ## Compatibility
44
46
 
@@ -52,12 +54,10 @@ Whois::Parser
52
54
  ```
53
55
 
54
56
  Existing applications can retain `require "whois-parser"` and their
55
- `Whois::Parser` integrations while changing the dependency source:
57
+ `Whois::Parser` integrations. To install version 0.3.1, pin it as follows:
56
58
 
57
59
  ```ruby
58
- gem "whois-parser-next",
59
- github: "estevecastells/whois-parser-next",
60
- require: "whois-parser"
60
+ gem "whois-parser-next", "= 0.3.1", require: "whois-parser"
61
61
  ```
62
62
 
63
63
  ## Requirements
@@ -82,12 +82,22 @@ bundle exec rspec
82
82
 
83
83
  ## Coverage evidence
84
84
 
85
- The 2026-09-19 integrated audit covers 1,000 observed TLD strings from the
86
- ICANN DNS Magnitude snapshot dated 2026-09-12. These strings are not 1,000
87
- guaranteed working WHOIS registries or domain-availability results. The audit
88
- keeps verified parser evidence, explicit unavailable or unsupported
85
+ The [2026-09-25 scorecard](docs/audits/top-1000-effective-coverage-scorecard-2026-09-25.md)
86
+ summarizes evidence for 1,000 observed TLD strings from the
87
+ [ICANN DNS Magnitude snapshot dated 2026-09-12](https://magnitude.research.icann.org/historic/20260912.full.html).
88
+ These strings are not 1,000 guaranteed working WHOIS registries or
89
+ domain-availability results. Its strict paired count is pinned to baseline
90
+ `f2c822c`, excludes the Top 100 whose summary labels do not expose row-level
91
+ evidence pairs, and does not claim an effective-coverage percentage. The
92
+ scorecard keeps verified parser evidence, explicit unavailable or unsupported
89
93
  responses, unknown or unresolved endpoints, and classification-only rows
90
- distinct.
94
+ distinct. Later `.uz`/`.sa` paired evidence and `.pk`/`.ge` parsers are
95
+ follow-ups, not changes to the pinned scorecard counts. The `.pk` parser does
96
+ not make the default `whois` 6.0.3 Web adapter use port 43; `Whois::Client`
97
+ host configuration does not override that adapter. The `.ge` parser targets
98
+ IANA's `whois.nic.ge`, while the default `whois` route is
99
+ `whois.registration.ge`. Both require explicit fixed-host routing before an
100
+ application can count them as effective coverage.
91
101
 
92
102
  Start with the [source verification report](docs/audits/top-1000-source-verification-2026-09-19.md),
93
103
  the [Top 20 review](docs/audits/top-20-tlds-2026-09-19.md), and the [Top 100
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.2.0 -m "whois-parser-next 0.2.0"
25
- git push origin v0.2.0
24
+ git tag -a v0.3.0 -m "whois-parser-next 0.3.0"
25
+ git push origin v0.3.0
26
26
  ```
27
27
 
28
28
  The release workflow repeats the full suite and package smoke test before it
data/ROADMAP.md CHANGED
@@ -6,24 +6,29 @@ feature calendar.
6
6
 
7
7
  ## Current release
8
8
 
9
- - Establish a safe, maintained successor with Ruby 3.2 through Ruby 4 support.
10
- - Audit the first 300 rows of a dated, reproducible TLD-usage snapshot.
11
- - Replace unsafe fallback classifications with explicit unknown or unavailable
12
- outcomes when a response lacks authoritative evidence.
13
- - Keep the gem compatible with `require "whois-parser"` and `Whois::Parser`.
14
- - Publish through short-lived RubyGems trusted-publishing credentials.
9
+ - Published `whois-parser-next` 0.2.0 to RubyGems, retaining the established
10
+ `require "whois-parser"` and `Whois::Parser` compatibility.
11
+ - Completed the audit of 1,000 observed TLD strings from the ICANN DNS Magnitude
12
+ snapshot dated 2026-09-12. These rows are ranked TLD strings, not 1,000
13
+ confirmed working WHOIS registries or domain-availability results.
14
+ - Kept verified parser evidence, unavailable or unsupported responses,
15
+ unresolved endpoints, and classification-only rows distinct.
15
16
 
16
17
  ## Next priorities
17
18
 
18
- 1. Continue evidence-backed TLD coverage in reproducible, non-overlapping
19
- batches.
20
- 2. Add regression fixtures when registries change format, server, policy, or
21
- availability wording.
22
- 3. Reduce avoidable runtime dependencies without changing public behavior.
23
- 4. Improve contributor tooling for fixture privacy, server-mapping drift, and
24
- ambiguous-response safety.
25
- 5. Document parser confidence and provenance without pretending that a parsed
26
- WHOIS response is an availability guarantee.
19
+ 1. Refresh the existing 1,000-row cohort only against a new, dated source
20
+ snapshot, keeping each snapshot's ranks and labels separate.
21
+ 2. Close parser evidence gaps for mapped official WHOIS hosts with bounded
22
+ probes and sanitized fixtures for exact response behavior. DNS failures,
23
+ timeouts, unsupported replies, and ambiguous responses remain unresolved or
24
+ unavailable.
25
+ 3. Add or change parser behavior only when current response evidence is backed
26
+ by a regression spec; do not infer one host's behavior for unmapped or
27
+ unverified TLDs.
28
+ 4. Scope any expansion beyond rank 1,000 from a separately documented dataset
29
+ before treating it as parser-coverage work.
30
+ 5. Reduce avoidable runtime dependencies and improve contributor tooling for
31
+ fixture privacy, server-mapping drift, and response-safety checks.
27
32
 
28
33
  ## Performance and Rust
29
34
 
data/SECURITY.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
- Until the first release, only the current main branch is supported.
5
+ The current main branch and the latest published release are supported.
6
6
 
7
7
  | Version | Supported |
8
8
  |---|---|
9
9
  | main | Yes |
10
- | Released versions | No released versions yet |
10
+ | 0.2.0 (latest release) | Yes |
11
11
 
12
12
  ## Reporting a vulnerability
13
13
 
@@ -1,6 +1,6 @@
1
1
  module Whois
2
2
  class Parser
3
3
  # The current library version.
4
- VERSION = "0.2.0".freeze
4
+ VERSION = "0.3.1".freeze
5
5
  end
6
6
  end
@@ -27,26 +27,15 @@ module Whois
27
27
  include RegistryResponseSafety
28
28
 
29
29
  property_supported :status do
30
- if available?
31
- :available
32
- elsif content_for_scanner =~ /^Status: (.+?)\n/
33
- case ::Regexp.last_match(1).downcase
34
- when "active" then :registered
35
- when "reserved" then :reserved
36
- else
37
- Whois::Parser.bug!(ParserError, "Unknown status `#{::Regexp.last_match(1)}'.")
38
- end
39
- else
40
- :unknown
41
- end
30
+ classify_status
42
31
  end
43
32
 
44
33
  property_supported :available? do
45
- !!(content_for_scanner =~ /not found in database/)
34
+ classify_status == :available
46
35
  end
47
36
 
48
37
  property_supported :registered? do
49
- [:registered, :reserved].include?(status)
38
+ classify_status == :registered
50
39
  end
51
40
 
52
41
 
@@ -77,6 +66,53 @@ module Whois
77
66
  end
78
67
  end
79
68
 
69
+ private
70
+
71
+ def classify_status
72
+ return :unknown if absence_conflicts_with_record?
73
+
74
+ evidence = []
75
+ evidence << :registered if registered_evidence?
76
+ evidence << :reserved if reserved_evidence?
77
+ evidence << :available if available_evidence?
78
+ evidence.one? ? evidence.first : :unknown
79
+ end
80
+
81
+ def absence_conflicts_with_record?
82
+ return false unless absence_domain_names.any?
83
+
84
+ domain_names.any? || status_values.any? || content_for_scanner.match?(/^[ \t]*Domain Name:/i)
85
+ end
86
+
87
+ def registered_evidence?
88
+ domain_names.one? && status_values == ["active"]
89
+ end
90
+
91
+ def reserved_evidence?
92
+ domain_names.one? && status_values == ["reserved"]
93
+ end
94
+
95
+ def available_evidence?
96
+ absence_domain_names.one? && status_values.empty? &&
97
+ !content_for_scanner.match?(/^[ \t]*Domain Name:/i)
98
+ end
99
+
100
+ def domain_names
101
+ content_for_scanner.scan(
102
+ /^[ \t]*Domain Name:[ \t]*((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+uz)[ \t]*$/i
103
+ ).flatten.map(&:downcase).uniq
104
+ end
105
+
106
+ def status_values
107
+ content_for_scanner.scan(/^[ \t]*Status:[ \t]*([a-z]+)[ \t]*$/i).flatten.map(&:downcase).uniq
108
+ end
109
+
110
+ def absence_domain_names
111
+ content_for_scanner.scan(
112
+ /^[ \t]*Sorry, but domain:[ \t]*"((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+uz)", not found in database[ \t]*$/i
113
+ ).flatten.map(&:downcase).uniq
114
+ end
115
+
80
116
  end
81
117
 
82
118
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
 
10
10
  require_relative 'base'
11
+ require_relative 'registry_response_safety'
11
12
 
12
13
  module Whois
13
14
  class Parsers
@@ -24,12 +25,15 @@ module Whois
24
25
  # and examples.
25
26
  #
26
27
  class WhoisNicCh < Base
28
+ include RegistryResponseSafety
27
29
 
28
30
  property_supported :status do
29
31
  if available?
30
32
  :available
31
- else
33
+ elsif content_for_scanner.match?(/^Domain name:\s*\S+/i)
32
34
  :registered
35
+ else
36
+ Whois::Parser.bug!(ParserError, "Unable to parse .ch response status.")
33
37
  end
34
38
  end
35
39
 
@@ -38,7 +42,7 @@ module Whois
38
42
  end
39
43
 
40
44
  property_supported :registered? do
41
- !available?
45
+ status == :registered
42
46
  end
43
47
 
44
48
 
@@ -0,0 +1,8 @@
1
+ require_relative 'base_unsupported_registry'
2
+
3
+ module Whois
4
+ class Parsers
5
+ class WhoisNicDigital < BaseUnsupportedRegistry
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'base_unsupported_registry'
2
+
3
+ module Whois
4
+ class Parsers
5
+ class WhoisNicEmail < BaseUnsupportedRegistry
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,74 @@
1
+ #--
2
+ # Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client and parser.
5
+ #
6
+ # Copyright (c) 2009-2022 Simone Carletti <weppos@weppos.net>
7
+ #++
8
+
9
+
10
+ require_relative 'base'
11
+ require_relative 'registry_response_safety'
12
+
13
+
14
+ module Whois
15
+ class Parsers
16
+
17
+ # Parser for the whois.nic.ge server.
18
+ #
19
+ class WhoisNicGe < Base
20
+ include RegistryResponseSafety
21
+
22
+ property_supported :status do
23
+ classify_status
24
+ end
25
+
26
+ property_supported :available? do
27
+ classify_status == :available
28
+ end
29
+
30
+ property_supported :registered? do
31
+ classify_status == :registered
32
+ end
33
+
34
+ private
35
+
36
+ def classify_status
37
+ if registered_evidence?
38
+ :registered
39
+ elsif available_evidence?
40
+ :available
41
+ else
42
+ :unknown
43
+ end
44
+ end
45
+
46
+ def registered_evidence?
47
+ registered_domain_names.one? && absence_domain_names.empty? && status_values == ['ok']
48
+ end
49
+
50
+ def available_evidence?
51
+ absence_domain_names.one? && registered_domain_names.empty? && status_values.empty? &&
52
+ !content_for_scanner.match?(/^[ \t]*Domain Name:/i)
53
+ end
54
+
55
+ def registered_domain_names
56
+ content_for_scanner.scan(
57
+ /^[ \t]*Domain Name:[ \t]*((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ge)[ \t]*$/i
58
+ ).flatten.map(&:downcase).uniq
59
+ end
60
+
61
+ def status_values
62
+ content_for_scanner.scan(/^[ \t]*Domain Status:[ \t]*(.+?)[ \t]*$/i).flatten.map(&:downcase).uniq
63
+ end
64
+
65
+ def absence_domain_names
66
+ content_for_scanner.scan(
67
+ /^[ \t]*No match for "((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ge)"\.[ \t]*$/i
68
+ ).flatten.map(&:downcase).uniq
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -24,14 +24,14 @@ module Whois
24
24
 
25
25
  property_supported :status do
26
26
  if no_object_found?
27
- :available
27
+ registration_fields_present? ? :unknown : :available
28
28
  else
29
29
  super()
30
30
  end
31
31
  end
32
32
 
33
33
  property_supported :available? do
34
- no_object_found? || super()
34
+ status == :available
35
35
  end
36
36
 
37
37
  property_supported :registered? do
@@ -43,6 +43,12 @@ module Whois
43
43
  def no_object_found?
44
44
  content_for_scanner.match?(/^The queried object does not exist:\s*No Object Found\s*$/i)
45
45
  end
46
+
47
+ def registration_fields_present?
48
+ content_for_scanner.match?(
49
+ /^[ \t]*(?:Domain Status|Domain ID|Creation Date|Updated Date|Registry Expiry Date|Name Server|(?:Sponsoring )?Registrar|Registrant|Admin|Billing|Tech):/i
50
+ )
51
+ end
46
52
  end
47
53
 
48
54
  end
@@ -0,0 +1,8 @@
1
+ require_relative 'base_unsupported_registry'
2
+
3
+ module Whois
4
+ class Parsers
5
+ class WhoisNicLive < BaseUnsupportedRegistry
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'base_unsupported_registry'
2
+
3
+ module Whois
4
+ class Parsers
5
+ class WhoisNicMedia < BaseUnsupportedRegistry
6
+ end
7
+ end
8
+ end
@@ -27,21 +27,15 @@ module Whois
27
27
  include RegistryResponseSafety
28
28
 
29
29
  property_supported :status do
30
- if available?
31
- :available
32
- elsif content_for_scanner.match?(/^Domain Name:\s*\S+/i)
33
- :registered
34
- else
35
- :unknown
36
- end
30
+ classify_status
37
31
  end
38
32
 
39
33
  property_supported :available? do
40
- !!(content_for_scanner =~ /^No Match for/)
34
+ classify_status == :available
41
35
  end
42
36
 
43
37
  property_supported :registered? do
44
- status == :registered
38
+ classify_status == :registered
45
39
  end
46
40
 
47
41
 
@@ -68,6 +62,31 @@ module Whois
68
62
  end
69
63
  end
70
64
 
65
+ private
66
+
67
+ def classify_status
68
+ registered = registered_domain_names
69
+ absent = absence_domain_names
70
+
71
+ if registered.one? && absent.empty?
72
+ :registered
73
+ elsif absent.one? && registered.empty?
74
+ :available
75
+ else
76
+ :unknown
77
+ end
78
+ end
79
+
80
+ def registered_domain_names
81
+ content_for_scanner.scan(/^[ \t]*Domain Name:[ \t]*([^\s]+\.sa)[ \t]*$/i).flatten.map(&:downcase).uniq
82
+ end
83
+
84
+ def absence_domain_names
85
+ content_for_scanner.scan(
86
+ /^[ \t]*No Match for (?:domain:[ \t]*)?([^\s]+\.sa)[ \t]*$/i
87
+ ).flatten.map(&:downcase).uniq
88
+ end
89
+
71
90
  end
72
91
 
73
92
  end
@@ -0,0 +1,8 @@
1
+ require_relative 'base_unsupported_registry'
2
+
3
+ module Whois
4
+ class Parsers
5
+ class WhoisNicNetwork < BaseUnsupportedRegistry
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'base_unsupported_registry'
2
+
3
+ module Whois
4
+ class Parsers
5
+ class WhoisNicServices < BaseUnsupportedRegistry
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'base_top1000_icann'
2
+
3
+ module Whois
4
+ class Parsers
5
+ # The .top registry uses the shared ICANN record shape and returns the full
6
+ # queried .top name in its exact no-record response.
7
+ class WhoisNicTop < BaseTop1000Icann
8
+ 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
+ )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -19,6 +19,36 @@ module Whois
19
19
  # The Example parser for the list of all available methods.
20
20
  #
21
21
  class WhoisNicUs < BaseShared2
22
+ # The .us registry says a missing record does not establish availability.
23
+ # Keep its no-record responses unknown and require record status evidence
24
+ # before reporting a domain as registered.
25
+ property_supported :status do
26
+ statuses = Array.wrap(node("Domain Status"))
27
+ registered_evidence? ? statuses : :unknown
28
+ end
29
+
30
+ property_supported :available? do
31
+ false
32
+ end
33
+
34
+ property_supported :registered? do
35
+ registered_evidence?
36
+ end
37
+
38
+ private
39
+
40
+ def registered_evidence?
41
+ domain_name = node("Domain Name").to_s.strip
42
+ statuses = Array.wrap(node("Domain Status")).reject { |value| value.to_s.strip.empty? }
43
+
44
+ domain_name.match?(/\A\S+\.us\z/i) && statuses.any? { |status| epp_status?(status) }
45
+ end
46
+
47
+ def epp_status?(status)
48
+ status.to_s.strip.match?(
49
+ %r{\A(?<status>(?:client|server|pending)[A-Z][A-Za-z]*)(?:\s+https?://icann\.org/epp#\k<status>)?\z}
50
+ )
51
+ end
22
52
  end
23
53
 
24
54
  end
@@ -0,0 +1,70 @@
1
+ #--
2
+ # Ruby Whois
3
+ #++
4
+
5
+ require_relative 'base'
6
+ require_relative 'registry_response_safety'
7
+
8
+ module Whois
9
+ class Parsers
10
+
11
+ # Parser for PKNIC's port-43 service.
12
+ #
13
+ # PKNIC identifies positive registration and availability results with
14
+ # separate status lines. Keep other responses unknown rather than
15
+ # inferring absence from an incomplete "Not Registered" message.
16
+ class WhoisPknicNetPk < Base
17
+ include RegistryResponseSafety
18
+
19
+ property_supported :domain do
20
+ echoed_domain
21
+ end
22
+
23
+ property_supported :status do
24
+ registered = registered_evidence?
25
+ available = available_evidence?
26
+
27
+ if registered && !available
28
+ :registered
29
+ elsif available && !registered
30
+ :available
31
+ else
32
+ :unknown
33
+ end
34
+ end
35
+
36
+ property_supported :available? do
37
+ available_evidence? && !registered_evidence?
38
+ end
39
+
40
+ property_supported :registered? do
41
+ registered_evidence? && !available_evidence?
42
+ end
43
+
44
+ property_not_supported :created_on
45
+ property_not_supported :updated_on
46
+ property_not_supported :expires_on
47
+ property_not_supported :registrant_contacts
48
+ property_not_supported :admin_contacts
49
+ property_not_supported :technical_contacts
50
+ property_not_supported :nameservers
51
+
52
+ private
53
+
54
+ def echoed_domain
55
+ content_for_scanner[/^ {0,8}Domain:[ \t]*((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+pk)[ \t]*$/i, 1]
56
+ end
57
+
58
+ def registered_evidence?
59
+ !!(echoed_domain && content_for_scanner.match?(/^ {0,8}Status: Domain is Registered[ \t]*$/))
60
+ end
61
+
62
+ def available_evidence?
63
+ !!(echoed_domain &&
64
+ content_for_scanner.match?(/^ {0,8}Status: Not Registered, and may be available if valid[ \t]*$/) &&
65
+ content_for_scanner.match?(/^ {0,8}Available: Yes\.[ \t]*$/))
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -27,21 +27,15 @@ module Whois
27
27
  include RegistryResponseSafety
28
28
 
29
29
  property_supported :status do
30
- if available?
31
- :available
32
- elsif content_for_scanner.match?(/^domain:/i)
33
- :registered
34
- else
35
- :unknown
36
- end
30
+ classify_status
37
31
  end
38
32
 
39
33
  property_supported :available? do
40
- !!(content_for_scanner =~ /^%error 230 No Objects Found/)
34
+ classify_status == :available
41
35
  end
42
36
 
43
37
  property_supported :registered? do
44
- status == :registered
38
+ classify_status == :registered
45
39
  end
46
40
 
47
41
 
@@ -59,6 +53,92 @@ module Whois
59
53
 
60
54
  property_not_supported :expires_on
61
55
 
56
+ private
57
+
58
+ def classify_status
59
+ return :unknown if conflicting_evidence?
60
+
61
+ return :registered if registered_evidence?
62
+ return :available if available_evidence?
63
+
64
+ :unknown
65
+ end
66
+
67
+ def conflicting_evidence?
68
+ return false unless absence_markers.any?
69
+
70
+ record_details_present? || legacy_record_evidence?
71
+ end
72
+
73
+ def registered_evidence?
74
+ return false unless valid_domain_name_fields? && domain_names.one? && absence_markers.empty?
75
+
76
+ current_record_evidence? || legacy_record_evidence?
77
+ end
78
+
79
+ def current_record_evidence?
80
+ domain_status_values == ['clientTransferProhibited https://icann.org/epp#clientTransferProhibited']
81
+ end
82
+
83
+ def legacy_record_evidence?
84
+ legacy_domain_names.one? &&
85
+ content_for_scanner.match?(/^[ \t]*domain:Class-Name:domain[ \t]*$/i) &&
86
+ content_for_scanner.match?(/^[ \t]*%ok[ \t]*$/i)
87
+ end
88
+
89
+ def available_evidence?
90
+ absence_markers.one? && valid_domain_name_fields? && !record_details_present? &&
91
+ !legacy_record_evidence?
92
+ end
93
+
94
+ def valid_domain_name_fields?
95
+ fields = domain_name_field_values
96
+ return false if domain_name_field? && fields.empty?
97
+
98
+ fields.empty? || (fields.one? && domain_names.one?)
99
+ end
100
+
101
+ def domain_names
102
+ (current_domain_names + legacy_domain_names).uniq
103
+ end
104
+
105
+ def domain_name_field_values
106
+ content_for_scanner.scan(/^[ \t]*Domain Name:[ \t]*(\S+)[ \t]*$/i).flatten +
107
+ content_for_scanner.scan(/^[ \t]*domain:Domain-Name:[ \t]*(\S+)[ \t]*$/i).flatten
108
+ end
109
+
110
+ def current_domain_names
111
+ content_for_scanner.scan(
112
+ /^[ \t]*Domain Name:[ \t]*((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ma)[ \t]*$/i
113
+ ).flatten.map(&:downcase)
114
+ end
115
+
116
+ def legacy_domain_names
117
+ content_for_scanner.scan(
118
+ /^[ \t]*domain:Domain-Name:[ \t]*((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ma)[ \t]*$/i
119
+ ).flatten.map(&:downcase)
120
+ end
121
+
122
+ def domain_status_values
123
+ content_for_scanner.scan(/^[ \t]*Domain Status:[ \t]*(.+?)[ \t]*$/i).flatten
124
+ end
125
+
126
+ def absence_markers
127
+ content_for_scanner.scan(
128
+ /^[ \t]*(?:%error 230 No Objects Found|The queried object does not exist: No Object Found)[ \t]*$/i
129
+ )
130
+ end
131
+
132
+ def domain_name_field?
133
+ content_for_scanner.match?(/^[ \t]*(?:Domain Name:|domain:Domain-Name:)/i)
134
+ end
135
+
136
+ def record_details_present?
137
+ content_for_scanner.match?(
138
+ /^[ \t]*(?:Domain Status|Domain ID|Registrar|Creation Date|Updated Date|Registry Expiry Date|Name Server|domain:Class-Name|domain:ID|domain:Auth-Area):/i
139
+ )
140
+ end
141
+
62
142
  end
63
143
 
64
144
  end
@@ -20,6 +20,7 @@ module Whois
20
20
  DISCLAIMER_MATCHES = [
21
21
  "TERMS OF USE:", # global
22
22
  "Terminos de Uso:", # whois.nic.hn
23
+ "Queda absolutamente prohibido su uso", # current whois.nic.hn footer
23
24
  "The data in the WHOIS database of Meridian", # whois.meridiantld.net
24
25
  "This information is provided", # whois.gg
25
26
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whois-parser-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simone Carletti
@@ -392,6 +392,7 @@ files:
392
392
  - lib/whois/parsers/whois.nic.democrat.rb
393
393
  - lib/whois/parsers/whois.nic.design.rb
394
394
  - lib/whois/parsers/whois.nic.diamonds.rb
395
+ - lib/whois/parsers/whois.nic.digital.rb
395
396
  - lib/whois/parsers/whois.nic.direct.rb
396
397
  - lib/whois/parsers/whois.nic.directory.rb
397
398
  - lib/whois/parsers/whois.nic.dm.rb
@@ -403,6 +404,7 @@ files:
403
404
  - lib/whois/parsers/whois.nic.ec.rb
404
405
  - lib/whois/parsers/whois.nic.eco.rb
405
406
  - lib/whois/parsers/whois.nic.education.rb
407
+ - lib/whois/parsers/whois.nic.email.rb
406
408
  - lib/whois/parsers/whois.nic.energy.rb
407
409
  - lib/whois/parsers/whois.nic.engineer.rb
408
410
  - lib/whois/parsers/whois.nic.equipment.rb
@@ -435,6 +437,7 @@ files:
435
437
  - lib/whois/parsers/whois.nic.gal.rb
436
438
  - lib/whois/parsers/whois.nic.games.rb
437
439
  - lib/whois/parsers/whois.nic.gd.rb
440
+ - lib/whois/parsers/whois.nic.ge.rb
438
441
  - lib/whois/parsers/whois.nic.gent.rb
439
442
  - lib/whois/parsers/whois.nic.giving.rb
440
443
  - lib/whois/parsers/whois.nic.gl.rb
@@ -485,6 +488,7 @@ files:
485
488
  - lib/whois/parsers/whois.nic.limited.rb
486
489
  - lib/whois/parsers/whois.nic.limo.rb
487
490
  - lib/whois/parsers/whois.nic.link.rb
491
+ - lib/whois/parsers/whois.nic.live.rb
488
492
  - lib/whois/parsers/whois.nic.lk.rb
489
493
  - lib/whois/parsers/whois.nic.london.rb
490
494
  - lib/whois/parsers/whois.nic.love.rb
@@ -500,6 +504,7 @@ files:
500
504
  - lib/whois/parsers/whois.nic.mba.rb
501
505
  - lib/whois/parsers/whois.nic.md.rb
502
506
  - lib/whois/parsers/whois.nic.me.rb
507
+ - lib/whois/parsers/whois.nic.media.rb
503
508
  - lib/whois/parsers/whois.nic.memorial.rb
504
509
  - lib/whois/parsers/whois.nic.men.rb
505
510
  - lib/whois/parsers/whois.nic.mg.rb
@@ -521,6 +526,7 @@ files:
521
526
  - lib/whois/parsers/whois.nic.net.ng.rb
522
527
  - lib/whois/parsers/whois.nic.net.sa.rb
523
528
  - lib/whois/parsers/whois.nic.net.sb.rb
529
+ - lib/whois/parsers/whois.nic.network.rb
524
530
  - lib/whois/parsers/whois.nic.news.rb
525
531
  - lib/whois/parsers/whois.nic.nf.rb
526
532
  - lib/whois/parsers/whois.nic.ninja.rb
@@ -574,6 +580,7 @@ files:
574
580
  - lib/whois/parsers/whois.nic.scot.rb
575
581
  - lib/whois/parsers/whois.nic.security.rb
576
582
  - lib/whois/parsers/whois.nic.select.rb
583
+ - lib/whois/parsers/whois.nic.services.rb
577
584
  - lib/whois/parsers/whois.nic.sh.rb
578
585
  - lib/whois/parsers/whois.nic.shoes.rb
579
586
  - lib/whois/parsers/whois.nic.shopping.rb
@@ -620,6 +627,7 @@ files:
620
627
  - lib/whois/parsers/whois.nic.tm.rb
621
628
  - lib/whois/parsers/whois.nic.today.rb
622
629
  - lib/whois/parsers/whois.nic.tools.rb
630
+ - lib/whois/parsers/whois.nic.top.rb
623
631
  - lib/whois/parsers/whois.nic.town.rb
624
632
  - lib/whois/parsers/whois.nic.toyota.rb
625
633
  - lib/whois/parsers/whois.nic.toys.rb
@@ -669,6 +677,7 @@ files:
669
677
  - lib/whois/parsers/whois.pairnic.com.rb
670
678
  - lib/whois/parsers/whois.pandi.or.id.rb
671
679
  - lib/whois/parsers/whois.pir.org.rb
680
+ - lib/whois/parsers/whois.pknic.net.pk.rb
672
681
  - lib/whois/parsers/whois.pnina.ps.rb
673
682
  - lib/whois/parsers/whois.publicinterestregistry.net.rb
674
683
  - lib/whois/parsers/whois.register.bg.rb