whois-parser-next 0.4.8 → 0.4.10

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: dd5238487b0cc29b77e6d56f3594c009ea7712fe877fc31a41079dc1e49178be
4
- data.tar.gz: 6f94667791431a36eb9b45ce8550143c89b91e9f08d88eafded9411722ae92fc
3
+ metadata.gz: 9e023caaec0b71465576d51e1aab35e0fa956f22fe435c9ebe91e7eae29e0d18
4
+ data.tar.gz: e90fe07318d7c90413f88ba7201eade76e7fc8ada882da016f1bcfb9574bc68e
5
5
  SHA512:
6
- metadata.gz: 454380ba261a261d3f13e91646c54fde3adf7141b5782580323c3511f45b13c1dde9fd5f3f29b03855a090d7d48585304d660788e5a2942f943706670ae104cf
7
- data.tar.gz: 21b62020ab6c094190a908d3e3e926b38928589c1727266fb691743b06003662a8ee3c22a973c6904b38f37402f6596e68de25efcf2a2f2443f9079e709f2564
6
+ metadata.gz: 2a55f20c2508a3a06d2a704e255532940ddf67b7ae3c9249b1a893b60c1f664c56ddd497aeac46cb1e14b4f6a9a8576db3411d318f768504027f0131916232c2
7
+ data.tar.gz: 3bd083076d0699b8520cd1cd232f09a8c755dcd33192198d7512974f44d73c50044201e34a919eb617c6132079710628c4fa3069e4306b4754292fbc8446f406
data/CHANGELOG.md CHANGED
@@ -2,6 +2,61 @@
2
2
 
