# # = Ruby Whois # # An intelligent pure Ruby WHOIS client and parser. # # # Category:: Net # Package:: Whois # Author:: Simone Carletti <weppos@weppos.net> # License:: MIT License # #-- # #++ require 'whois/answer/parser/base' module Whois class Answer class Parser # # = whois.publicinterestregistry.net parser # # Parser for the whois.publicinterestregistry.net server. # class WhoisPublicinterestregistryNet < Base include Ast property_supported :disclaimer do @disclaimer ||= node("disclaimer") end property_supported :domain do @domain ||= node("Domain Name") { |value| value.downcase } end property_supported :domain_id do @domain_id ||= node("Domain ID") end property_supported :status do @status ||= node("Status") end property_supported :available? do @available ||= node("Domain ID").nil? end property_supported :registered? do @registered ||= !available? end property_supported :created_on do @created_on ||= node("Created On") { |value| Time.parse(value) } end property_supported :updated_on do @updated_on ||= node("Last Updated On") { |value| Time.parse(value) } end property_supported :expires_on do @expires_on ||= node("Expiration Date") { |value| Time.parse(value) } end property_supported :registrar do @registrar ||= node("Sponsoring Registrar") do |registrar| id, name = if registrar =~ /(.*?)\((.*?)\)/ [$2.strip, $1.strip] else [nil, registrar] end Whois::Answer::Registrar.new( :id => id, :name => name ) end end property_supported :registrant_contact do @registrant_contact ||= contact("Registrant", Whois::Answer::Contact::TYPE_REGISTRANT) end property_supported :admin_contact do @admin_contact ||= contact("Admin", Whois::Answer::Contact::TYPE_ADMIN) end property_supported :technical_contact do @technical_contact ||= contact("Tech", Whois::Answer::Contact::TYPE_TECHNICAL) end property_supported :nameservers do @nameservers ||= node("Name Server") { |server| server.reject(&:empty?).map(&:downcase) } @nameservers ||= [] end # NEWPROPERTY property_supported :changed? do |other| !unchanged?(other) end # NEWPROPERTY property_supported :unchanged? do |other| self == other || self.content.to_s == other.content.to_s end # NEWPROPERTY def throttle? !!node("status-throttle") end protected def parse Scanner.new(content_for_scanner).parse end def contact(element, type) node("#{element} ID") do |registrant_id| Whois::Answer::Contact.new( :id => registrant_id, :type => type, :name => node("#{element} Name"), :organization => node("#{element} Organization"), :address => [node("#{element} Street1"), node("#{element} Street2"), node("#{element} Street3")].reject { |value| value.to_s.empty? }.join(" "), :city => node("#{element} City"), :zip => node("#{element} Postal Code"), :state => node("#{element} State/Province"), :country_code => node("#{element} Country"), :phone => node("#{element} Phone"), :fax => node("#{element} FAX"), :email => node("#{element} Email") ) end end class Scanner def initialize(content) @input = StringScanner.new(content) end def parse @ast = {} while !@input.eos? parse_content end @ast end private def parse_content parse_not_found || parse_throttle || parse_disclaimer || parse_pair || trim_empty_line || error("Unexpected token") end def trim_empty_line @input.skip(/^\n/) end def error(message) if @input.eos? raise "Unexpected end of input." else raise "#{message}: `#{@input.peek(@input.string.length)}'" end end def parse_not_found @input.skip(/^NOT FOUND\n/) end def parse_throttle if @input.match?(/^WHOIS LIMIT EXCEEDED/) @ast["status-throttle"] = true @input.skip(/^.+\n/) end end def parse_disclaimer if @input.match?(/^NOTICE:/) lines = [] while !@input.match?(/\n/) && @input.scan(/(.*)\n/) lines << @input[1].strip end @ast["disclaimer"] = lines.join(" ") else false end end def parse_pair if @input.scan(/(.+?):(.*?)\n/) key, value = @input[1].strip, @input[2].strip if @ast[key].nil? @ast[key] = value else @ast[key].is_a?(Array) || @ast[key] = [@ast[key]] @ast[key] << value end else false end end end end end end end