Module: RDAP
- Defined in:
- lib/rdap.rb
Defined Under Namespace
Classes: EmptyResponse, Error, InvalidResponse, NotFound, SSLError, ServerError, TooManyRequests
Constant Summary collapse
- VERSION =
"1.0.0"- BOOTSTRAP =
"https://rdap.org/"- TYPES =
[:domain, :ip, :autnum].freeze
- HEADERS =
{ "User-Agent" => "RDAP ruby gem (#{VERSION})", "Accept" => "application/rdap+json, application/json, */*;q=0.8", # Had to include other types here because rdap.nic.fr returns an error with only rdap+json -_- }
Class Method Summary collapse
- .as(name, server: nil, **opts) ⇒ Object
-
.asn_index ⇒ Object
- AS range, RDAP server
-
pairs, built once so the ranges are parsed a single time rather than on every lookup.
-
.bootstrap(registry) ⇒ Object
Service entries from a bundled IANA bootstrap file (data.iana.org/rdap/).
-
.dns_index ⇒ Object
TLD => RDAP server, built once for O(1) domain lookups.
- .domain(name, server: nil, **opts) ⇒ Object
- .get_follow_redirects(uri, timeout: 5, headers: {}, redirection_limit: 5) ⇒ Object
- .ip(name, server: nil, **opts) ⇒ Object
-
.ip_index(registry) ⇒ Object
- network, RDAP server
-
pairs for a registry, built once so the CIDRs are parsed into IPAddr objects a single time rather than on every lookup.
- .query(name, type:, timeout: 5, server: nil, headers: {}) ⇒ Object
Class Method Details
.as(name, server: nil, **opts) ⇒ Object
35 36 37 38 39 40 |
# File 'lib/rdap.rb', line 35 def self.as name, server: nil, **opts asn = name.to_i if name.is_a?(Integer) || name.to_s =~ /\A\d+\z/ raise ArgumentError.new("RDAP: Invalid AS number: #{name.inspect}") unless asn server ||= asn_index.find { |range, _| range.cover?(asn) }&.last query name, type: :autnum, server: server, **opts end |
.asn_index ⇒ Object
- AS range, RDAP server
-
pairs, built once so the ranges are parsed a single
time rather than on every lookup.
71 72 73 74 75 |
# File 'lib/rdap.rb', line 71 def self.asn_index @asn_index ||= bootstrap('asn').flat_map do |ranges, urls| ranges.map { |range| low, high = range.split('-'); [(low.to_i..(high || low).to_i), urls.first] } end end |
.bootstrap(registry) ⇒ Object
Service entries from a bundled IANA bootstrap file (data.iana.org/rdap/).
78 79 80 |
# File 'lib/rdap.rb', line 78 def self.bootstrap registry JSON.parse(File.read(File.("../data/#{registry}.json", __dir__)))["services"] end |
.dns_index ⇒ Object
TLD => RDAP server, built once for O(1) domain lookups. IANA lists the preferred (https) endpoint first, so urls.first is the server to use.
55 56 57 58 59 |
# File 'lib/rdap.rb', line 55 def self.dns_index @dns_index ||= bootstrap('dns').each_with_object({}) do |(labels, urls), index| labels.each { |label| index[label.downcase] = urls.first } end end |
.domain(name, server: nil, **opts) ⇒ Object
23 24 25 26 27 |
# File 'lib/rdap.rb', line 23 def self.domain name, server: nil, **opts tld = name.to_s.chomp('.').split('.').last server ||= dns_index[tld.downcase] if tld query name, type: :domain, server: server, **opts end |
.get_follow_redirects(uri, timeout: 5, headers: {}, redirection_limit: 5) ⇒ Object
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/rdap.rb', line 82 def self.get_follow_redirects uri, timeout: 5, headers: {}, redirection_limit: 5 raise ServerError.new("Too many redirections (> #{redirection_limit}) at #{uri}") if redirection_limit == 0 http = Net::HTTP.new(uri.host, uri.port) http.open_timeout = timeout http.read_timeout = timeout http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER response = http.get(uri.path, HEADERS.merge(headers)) case response when Net::HTTPSuccess if !response.body raise EmptyResponse.new("[#{response.code}] #{response.}") end document = JSON.parse(response.body) if document["errorCode"] raise ServerError.new("[#{document["errorCode"]}] #{document["title"]} (#{uri})") end document when Net::HTTPNotFound # 404 sometimes return details in the JSON body so we threat them later if response.body.size > 0 and (document = JSON.parse(response.body) rescue nil) raise NotFound.new("[#{document["errorCode"]}] #{document["title"]}") else raise NotFound.new("[#{response.code}] #{response.}") end when Net::HTTPTooManyRequests raise TooManyRequests.new("[#{response.code}] #{response.}") when Net::HTTPRedirection get_follow_redirects(URI(response["location"]), timeout: timeout, headers: headers, redirection_limit: redirection_limit - 1) else raise ServerError.new("[#{response.code}] #{response.}") end rescue OpenSSL::SSL::SSLError => e raise SSLError.new("#{e.} (#{uri.host})") rescue JSON::ParserError => e raise InvalidResponse.new("JSON parser error: #{e.}") end |
.ip(name, server: nil, **opts) ⇒ Object
29 30 31 32 33 |
# File 'lib/rdap.rb', line 29 def self.ip name, server: nil, **opts ip = IPAddr.new(name.to_s) server ||= ip_index(ip.ipv6? ? 'ipv6' : 'ipv4').find { |net, _| net.include?(ip) }&.last query name, type: :ip, server: server, **opts end |
.ip_index(registry) ⇒ Object
- network, RDAP server
-
pairs for a registry, built once so the CIDRs are
parsed into IPAddr objects a single time rather than on every lookup.
63 64 65 66 67 |
# File 'lib/rdap.rb', line 63 def self.ip_index registry (@ip_index ||= {})[registry] ||= bootstrap(registry).flat_map do |cidrs, urls| cidrs.map { |cidr| [IPAddr.new(cidr), urls.first] } end end |
.query(name, type:, timeout: 5, server: nil, headers: {}) ⇒ Object
42 43 44 45 46 47 48 49 |
# File 'lib/rdap.rb', line 42 def self.query name, type:, timeout: 5, server: nil, headers: {} TYPES.include?(type) or raise ArgumentError.new("RDAP: Invalid query type: #{type}, supported types: #{TYPES}") # .domain/.ip/.as resolve the authoritative server from the bundled IANA # files; fall back to the public bootstrap server for anything not covered. server ||= BOOTSTRAP uri = URI("#{server.chomp('/')}/#{type}/#{name}") get_follow_redirects(uri, timeout: timeout, headers: headers) end |