whois-parser-next 0.4.7 → 0.4.9

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: bf39e6c3afbb1b6a0cb49c48cb3fb11cf6db7d0162aaba77c9027ca4d2fadc3f
4
- data.tar.gz: 1134d08ab3b1bcef0551f8008c366234805614e86cbe02ffe90501f5203dae84
3
+ metadata.gz: a5663fd18a8e122c6ccc8085b25e18be3b61fab9ff9cbd9bda64cdefbf2e10f5
4
+ data.tar.gz: a34398419d706543bf070bfa3edda47c501b06cb9ac0452c8e18c0c11d414204
5
5
  SHA512:
6
- metadata.gz: addd71819acd60b21142b60e419625ac8ebbed5b160b33e6cfe5562bcd064727e04b98c820f53bb98c1e1649158829b10b10d3c9ae7b900b47b25f57c1023043
7
- data.tar.gz: 56a6dde832f4f0a9990e7753ea7f709a2bcba56d461d4eac0aa299bdd69b5896fd3b584647b6766267e621c4f6d52e42961a1fca1521c619b086f96f2dc80fcf
6
+ metadata.gz: 2ae5c942ba61ee7143b6eec11d41d33b16f0a13046b8c1c150be040fbcedc377fea3580caeab8a8bce22b063e7d09c8f6fa7436056dd1240e17250670478dfcc
7
+ data.tar.gz: '089dfc0682b7bee5730c452b7ffb7b4e9022b3b82cea695f0ac6532d789c052cf2eead16de4d8ca6f651f9909b2f4e9ac34f80f2f2f7bab9e4bd43d69d66c1d2'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,72 @@
2
2
 
