whois-parser-next 0.4.1 → 0.4.3

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: '09b27e56a5225ea22fadb05db7497d93757e87e811520604377c2dcaa14900f8'
4
- data.tar.gz: 7db37727a691aad97de784fb680da596ad79f5918df671a9f862becae1a0d37e
3
+ metadata.gz: c58e47a473ff8911ebe65e7a94e538d557585d23196f8116cbeb9720a45f4421
4
+ data.tar.gz: b2e930eecc35340b1812c7a6e351d87e22adf67f1a9a1efaa48c53d736452382
5
5
  SHA512:
6
- metadata.gz: e71cf9f12d9602ad01877a3ab8592dfd387bc3f9afe6a01976a55a3d44af2e931ddd70debe9d9a60042b439712a4773cb4b65f29f5199f91d0815624ce749a7e
7
- data.tar.gz: 3671c9bd5513063ebde38e00f07b39e26b3b00f2ad605ba6233a402cf5002a43cf75f0c0ce52bc3dcce376c860309a438530764d3dfb6494d76006c507927ccb
6
+ metadata.gz: eb08e46591a409452ee668eb3087b02221afaeec040bba662654e26f8b3b5ea2e02213efe68a7bad508d5cf9a74686534af72394bad0207b4c6ec784111eeeb9
7
+ data.tar.gz: 632d2fe08d217051da515695dedec0f62f41bc145aad9cca96429adc7a822361c886a3b8414d99c517e71df6d56518a26528f96f72caf819d130cbcde1ffbdfb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  This project uses [Semantic Versioning 2.0.0](http://semver.org/).
4
4
 
5
+ #### Release 0.4.3 (2026-10-18)
6
+
7
+ - ADDED: Add the official `.ls` WHOIS parser for the registered `domain`,
8
+ `registered`, and `expire` fields and the registry's exact generated-name
9
+ absence response. Mixed and incomplete replies remain unknown.
10
+ - UPDATED: Recognize the current `.ax` dotted-field registered format and
11
+ exact `Domain not found` absence marker. For `.ki`, `.bi`, and `.ci`, accept
12
+ current CoCCA EPP status and absence shapes only with a single valid domain
13
+ identity; contradictory or malformed replies remain unknown.
14
+ - EVIDENCE: IANA lists the WHOIS hosts for [`.ki`](https://www.iana.org/domains/root/db/ki.html),
15
+ [`.bi`](https://www.iana.org/domains/root/db/bi.html),
16
+ [`.ci`](https://www.iana.org/domains/root/db/ci.html),
17
+ [`.ax`](https://www.iana.org/domains/root/db/ax.html), and
18
+ [`.ls`](https://www.iana.org/domains/root/db/ls.html). CoCCA fixtures are
19
+ synthetic technical-shape controls because record redistribution is
20
+ restricted; `.ax` and `.ls` fixtures contain reduced technical excerpts.
21
+ - TESTED: The focused suite passes 84 examples. The full suite passes 7,040
22
+ examples with 0 failures on Ruby 3.2.2 and 3.4.5; package smoke passes on
23
+ both and loads every packaged parser. The 633-file gem contains the CoCCA
24
+ helper and `.ls` parser and has no native extensions. Supported-Ruby CI
25
+ passed on Ruby 3.2, 3.3, 3.4, and 4.0.7 for implementation commit
26
+ `7b32576500609aecd8f69b9ae7941bdd41213647` (run 39172234679).
27
+
28
+ #### Release 0.4.2 (2026-10-18)
29
+
30
+ - SAFETY: `.ma` accepts the observed `active https://icann.org/epp#active`
31
+ status as registered only with exactly one valid `.ma` domain name and no
32
+ contradictory absence marker. Incomplete, multiple-domain, or mixed replies
33
+ remain unknown.
34
+ - SAFETY: `.sh` accepts the exact `Domain not found.` response as absence while
35
+ retaining the legacy `NOT FOUND` marker. Partial text, repeated absence
36
+ markers, record data mixed with absence, and error replies do not become
37
+ available.
38
+ - EVIDENCE: The `.ma` active-state meaning is documented by
39
+ [Registre.ma](https://www.registre.ma/language/en/domain-name/e-statuts);
40
+ IANA lists `whois.registre.ma` for `.ma` and `whois.nic.sh` for `.sh`
41
+ ([`.ma`](https://www.iana.org/domains/root/db/ma.html),
42
+ [`.sh`](https://www.iana.org/domains/root/db/sh.html)). Only minimal public
43
+ technical fields and synthetic ambiguity cases are used in tests; no
44
+ personal response data is included.
45
+ - TESTED: The full suite passes 7,024 examples with 0 failures on Ruby 3.2.2
46
+ and 3.4.5. The package smoke check passes on both versions.
47
+
5
48
  #### Release 0.4.1 (2026-10-17)
6
49
 
7
50
  - SAFETY: `.lu` records are registered only when the response contains a
@@ -1,6 +1,6 @@
1
1
  module Whois
2
2
  class Parser
3
3
  # The current library version.
4
- VERSION = "0.4.1".freeze
4
+ VERSION = "0.4.3".freeze
5
5
  end
6
6
  end
@@ -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
@@ -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
@@ -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
@@ -19,6 +19,33 @@ module Whois
19
19
  # The Example parser for the list of all available methods.
20
20
  #
21
21
  class WhoisNicSh < BaseIcb
22
+ self.scanner = Scanners::BaseIcannCompliant, {
23
+ pattern_available: /^(?:NOT FOUND|Domain not found\.)[ \t]*\r?$/i,
24
+ }
25
+
26
+ property_supported :status do
27
+ if absence_marker_count.positive?
28
+ available? ? :available : :unknown
29
+ else
30
+ super()
31
+ end
32
+ end
33
+
34
+ property_supported :available? do
35
+ absence_marker_count == 1 && !record_details_present?
36
+ end
37
+
38
+ private
39
+
40
+ def absence_marker_count
41
+ content_for_scanner.scan(/^(?:NOT FOUND|Domain not found\.)[ \t]*\r?$/i).length
42
+ end
43
+
44
+ def record_details_present?
45
+ content_for_scanner.match?(
46
+ /^[ \t]*(?:Domain Name|Registry Domain ID|Domain Status|Creation Date|Updated Date|Registry Expiry Date|Registrar|Name Server):/i
47
+ )
48
+ end
22
49
  end
23
50
 
24
51
  end
@@ -77,7 +77,10 @@ module Whois
77
77
  end
78
78
 
79
79
  def current_record_evidence?
80
- domain_status_values == ['clientTransferProhibited https://icann.org/epp#clientTransferProhibited']
80
+ [
81
+ ['clientTransferProhibited https://icann.org/epp#clientTransferProhibited'],
82
+ ['active https://icann.org/epp#active'],
83
+ ].include?(domain_status_values)
81
84
  end
82
85
 
83
86
  def legacy_record_evidence?
@@ -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.1
4
+ version: 0.4.3
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
@@ -498,6 +499,7 @@ files:
498
499
  - lib/whois/parsers/whois.nic.lk.rb
499
500
  - lib/whois/parsers/whois.nic.london.rb
500
501
  - lib/whois/parsers/whois.nic.love.rb
502
+ - lib/whois/parsers/whois.nic.ls.rb
501
503
  - lib/whois/parsers/whois.nic.ltd.rb
502
504
  - lib/whois/parsers/whois.nic.luxury.rb
503
505
  - lib/whois/parsers/whois.nic.lv.rb