whois-parser-next 0.4.6 → 0.4.8

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: 66e7767b738ae0cf7df9736dc9a6a7685f9ddf7d344506a3a4233accf905f0d2
4
- data.tar.gz: c15b010e832b1e52cebdb02fd473e8c75176a0f232c777ba385e39b17676b327
3
+ metadata.gz: dd5238487b0cc29b77e6d56f3594c009ea7712fe877fc31a41079dc1e49178be
4
+ data.tar.gz: 6f94667791431a36eb9b45ce8550143c89b91e9f08d88eafded9411722ae92fc
5
5
  SHA512:
6
- metadata.gz: 587e231a29331459c3d14a9cd655ef2fa0230b819cbbcde397be5465500fd4a8eef4b5742407f402ed203d7104a9dc235553795427ec1b2daa1d08a34a24e82e
7
- data.tar.gz: d4e01866484c46fcf88ef68889d677459ae34db5c0920dcabfc4bb2eaf390f16af8d5524436056a543154320742d51cc445dfb3553de4eb4386adc5c0ef511f1
6
+ metadata.gz: 454380ba261a261d3f13e91646c54fde3adf7141b5782580323c3511f45b13c1dde9fd5f3f29b03855a090d7d48585304d660788e5a2942f943706670ae104cf
7
+ data.tar.gz: 21b62020ab6c094190a908d3e3e926b38928589c1727266fb691743b06003662a8ee3c22a973c6904b38f37402f6596e68de25efcf2a2f2443f9079e709f2564
data/CHANGELOG.md CHANGED
@@ -2,6 +2,55 @@
2
2
 
