whois-parser-next 0.4.2 → 0.4.4

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: 638978b26282a734fc56d9b8fca805a2808a6a3eaf4aaa9804be31a1b1760a46
4
- data.tar.gz: 216cfa13ce249145a3d4dff1eeb9ced17c21b2d2c0dcac5faaa1b3279666bc2e
3
+ metadata.gz: f4c96a0a74b64788109bc1bf2d100571089e9bbbd49668c8146e175567b0f08c
4
+ data.tar.gz: e9e721292aaad948280edebea47d502ce309c1db903be8da692bfb50bf4e6e00
5
5
  SHA512:
6
- metadata.gz: 6dc981427e17392f85c4d99f03ac033b9d62bfe525f6ad3785f4b1ed0d22ddf05ed1bc1848e0f0b6599a0f776ae9cfb4b938a6e913c997222313cc5693d20178
7
- data.tar.gz: fa74d6035567289e6d29f140740a04ae5b1694c2f483ddbb3c1f1e54e1964a0f6621f3be95efb4ae1fd3a964d74141c5bf6d1b8cd33edcf38d1ba662d91bc329
6
+ metadata.gz: daf6ebf45111891d95f5c3bd6bc0bc57d5be7a0e641fd8054cdb359bf5bc64a2406db5cc4aeb9cf02e195e65a740abc15260b5f092f3c23b75a1c6927bc3a56a
7
+ data.tar.gz: 87849973634827aa16cc8ecad6e78031cf348ee01351709217bcfc9580cf9603a7943824d87df9a0222d76bf7940db020b4dc5928f2141f225390dfffc07e639
data/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  This project uses [Semantic Versioning 2.0.0](http://semver.org/).
4
4
 
5
+ #### Release 0.4.4 (2026-10-18)
6
+
7
+ - ADDED: Added parsers for the current `.gh` and `.do` CoCCA WHOIS response
8
+ shapes, including exact EPP registration states and `No Object Found`
9
+ absence responses. Added `.vu` handling for registered records and the exact
10
+ GoDaddy `No Data Found` response. Domain identity checks remain strict, and
11
+ mixed or ambiguous replies remain unknown.
12
+ - FIXED: Scope JWhois `NO OBJECT FOUND!` blocks to the named object. Missing
13
+ contact objects no longer make a domain with registered record data appear
14
+ available; absence requires one exact domain-object response. Malformed or
15
+ mixed domain absence remains unknown. Added two regression examples.
16
+ - EVIDENCE: IANA lists `whois.nic.gh`, `whois.nic.do`, and `whois.dnrs.vu`
17
+ for [`.gh`](https://www.iana.org/domains/root/db/gh.html),
18
+ [`.do`](https://www.iana.org/domains/root/db/do.html), and
19
+ [`.vu`](https://www.iana.org/domains/root/db/vu.html). IANA lists
20
+ `whois.mediaserv.net` for [`.mq`](https://www.iana.org/domains/root/db/mq.html).
21
+ The `.gh`, `.do`, and `.vu` fixtures are minimal synthetic shape controls;
22
+ the `.mq` regression uses an inline synthetic response. Live response
23
+ originals and personal contact details are not included.
24
+ - TESTED: The full suite passes 7,054 examples with 0 failures on Ruby 3.2.2
25
+ and 3.4.5. Package smoke passes on both versions. The supported-Ruby CI
26
+ matrix is the release gate.
27
+
28
+ #### Release 0.4.3 (2026-10-18)
29
+
30
+ - ADDED: Add the official `.ls` WHOIS parser for the registered `domain`,
31
+ `registered`, and `expire` fields and the registry's exact generated-name
32
+ absence response. Mixed and incomplete replies remain unknown.
33
+ - UPDATED: Recognize the current `.ax` dotted-field registered format and
34
+ exact `Domain not found` absence marker. For `.ki`, `.bi`, and `.ci`, accept
35
+ current CoCCA EPP status and absence shapes only with a single valid domain
36
+ identity; contradictory or malformed replies remain unknown.
37
+ - EVIDENCE: IANA lists the WHOIS hosts for [`.ki`](https://www.iana.org/domains/root/db/ki.html),
38
+ [`.bi`](https://www.iana.org/domains/root/db/bi.html),
39
+ [`.ci`](https://www.iana.org/domains/root/db/ci.html),
40
+ [`.ax`](https://www.iana.org/domains/root/db/ax.html), and
41
+ [`.ls`](https://www.iana.org/domains/root/db/ls.html). CoCCA fixtures are
42
+ synthetic technical-shape controls because record redistribution is
43
+ restricted; `.ax` and `.ls` fixtures contain reduced technical excerpts.
44
+ - TESTED: The focused suite passes 84 examples. The full suite passes 7,040
45
+ examples with 0 failures on Ruby 3.2.2 and 3.4.5; package smoke passes on
46
+ both and loads every packaged parser. The 633-file gem contains the CoCCA
47
+ helper and `.ls` parser and has no native extensions. Supported-Ruby CI
48
+ passed on Ruby 3.2, 3.3, 3.4, and 4.0.7 for implementation commit
49
+ `7b32576500609aecd8f69b9ae7941bdd41213647` (run 39172234679).
50
+
5
51
  #### Release 0.4.2 (2026-10-18)
6
52
 
7
53
  - SAFETY: `.ma` accepts the observed `active https://icann.org/epp#active`
@@ -1,6 +1,6 @@
1
1
  module Whois
2
2
  class Parser
3
3
  # The current library version.
4
- VERSION = "0.4.2".freeze
4
+ VERSION = "0.4.4".freeze
5
5
  end
6
6
  end
@@ -14,17 +14,25 @@ module Whois
14
14
  end
15
15
 
16
16
  property_supported :status do
17
- if available?
18
- :available
19
- elsif registered_evidence?
20
- :registered
17
+ text = content_for_scanner
18
+ domains = text.scan(/^domain:[ \t]*(\S+)[ \t]*\r?$/i).flatten
19
+ missing = text.scan(/^NO OBJECT FOUND![ \t]*\r?\nobject:[ \t]*(\S+)[ \t]*\r?\ntype:[ \t]*([^\r\n]+)$/i)
20
+ markers = text.scan(/^NO OBJECT FOUND![ \t]*\r?$/i).length
21
+ return :unknown unless markers == missing.length
22
+
23
+ if domains.empty?
24
+ missing.one? && missing.first.last.strip.casecmp('domain').zero? &&
25
+ missing.first.first.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:gf|mq)\z/i) ? :available : :unknown
26
+ elsif domains.one? && domains.first.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:gf|mq)\z/i) && registered_evidence?
27
+ # Referenced person objects may be absent while the domain exists.
28
+ missing.all? { |name, type| type.strip.casecmp('person').zero? && !name.casecmp(domains.first).zero? } ? :registered : :unknown
21
29
  else
22
30
  :unknown
23
31
  end
24
32
  end
25
33
 
26
34
  property_supported :available? do
27
- content_for_scanner.match?(/^NO OBJECT FOUND!\s*$/i)
35
+ status == :available
28
36
  end
29
37
 
30
38
  property_supported :registered? do
@@ -0,0 +1,33 @@
1
+ require_relative 'registry_response_safety'
2
+
3
+ module Whois
4
+ class Parsers
5
+ # Current CoCCA responses annotate status values with exact EPP links and
6
+ # can identify an absent query using a Domain Name echo plus one marker.
7
+ module CoccaEppStatus
8
+ include RegistryResponseSafety
9
+
10
+ private
11
+
12
+ def current_cocca_status
13
+ text = content_for_scanner
14
+ absent = text.scan(/^(?:The queried object does not exist:|Domain Status:)[ \t]*No Object Found[ \t]*$/i)
15
+ return nil unless absent.any? || text.match?(/^Domain Status:.*https:\/\/icann\.org\/epp#/i)
16
+
17
+ names = text.scan(/^Domain Name:[ \t]*([^\r\n]+)$/i).flatten.map(&:strip)
18
+ return :unknown unless names.one? && names.first.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+#{self.class::CURRENT_COCCA_SUFFIX}\z/i)
19
+
20
+ statuses = text.scan(/^Domain Status:[ \t]*([^\r\n]+)$/i).flatten.map(&:strip)
21
+ details = text.match?(/^(?:Creation Date|Updated Date|Registry Expiry Date|Name Server|Sponsoring Registrar):[ \t]*\S/i)
22
+ return :available if absent.one? && statuses.all? { |value| value.casecmp('No Object Found').zero? } && !details
23
+ return :unknown if absent.any?
24
+
25
+ valid = statuses.all? do |status|
26
+ status.match?(/\A(?<token>ok|active|(?:client|server)(?:Delete|Renew|Transfer|Update)Prohibited)(?:[ \t]+https:\/\/icann\.org\/epp#\k<token>)?\z/i)
27
+ end
28
+ present = statuses.any? { |status| status.match?(/\A(?:ok|active)(?:[ \t]|\z)/i) }
29
+ valid && present ? :registered : :unknown
30
+ end
31
+ end
32
+ end
33
+ end
@@ -27,7 +27,9 @@ module Whois
27
27
  include RegistryResponseSafety
28
28
 
29
29
  property_supported :status do
30
- if available?
30
+ if contradictory_evidence?
31
+ :unknown
32
+ elsif available?
31
33
  :available
32
34
  elsif registered_evidence?
33
35
  :registered
@@ -37,7 +39,7 @@ module Whois
37
39
  end
38
40
 
39
41
  property_supported :available? do
40
- !!(content_for_scanner =~ /^No records matching \S+ found\.\s*$/i ||
42
+ !contradictory_evidence? && !!(content_for_scanner =~ /^No records matching \S+ found\.\s*$/i ||
41
43
  content_for_scanner =~ /^Domain not found\s*$/i)
42
44
  end
43
45
 
@@ -65,9 +67,21 @@ module Whois
65
67
 
66
68
  private
67
69
 
70
+ def contradictory_evidence?
71
+ markers = content_for_scanner.scan(/^(?:No records matching \S+ found\.|Domain not found)[ \t]*$/i)
72
+ markers.length > 1 || markers.any? && content_for_scanner.match?(/^(?:Domain Name:|domain\.+:|status\.+:|Created:|created\.+:|Name Server \d:)/i)
73
+ end
74
+
68
75
  def registered_evidence?
69
- content_for_scanner.match?(/^Domain Name:\s+\S+/i) &&
70
- content_for_scanner.match?(/^(?:Created|Name Server \d):\s+\S+/i)
76
+ dotted = content_for_scanner.scan(/^domain\.+:[ \t]*(\S+)[ \t]*$/i).flatten
77
+ if dotted.any?
78
+ dotted.one? && dotted.first.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ax\z/i) &&
79
+ content_for_scanner.scan(/^status\.+:[ \t]*(.+)$/i).flatten.map(&:strip) == ['Registered'] &&
80
+ content_for_scanner.match?(/^created\.+:[ \t]*\d{2}\.\d{2}\.\d{4}[ \t]*$/i)
81
+ else
82
+ content_for_scanner.match?(/^Domain Name:\s+\S+/i) &&
83
+ content_for_scanner.match?(/^(?:Created|Name Server \d):\s+\S+/i)
84
+ end
71
85
  end
72
86
 
73
87
  end
@@ -0,0 +1,26 @@
1
+ require_relative 'whois.biz'
2
+ require_relative 'registry_response_safety'
3
+
4
+ module Whois
5
+ class Parsers
6
+ class WhoisDnrsVu < WhoisBiz
7
+ include RegistryResponseSafety
8
+
9
+ property_supported :status do
10
+ text = content_for_scanner
11
+ absence = text.scan(/^No Data Found[ \t]*\r?$/i)
12
+ record = text.match?(/^(?:Domain Name|Registry Domain ID|Creation Date|Registry Expiry Date|Registrar|Domain Status|Name Server):[ \t]*\S/i)
13
+ if absence.any?
14
+ absence.one? && !record ? :available : :unknown
15
+ else
16
+ names = text.scan(/^Domain Name:[ \t]*([^\r\n]+)$/i).flatten.map(&:strip)
17
+ details = text.match?(/^(?:Registry Domain ID|Creation Date|Registry Expiry Date|Registrar):[ \t]*\S/i)
18
+ names.one? && names.first.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+vu\z/i) && details ? :registered : :unknown
19
+ end
20
+ end
21
+
22
+ property_supported(:available?) { status == :available }
23
+ property_supported(:registered?) { status == :registered }
24
+ end
25
+ end
26
+ end
@@ -9,6 +9,7 @@
9
9
 
10
10
  require_relative 'base'
11
11
  require_relative 'registry_response_safety'
12
+ require_relative 'cocca_epp_status'
12
13
 
13
14
 
14
15
  module Whois
@@ -26,10 +27,14 @@ module Whois
26
27
  # and examples.
27
28
  #
28
29
  class WhoisNicCi < Base
29
- include RegistryResponseSafety
30
+ include CoccaEppStatus
31
+
32
+ CURRENT_COCCA_SUFFIX = "ci".freeze
30
33
 
31
34
  property_supported :status do
32
- if available?
35
+ if (current = current_cocca_status)
36
+ current
37
+ elsif available?
33
38
  :available
34
39
  elsif content_for_scanner.match?(/^Domain:\s*\S+/i) && content_for_scanner.match?(/^Created:\s*\S+/i)
35
40
  :registered
@@ -39,7 +44,8 @@ module Whois
39
44
  end
40
45
 
41
46
  property_supported :available? do
42
- !!(content_for_scanner =~ /^Domain \S+ not found\s*$/i)
47
+ current = current_cocca_status
48
+ current ? current == :available : !!(content_for_scanner =~ /^Domain \S+ not found\s*$/i)
43
49
  end
44
50
 
45
51
  property_supported :registered? do
@@ -0,0 +1,15 @@
1
+ require_relative 'base_cocca2'
2
+ require_relative 'cocca_epp_status'
3
+
4
+ module Whois
5
+ class Parsers
6
+ class WhoisNicDo < BaseCocca2
7
+ include CoccaEppStatus
8
+ CURRENT_COCCA_SUFFIX = "do".freeze
9
+
10
+ property_supported(:status) { current_cocca_status || :unknown }
11
+ property_supported(:available?) { status == :available }
12
+ property_supported(:registered?) { status == :registered }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'base_cocca2'
2
+ require_relative 'cocca_epp_status'
3
+
4
+ module Whois
5
+ class Parsers
6
+ class WhoisNicGh < BaseCocca2
7
+ include CoccaEppStatus
8
+ CURRENT_COCCA_SUFFIX = "gh".freeze
9
+
10
+ property_supported(:status) { current_cocca_status || :unknown }
11
+ property_supported(:available?) { status == :available }
12
+ property_supported(:registered?) { status == :registered }
13
+ end
14
+ end
15
+ end
@@ -8,6 +8,7 @@
8
8
 
9
9
 
10
10
  require_relative 'base_cocca2'
11
+ require_relative 'cocca_epp_status'
11
12
 
12
13
 
13
14
  module Whois
@@ -19,6 +20,21 @@ module Whois
19
20
  # The Example parser for the list of all available methods.
20
21
  #
21
22
  class WhoisNicKi < BaseCocca2
23
+ include CoccaEppStatus
24
+
25
+ CURRENT_COCCA_SUFFIX = "ki".freeze
26
+
27
+ property_supported :status do
28
+ current_cocca_status || super()
29
+ end
30
+
31
+ property_supported :available? do
32
+ status == :available
33
+ end
34
+
35
+ property_supported :registered? do
36
+ status == :registered
37
+ end
22
38
  end
23
39
 
24
40
  end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Ruby Whois
3
+ #++
4
+
5
+ require_relative 'whois.nic.cr'
6
+
7
+ module Whois
8
+ class Parsers
9
+ # The official LSNIC response uses NIC-style registration fields.
10
+ class WhoisNicLs < WhoisNicCr
11
+ property_supported :status do
12
+ classify_ls_status
13
+ end
14
+
15
+ property_supported :available? do
16
+ classify_ls_status == :available
17
+ end
18
+
19
+ private
20
+
21
+ def classify_ls_status
22
+ text = content_for_scanner
23
+ domains = text.scan(/^domain:[ \t]*(\S+)[ \t]*$/i).flatten
24
+ absent = text.scan(/^No record found for '([^']+)'\.[ \t]*$/i).flatten
25
+ details = text.match?(/^(?:domain|registered|expire|nserver|registrar):/i)
26
+ if absent.one? && valid_ls_domain?(absent.first) && !details
27
+ :available
28
+ elsif absent.empty? && domains.one? && valid_ls_domain?(domains.first) &&
29
+ text.scan(/^registered:[ \t]*\d{2}\.\d{2}\.\d{4}(?:[ \t][^\r\n]*)?$/i).one? &&
30
+ text.scan(/^expire:[ \t]*\d{2}\.\d{2}\.\d{4}[ \t]*$/i).one?
31
+ :registered
32
+ else
33
+ :unknown
34
+ end
35
+ end
36
+
37
+ def valid_ls_domain?(name)
38
+ name.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+ls\z/i)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -8,6 +8,7 @@
8
8
 
9
9
 
10
10
  require_relative 'base_cocca2'
11
+ require_relative 'cocca_epp_status'
11
12
 
12
13
 
13
14
  module Whois
@@ -19,6 +20,21 @@ module Whois
19
20
  # The Example parser for the list of all available methods.
20
21
  #
21
22
  class Whois1NicBi < BaseCocca2
23
+ include CoccaEppStatus
24
+
25
+ CURRENT_COCCA_SUFFIX = "bi".freeze
26
+
27
+ property_supported :status do
28
+ current_cocca_status || super()
29
+ end
30
+
31
+ property_supported :available? do
32
+ status == :available
33
+ end
34
+
35
+ property_supported :registered? do
36
+ status == :registered
37
+ end
22
38
  end
23
39
 
24
40
  end
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.4.2
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simone Carletti
@@ -196,6 +196,7 @@ files:
196
196
  - lib/whois/parsers/capetown-whois.registry.net.za.rb
197
197
  - lib/whois/parsers/ccwhois.ksregistry.net.rb
198
198
  - lib/whois/parsers/ccwhois.verisign-grs.com.rb
199
+ - lib/whois/parsers/cocca_epp_status.rb
199
200
  - lib/whois/parsers/coza-whois.registry.net.za.rb
200
201
  - lib/whois/parsers/cwhois.cnnic.cn.rb
201
202
  - lib/whois/parsers/durban-whois.registry.net.za.rb
@@ -236,6 +237,7 @@ files:
236
237
  - lib/whois/parsers/whois.corporatedomains.com.rb
237
238
  - lib/whois/parsers/whois.denic.de.rb
238
239
  - lib/whois/parsers/whois.dk-hostmaster.dk.rb
240
+ - lib/whois/parsers/whois.dnrs.vu.rb
239
241
  - lib/whois/parsers/whois.dns.be.rb
240
242
  - lib/whois/parsers/whois.dns.hr.rb
241
243
  - lib/whois/parsers/whois.dns.lu.rb
@@ -398,6 +400,7 @@ files:
398
400
  - lib/whois/parsers/whois.nic.direct.rb
399
401
  - lib/whois/parsers/whois.nic.directory.rb
400
402
  - lib/whois/parsers/whois.nic.dm.rb
403
+ - lib/whois/parsers/whois.nic.do.rb
401
404
  - lib/whois/parsers/whois.nic.doctor.rb
402
405
  - lib/whois/parsers/whois.nic.dog.rb
403
406
  - lib/whois/parsers/whois.nic.domains.rb
@@ -441,6 +444,7 @@ files:
441
444
  - lib/whois/parsers/whois.nic.gd.rb
442
445
  - lib/whois/parsers/whois.nic.ge.rb
443
446
  - lib/whois/parsers/whois.nic.gent.rb
447
+ - lib/whois/parsers/whois.nic.gh.rb
444
448
  - lib/whois/parsers/whois.nic.gifts.rb
445
449
  - lib/whois/parsers/whois.nic.gives.rb
446
450
  - lib/whois/parsers/whois.nic.giving.rb
@@ -498,6 +502,7 @@ files:
498
502
  - lib/whois/parsers/whois.nic.lk.rb
499
503
  - lib/whois/parsers/whois.nic.london.rb
500
504
  - lib/whois/parsers/whois.nic.love.rb
505
+ - lib/whois/parsers/whois.nic.ls.rb
501
506
  - lib/whois/parsers/whois.nic.ltd.rb
502
507
  - lib/whois/parsers/whois.nic.luxury.rb
503
508
  - lib/whois/parsers/whois.nic.lv.rb