3
3
  This project uses [Semantic Versioning 2.0.0](http://semver.org/).
4
4
 
5
+ #### Release 0.4.9 (2026-10-25)
6
+
7
+ - ADDED: Added `registrar`, `registrant_contacts` and `technical_contacts` to
8
+ the `whois.aeda.net.ae` (`.ae`) parser. The registrar comes from the
9
+ `Registrar ID` and `Registrar Name` lines. Each contact comes from the
10
+ `<Role> Contact ID`, `Name` and `Organisation` lines. The registry publishes
11
+ no contact email over port 43; the `Contact Email` line of the legacy layout
12
+ only pointed to the web WHOIS, so it is not read. `admin_contacts` is not
13
+ supported because responses carry no admin contact. No-data responses yield
14
+ no registrar or contact, and blacklisted responses raise
15
+ `Whois::ResponseIsThrottled`.
16
+ - EVIDENCE: On 2026-10-24 a single lookup of `google.ae` on
17
+ `whois.aeda.net.ae` (TCP/43) returned the registrar, registrant and technical
18
+ contact lines in the new `current_contacts_registered.txt` fixture
19
+ (unmodified, CRLF preserved; it holds organisation and role data only, no
20
+ personal data).
21
+ - FIXED: The `whois.registry.qa` (`.qa`, `.com.qa` and the other `.qa`
22
+ second-level zones) parser raised `Whois::ParserError` on every current
23
+ response. The registry now opens each reply with the instructions of its web
24
+ lookup form and closes it with a copyright, terms of use and query limit
25
+ notice; the parser skips both. Current records also renamed their lines:
26
+ `Registrar` replaces `Registrar ID` and `Registrar Name`, `Domain Status`
27
+ (an EPP code followed by its ICANN URL) replaces `Status`, and
28
+ `Registrant Name` and `Tech Name` replace the `<Role> Contact ID/Name/Email`
29
+ lines. The parser reads both layouts: `status` returns the EPP codes,
30
+ `registered?` no longer requires the legacy `Status` line, and the registrar
31
+ and contacts carry a name only, because current records publish no
32
+ registrar ID, contact ID or email.
33
+ - EVIDENCE: On 2026-10-24 single lookups on `whois.registry.qa` (TCP/43)
34
+ returned the registered `google.qa` and `ooredoo.com.qa` responses and the
35
+ `No Data Found` responses for one generated unregistered name in each zone,
36
+ stored unmodified (CRLF preserved) as the new `current_status_registered.txt`,
37
+ `current_com_qa_status_registered.txt`, `current_status_available.txt` and
38
+ `current_com_qa_status_available.txt` fixtures. The registered fixtures hold
39
+ role and organisation names only, no personal data. The 2026-09-19 probes
40
+ that received empty responses no longer reproduce.
41
+
42
+ #### Release 0.4.8 (2026-10-24)
43
+
44
+ - ADDED: Added `registrant_contacts` to the `whois.trabis.gov.tr` (`.tr`)
45
+ parser. The `** Registrant:` block yields the name, and the email when one is
46
+ published. Address, city, country, phone and fax are read only from the fully
47
+ published eight-line layout, because hidden fields ("Hidden upon user
48
+ request") shorten the block. Fully hidden blocks, no-match responses and
49
+ throttled responses yield no contact.
50
+ - EVIDENCE: Bounded 2026-10-22 observations of `whois.trabis.gov.tr` returned a
51
+ fully published registrant block, a block with only the name visible
52
+ (`google.com.tr`), a fully hidden block, and the `No match found` response.
53
+ The fully published and fully hidden fixtures are redacted: the registrant
54
+ name, address and email, the domain name and the name servers are replaced
55
+ with placeholders while the line layout and whitespace are preserved.
56
+ - FIXED: The `whois.aeda.net.ae` (`.ae`) parser no longer reports the
57
+ registry's rate-limit reply ("BLACKLISTED: You have exceeded the query limit
58
+ ...") as registered. `registered?` was the negation of `available?`, so any
59
+ reply other than an explicit absence marker counted as a registration. The
60
+ parser now includes `RegistryResponseSafety` and treats the `BLACKLISTED:`
61
+ reply as throttled, so status, availability and registration raise
62
+ `Whois::ResponseIsThrottled`. Empty and access-denied replies now raise
63
+ `Whois::ResponseIsUnavailable` instead of counting as registered.
64
+ - EVIDENCE: On 2026-10-22, after a bounded series of lookups, `whois.aeda.net.ae`
65
+ answered every query from the client network, including `google.ae` and
66
+ generated unregistered names, with the single `BLACKLISTED:` line in the new
67
+ `response_blacklisted.txt` fixture (unmodified). Earlier the same day,
68
+ registered names and generated unregistered names returned the regular
69
+ registered and `No Data Found` responses.
70
+
5
71
  #### Release 0.4.7 (2026-10-19)
6
72
 
7
73
  - ADDED: Added strict parsing for the official `.gt` and `.com.gt` web WHOIS
@@ -1,6 +1,6 @@
1
1
  module Whois
2
2
  class Parser
3
3
  # The current library version.
4
- VERSION = "0.4.7".freeze
4
+ VERSION = "0.4.9".freeze
5
5
  end
6
6
  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
@@ -23,6 +24,14 @@ module Whois
23
24
  # The Example parser for the list of all available methods.
24
25
  #
25
26
  class WhoisAedaNetAe < Base
27
+ include RegistryResponseSafety
28
+
29
+ # The registry answers rate-limited clients with a single "BLACKLISTED:"
30
+ # line. That reply carries no domain data, so it must not be read as
31
+ # either registered or available.
32
+ def response_throttled?
33
+ super || content_for_scanner.match?(/^BLACKLISTED:/i)
34
+ end
26
35
 
27
36
  property_supported :status do
28
37
  if content_for_scanner =~ /Status:\s+(.+?)\n/
@@ -53,12 +62,50 @@ module Whois
53
62
  property_not_supported :expires_on
54
63
 
55
64
 
65
+ property_supported :registrar do
66
+ id = field("Registrar ID")
67
+ name = field("Registrar Name")
68
+ Parser::Registrar.new(id: id, name: name) if id || name
69
+ end
70
+
71
+
72
+ property_supported :registrant_contacts do
73
+ build_contact("Registrant", Parser::Contact::TYPE_REGISTRANT)
74
+ end
75
+
76
+ property_not_supported :admin_contacts
77
+
78
+ property_supported :technical_contacts do
79
+ build_contact("Tech", Parser::Contact::TYPE_TECHNICAL)
80
+ end
81
+
82
+
56
83
  property_supported :nameservers do
57
84
  content_for_scanner.scan(/Name Server:\s+(.+)\n/).flatten.map do |name|
58
85
  Parser::Nameserver.new(:name => name)
59
86
  end
60
87
  end
61
88
 
89
+
90
+ private
91
+
92
+ def field(label)
93
+ content_for_scanner[/^#{label}:[ \t]+(.+)$/, 1]&.strip.presence
94
+ end
95
+
96
+ # A contact is the "<Role> Contact ID/Name/Organisation" lines. The
97
+ # registry publishes no contact email over port 43: the "Contact Email"
98
+ # line of the legacy layout only pointed to the web WHOIS, so it is not
99
+ # read.
100
+ def build_contact(role, type)
101
+ id = field("#{role} Contact ID")
102
+ name = field("#{role} Contact Name")
103
+ organization = field("#{role} Contact Organisation")
104
+ return [] unless id || name || organization
105
+
106
+ [Parser::Contact.new(type: type, id: id, name: name, organization: organization)]
107
+ end
108
+
62
109
  end
63
110
 
64
111
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
 
10
10
  require_relative 'base_shared1'
11
+ require 'whois/scanners/whois.registry.qa'
11
12
 
12
13
 
13
14
  module Whois
@@ -15,10 +16,44 @@ module Whois
15
16
 
16
17
  # Parser for the whois.registry.qa server.
17
18
  #
19
+ # Current responses use the "Registrar", "Domain Status" and
20
+ # "<Role> Name" lines; the legacy layout used "Registrar ID/Name",
21
+ # "Status" and "<Role> Contact ID/Name/Email". Both are read.
22
+ #
18
23
  # @see Whois::Parsers::Example
19
24
  # The Example parser for the list of all available methods.
20
25
  #
21
26
  class WhoisRegistryQa < BaseShared1
27
+
28
+ self.scanner = Scanners::WhoisRegistryQa
29
+
30
+
31
+ # "Domain Status" carries the EPP status code followed by its ICANN URL.
32
+ property_supported :status do
33
+ Array.wrap(node("Status") || node("Domain Status")).map { |value| value.split.first }
34
+ end
35
+
36
+ property_supported :registered? do
37
+ !available? && !status.empty? && !node("Domain Name").to_s.strip.empty?
38
+ end
39
+
40
+
41
+ property_supported :registrar do
42
+ name = node("Registrar Name") || node("Registrar")
43
+ Parser::Registrar.new(id: node("Registrar ID"), name: name, organization: name) if name.present?
44
+ end
45
+
46
+
47
+ private
48
+
49
+ def build_contact(element, type)
50
+ return super if node("#{element} Contact ID")
51
+
52
+ node("#{element} Name") do |name|
53
+ Parser::Contact.new(type: type, name: name) if name.present?
54
+ end
55
+ end
56
+
22
57
  end
23
58
 
24
59
  end
@@ -7,6 +7,9 @@ module Whois
7
7
  class WhoisTrabisGovTr < Base
8
8
  include RegistryResponseSafety
9
9
 
10
+ HIDDEN = "Hidden upon user request".freeze
11
+ EMAIL = /[\w.+-]+@[\w-]+(?:\.[\w-]+)+/
12
+
10
13
  property_supported :domain do
11
14
  content_for_scanner.slice(/^\*\* Domain Name:\s+(.+)\n/, 1)
12
15
  end
@@ -43,7 +46,35 @@ module Whois
43
46
 
44
47
  property_not_supported :updated_on
45
48
  property_not_supported :registrar
46
- property_not_supported :registrant_contacts
49
+
50
+ # The "** Registrant:" block runs until the first blank line. A fully
51
+ # published block has eight lines: name, two address lines, "city,",
52
+ # country, email, phone and fax. Each hidden field reads "Hidden upon user
53
+ # request" and hidden blocks are shorter (a visible name plus four hidden
54
+ # lines), so positional fields are read only from the eight-line layout.
55
+ # The name is always the first line and the email is matched anywhere in
56
+ # the block. A block with no visible field yields no contact.
57
+ property_supported :registrant_contacts do
58
+ block = content_for_scanner.slice(/^\*\* Registrant:\n((?:.+\n)+)/, 1)
59
+ return [] unless block
60
+
61
+ lines = block.lines.map(&:strip)
62
+ value = ->(i) { (v = lines[i].to_s).empty? || v == "+" || v == HIDDEN ? nil : v }
63
+ email = lines.find { |l| l.match?(EMAIL) }&.slice(EMAIL)
64
+ name = value.call(0)
65
+ full = lines.size == 8
66
+
67
+ address = full ? [value.call(1), value.call(2)].compact.join("\n").presence : nil
68
+ city = full ? value.call(3)&.chomp(",")&.strip : nil
69
+ country = full ? value.call(4) : nil
70
+ phone = full ? value.call(6) : nil
71
+ fax = full ? value.call(7) : nil
72
+ return [] if [name, email, address, city, country, phone, fax].all?(&:nil?)
73
+
74
+ [Parser::Contact.new(type: Parser::Contact::TYPE_REGISTRANT, name: name, address: address,
75
+ city: city, country: country, email: email, phone: phone, fax: fax)]
76
+ end
77
+
47
78
  property_not_supported :admin_contacts
48
79
  property_not_supported :technical_contacts
49
80
 
@@ -0,0 +1,29 @@
1
+ require_relative 'base_shared1'
2
+
3
+ module Whois
4
+ module Scanners
5
+
6
+ # Scanner for the whois.registry.qa records.
7
+ class WhoisRegistryQa < BaseShared1
8
+
9
+ self.tokenizers = [:skip_head, :skip_end] + tokenizers
10
+
11
+
12
+ # Current responses open with the instructions of the registry's web
13
+ # lookup form.
14
+ tokenizer :skip_head do
15
+ @input.skip(/^Welcome to Qatar Domains Registry WHOIS Lookup\n(?:.*\n)*?Type the word you see into the verification box to proceed\.\n/)
16
+ end
17
+
18
+ # The status code reference ends the record; the copyright, terms of use
19
+ # and query limit notice follow it.
20
+ tokenizer :skip_end do
21
+ if @input.check(/^For more information on Whois status codes, please visit /)
22
+ @input.terminate
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ 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.7
4
+ version: 0.4.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simone Carletti
@@ -783,6 +783,7 @@ files:
783
783
  - lib/whois/scanners/whois.nic.it.rb
784
784
  - lib/whois/scanners/whois.nic.net.jo.rb
785
785
  - lib/whois/scanners/whois.pir.org.rb
786
+ - lib/whois/scanners/whois.registry.qa.rb
786
787
  - lib/whois/scanners/whois.rnids.rs.rb
787
788
  - lib/whois/scanners/whois.smallregistry.net.rb
788
789
  - lib/whois/scanners/whois.srs.net.nz.rb