3
3
  This project uses [Semantic Versioning 2.0.0](http://semver.org/).
4
4
 
5
+ #### Release 0.4.8 (2026-10-24)
6
+
7
+ - ADDED: Added `registrant_contacts` to the `whois.trabis.gov.tr` (`.tr`)
8
+ parser. The `** Registrant:` block yields the name, and the email when one is
9
+ published. Address, city, country, phone and fax are read only from the fully
10
+ published eight-line layout, because hidden fields ("Hidden upon user
11
+ request") shorten the block. Fully hidden blocks, no-match responses and
12
+ throttled responses yield no contact.
13
+ - EVIDENCE: Bounded 2026-10-22 observations of `whois.trabis.gov.tr` returned a
14
+ fully published registrant block, a block with only the name visible
15
+ (`google.com.tr`), a fully hidden block, and the `No match found` response.
16
+ The fully published and fully hidden fixtures are redacted: the registrant
17
+ name, address and email, the domain name and the name servers are replaced
18
+ with placeholders while the line layout and whitespace are preserved.
19
+ - FIXED: The `whois.aeda.net.ae` (`.ae`) parser no longer reports the
20
+ registry's rate-limit reply ("BLACKLISTED: You have exceeded the query limit
21
+ ...") as registered. `registered?` was the negation of `available?`, so any
22
+ reply other than an explicit absence marker counted as a registration. The
23
+ parser now includes `RegistryResponseSafety` and treats the `BLACKLISTED:`
24
+ reply as throttled, so status, availability and registration raise
25
+ `Whois::ResponseIsThrottled`. Empty and access-denied replies now raise
26
+ `Whois::ResponseIsUnavailable` instead of counting as registered.
27
+ - EVIDENCE: On 2026-10-22, after a bounded series of lookups, `whois.aeda.net.ae`
28
+ answered every query from the client network, including `google.ae` and
29
+ generated unregistered names, with the single `BLACKLISTED:` line in the new
30
+ `response_blacklisted.txt` fixture (unmodified). Earlier the same day,
31
+ registered names and generated unregistered names returned the regular
32
+ registered and `No Data Found` responses.
33
+
34
+ #### Release 0.4.7 (2026-10-19)
35
+
36
+ - ADDED: Added strict parsing for the official `.gt` and `.com.gt` web WHOIS
37
+ response. Registration requires one result heading with a single valid domain
38
+ and `ACTIVO`; absence requires one `alert alert-danger` result with the same
39
+ domain and the registry's exact `no está registrado` marker. Duplicate,
40
+ contradictory, malformed, mismatched, incomplete, denied, throttled, or
41
+ challenge responses remain non-positive. The parser ignores scripts, styles,
42
+ comments, and unrelated page content; contacts and dates are not parsed.
43
+ - EVIDENCE: The official UVG registry lookup is
44
+ [`whois.php?dn=...`](https://www.gt/sitio/whois.php?dn={domain}). Bounded
45
+ 2026-10-19 observations returned registered results for `google.gt` and
46
+ `google.com.gt`, and exact absence results for generated eight-letter names
47
+ under both suffixes. The registry's
48
+ [policy](https://www.gt/sitio/registration_policy.php?lang=en) limits lookup
49
+ to individual names rather than lists of registered names. Regression
50
+ examples use only minimal synthetic HTML. This is a web WHOIS source; no
51
+ TCP/43 or RDAP service is claimed. See the
52
+ [dated evidence report](docs/audits/guatemala-web-whois-2026-10-19.md).
53
+
5
54
  #### Release 0.4.6 (2026-10-19)
6
55
 
7
56
  - ADDED: Added `.ga` parsing for ANINF's current French WHOIS response, requiring
@@ -1,6 +1,6 @@
1
1
  module Whois
2
2
  class Parser
3
3
  # The current library version.
4
- VERSION = "0.4.6".freeze
4
+ VERSION = "0.4.8".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/
@@ -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,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cgi'
4
+ require 'strscan'
5
+ require_relative 'base'
6
+ require_relative 'registry_response_safety'
7
+
8
+ module Whois
9
+ class Parsers
10
+ # The registry's per-domain public web WHOIS, observed 2026-10-19.
11
+ # Inspect only its result heading/alert, never navigation or script text.
12
+ class WwwGt < Base
13
+ include RegistryResponseSafety
14
+
15
+ property_supported(:status) { web_result[:status] }
16
+ property_supported(:domain) { web_result[:domain] }
17
+ property_supported(:registered?) { status == :registered }
18
+ property_supported(:available?) { status == :available }
19
+
20
+ def response_unavailable?
21
+ super || content_for_scanner.match?(/<cap-widget\b|\bclass=["']g-recaptcha["']|cf-chl-/i)
22
+ end
23
+
24
+ private
25
+
26
+ def web_result
27
+ @web_result ||= classify_web_result
28
+ end
29
+
30
+ def classify_web_result
31
+ blocks = result_blocks
32
+ return { status: :unknown } unless blocks
33
+
34
+ candidates = blocks.filter_map do |kind, text|
35
+ text = CGI.unescapeHTML(text.gsub('&aacute;', 'á').gsub('&Aacute;', 'Á')).gsub(/[ \t\r\n]+/, ' ').strip
36
+ if kind == 'h3' && (match = /\A(\S+)\. ACTIVO\z/i.match(text))
37
+ [match[1].downcase, :registered]
38
+ elsif kind == 'div' && (match = /\A(\S+)\. no está registrado \.\z/i.match(text))
39
+ [match[1].downcase, :available]
40
+ end
41
+ end
42
+ return { status: :unknown } unless candidates.size == 1
43
+
44
+ name, state = candidates.first
45
+ return { status: :unknown } unless name.bytesize <= 253 &&
46
+ name.match?(/\A(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+gt\z/)
47
+
48
+ { domain: name, status: state }
49
+ end
50
+
51
+ # One forward scan, including malformed tags. Limit each tag before
52
+ # inspecting attributes and skip scripts, styles and comments entirely.
53
+ def result_blocks
54
+ input = StringScanner.new(content_for_scanner)
55
+ blocks = []
56
+ active = nil
57
+ depth = 0
58
+ text = +''
59
+ until input.eos?
60
+ chunk = input.scan_until(/</)
61
+ unless chunk
62
+ text << input.rest if active
63
+ input.terminate
64
+ break
65
+ end
66
+ text << chunk[0...-1] if active
67
+ if input.peek(3) == '!--'
68
+ return nil unless input.scan_until(/-->/)
69
+ next
70
+ end
71
+ tag = input.scan_until(/>/)
72
+ return nil unless tag && tag.bytesize <= 16_384
73
+ match = /\A[ \t]*(\/?)[ \t]*([a-z0-9]+)/i.match(tag)
74
+ next unless match
75
+ closing = match[1] == '/'
76
+ name = match[2].downcase
77
+ if !closing && %w[script style].include?(name)
78
+ return nil if active
79
+ return nil unless input.scan_until(%r{</#{name}[ \t\r\n]*>}i)
80
+ next
81
+ end
82
+ if active
83
+ depth += 1 if !closing && name == active
84
+ depth -= 1 if closing && name == active
85
+ if depth.zero?
86
+ blocks << [active, text]
87
+ active = nil
88
+ text = +''
89
+ else
90
+ text << ' '
91
+ end
92
+ elsif !closing && (name == 'h3' || (name == 'div' && alert_classes?(tag)))
93
+ active = name
94
+ depth = 1
95
+ end
96
+ end
97
+ active ? nil : blocks
98
+ end
99
+
100
+ def alert_classes?(tag)
101
+ value = tag[/\bclass[ \t]*=[ \t]*["']([^"']*)["']/i, 1]
102
+ classes = value.to_s.split
103
+ classes.include?('alert') && classes.include?('alert-danger')
104
+ end
105
+ end
106
+ end
107
+ 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.6
4
+ version: 0.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simone Carletti
@@ -753,6 +753,7 @@ files:
753
753
  - lib/whois/parsers/whois.za.org.rb
754
754
  - lib/whois/parsers/whois.zicta.zm.rb
755
755
  - lib/whois/parsers/whois1.nic.bi.rb
756
+ - lib/whois/parsers/www.gt.rb
756
757
  - lib/whois/parsers/za_central_registry.rb
757
758
  - lib/whois/safe_record.rb
758
759
  - lib/whois/scanners/base.rb