3
3
  This project uses [Semantic Versioning 2.0.0](http://semver.org/).
4
4
 
5
+ #### Release 0.4.10 (2026-11-02)
6
+
7
+ - SAFETY: A response no longer keeps a scanner looping forever. A reply cut
8
+ off right after a disclaimer or notice header, with no final newline (for
9
+ example a Verisign body of just `TERMS OF USE:`, or the CIRA, `.ie`, ATI,
10
+ SX, PIR and configured Afilias disclaimers), made the tokenizer report
11
+ success without consuming input, so parsing never finished. The scanner now
12
+ raises `Whois::ParserError` when tokens stop consuming input, while still
13
+ allowing the short in-place state changes some scanners use to close a
14
+ section.
15
+ - SAFETY: The `whois.iana.org` scanner matches repeated field keys literally.
16
+ A key taken from the response was used as a regular expression, so a key
17
+ such as `()|^` matched without consuming input and looped forever. Contact
18
+ handles in the `.fr`-family, `.br` and `.ua` parsers are also matched
19
+ literally instead of as patterns.
20
+ - TESTED: `spec/whois/scanners/progress_spec.rb` covers each truncated
21
+ prefix and the regex-shaped IANA keys under a deadline.
22
+
23
+ #### Release 0.4.9 (2026-10-25)
24
+
25
+ - ADDED: Added `registrar`, `registrant_contacts` and `technical_contacts` to
26
+ the `whois.aeda.net.ae` (`.ae`) parser. The registrar comes from the
27
+ `Registrar ID` and `Registrar Name` lines. Each contact comes from the
28
+ `<Role> Contact ID`, `Name` and `Organisation` lines. The registry publishes
29
+ no contact email over port 43; the `Contact Email` line of the legacy layout
30
+ only pointed to the web WHOIS, so it is not read. `admin_contacts` is not
31
+ supported because responses carry no admin contact. No-data responses yield
32
+ no registrar or contact, and blacklisted responses raise
33
+ `Whois::ResponseIsThrottled`.
34
+ - EVIDENCE: On 2026-10-24 a single lookup of `google.ae` on
35
+ `whois.aeda.net.ae` (TCP/43) returned the registrar, registrant and technical
36
+ contact lines in the new `current_contacts_registered.txt` fixture
37
+ (unmodified, CRLF preserved; it holds organisation and role data only, no
38
+ personal data).
39
+ - FIXED: The `whois.registry.qa` (`.qa`, `.com.qa` and the other `.qa`
40
+ second-level zones) parser raised `Whois::ParserError` on every current
41
+ response. The registry now opens each reply with the instructions of its web
42
+ lookup form and closes it with a copyright, terms of use and query limit
43
+ notice; the parser skips both. Current records also renamed their lines:
44
+ `Registrar` replaces `Registrar ID` and `Registrar Name`, `Domain Status`
45
+ (an EPP code followed by its ICANN URL) replaces `Status`, and
46
+ `Registrant Name` and `Tech Name` replace the `<Role> Contact ID/Name/Email`
47
+ lines. The parser reads both layouts: `status` returns the EPP codes,
48
+ `registered?` no longer requires the legacy `Status` line, and the registrar
49
+ and contacts carry a name only, because current records publish no
50
+ registrar ID, contact ID or email.
51
+ - EVIDENCE: On 2026-10-24 single lookups on `whois.registry.qa` (TCP/43)
52
+ returned the registered `google.qa` and `ooredoo.com.qa` responses and the
53
+ `No Data Found` responses for one generated unregistered name in each zone,
54
+ stored unmodified (CRLF preserved) as the new `current_status_registered.txt`,
55
+ `current_com_qa_status_registered.txt`, `current_status_available.txt` and
56
+ `current_com_qa_status_available.txt` fixtures. The registered fixtures hold
57
+ role and organisation names only, no personal data. The 2026-09-19 probes
58
+ that received empty responses no longer reproduce.
59
+
5
60
  #### Release 0.4.8 (2026-10-24)
6
61
 
7
62
  - ADDED: Added `registrant_contacts` to the `whois.trabis.gov.tr` (`.tr`)
@@ -1,6 +1,6 @@
1
1
  module Whois
2
2
  class Parser
3
3
  # The current library version.
4
- VERSION = "0.4.8".freeze
4
+ VERSION = "0.4.10".freeze
5
5
  end
6
6
  end
@@ -117,7 +117,7 @@ module Whois
117
117
  return unless content_for_scanner =~ /#{element}:\s+(.+)\n/
118
118
 
119
119
  id = ::Regexp.last_match(1)
120
- content_for_scanner.scan(/nic-hdl:\s+#{id}\n((.+\n)+)\n/).any? ||
120
+ content_for_scanner.scan(/nic-hdl:\s+#{Regexp.escape(id)}\n((.+\n)+)\n/).any? ||
121
121
  Whois::Parser.bug!(ParserError, "Unable to parse contact block for nic-hdl: #{id}")
122
122
  values = build_hash(::Regexp.last_match(1).scan(/(.+?):\s+(.+?)\n/))
123
123
 
@@ -62,12 +62,50 @@ module Whois
62
62
  property_not_supported :expires_on
63
63
 
64
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
+
65
83
  property_supported :nameservers do
66
84
  content_for_scanner.scan(/Name Server:\s+(.+)\n/).flatten.map do |name|
67
85
  Parser::Nameserver.new(:name => name)
68
86
  end
69
87
  end
70
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
+
71
109
  end
72
110
 
73
111
  end
@@ -96,7 +96,7 @@ module Whois
96
96
  return unless content_for_scanner =~ /#{element}:\s+(.+)\n/
97
97
 
98
98
  id = ::Regexp.last_match(1)
99
- content_for_scanner.scan(/nic-hdl-br:\s+#{id}\n((.+\n)+)\n/).any? ||
99
+ content_for_scanner.scan(/nic-hdl-br:\s+#{Regexp.escape(id)}\n((.+\n)+)\n/).any? ||
100
100
  Whois.bug!(ParserError, "Unable to parse contact block for nic-hdl-br: #{id}")
101
101
  values = build_hash(::Regexp.last_match(1).scan(/(.+?):\s+(.+?)\n/))
102
102
 
@@ -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
@@ -65,7 +65,7 @@ module Whois
65
65
  return if contact_ids.empty?
66
66
 
67
67
  contact_ids.map do |contact_id|
68
- textblock = content.slice(/contact-id:\s+#{contact_id}\n((?:.+\n)+)\n/, 1)
68
+ textblock = content.slice(/contact-id:\s+#{Regexp.escape(contact_id)}\n((?:.+\n)+)\n/, 1)
69
69
 
70
70
  address = textblock.scan(/address:\s+(.+)\n/).flatten
71
71
  address = address.reject { |a| a == "n/a" }
@@ -137,7 +137,7 @@ module Whois
137
137
  return if contact_ids.empty?
138
138
 
139
139
  contact_ids.map do |contact_id|
140
- textblock = content.slice(/nic-handle:\s+#{contact_id}\n((?:.+\n)+)\n/, 1)
140
+ textblock = content.slice(/nic-handle:\s+#{Regexp.escape(contact_id)}\n((?:.+\n)+)\n/, 1)
141
141
 
142
142
  address = textblock.scan(/address:\s+(.+)\n/).flatten
143
143
  zip = nil
@@ -6,6 +6,9 @@ module Whois
6
6
  class_attribute :tokenizers
7
7
  self.tokenizers = []
8
8
 
9
+ # Consecutive tokens allowed to succeed without consuming input.
10
+ MAX_IDLE_TOKENS = 100
11
+
9
12
  def self.tokenizer(name, &block)
10
13
  define_method(name, &block)
11
14
  end
@@ -26,7 +29,21 @@ module Whois
26
29
  @ast = {}
27
30
 
28
31
  @input = StringScanner.new(content)
29
- tokenize until @input.eos?
32
+ idle = 0
33
+ until @input.eos?
34
+ position = @input.pos
35
+ tokenize
36
+ if @input.pos == position
37
+ # Some tokenizers succeed once without consuming input to change
38
+ # state (e.g. close a section) for the next one. One that keeps
39
+ # succeeding in place loops forever, e.g. a disclaimer header matched
40
+ # by lookahead in a reply truncated before its final newline.
41
+ idle += 1
42
+ unexpected_token if idle > MAX_IDLE_TOKENS
43
+ else
44
+ idle = 0
45
+ end
46
+ end
30
47
 
31
48
  @ast
32
49
  end
@@ -79,7 +79,9 @@ module Whois
79
79
  end
80
80
 
81
81
  def parse_section_pair_newlinevalue(key)
82
- if @input.scan(/^#{key}:\s*(.+)\n/)
82
+ # The key comes from the response: match it literally. As a pattern, a
83
+ # key such as "()|^" matches without consuming input and loops forever.
84
+ if @input.scan(/^#{Regexp.escape(key)}:\s*(.+)\n/)
83
85
  @input[1].strip
84
86
  end
85
87
  end
@@ -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.8
4
+ version: 0.4.10
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