Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial support for Whois::Response and Whois parsers (references #281).

  • Loading branch information...
commit ce867f8fade8e00db0d8e185cf4c42520c6b7342 1 parent 9958aa6
@weppos authored
View
27 lib/whois.rb
@@ -18,6 +18,7 @@
require 'whois/errors'
require 'whois/client'
require 'whois/server'
+require 'whois/response'
require 'whois/whois'
@@ -26,8 +27,30 @@ module Whois
NAME = 'Whois'
GEM = 'whois'
AUTHORS = ['Simone Carletti <weppos@weppos.net>']
-
- def self.whois(qstring)
+
+
+ def self.query(qstring)
Client.new.query(qstring)
end
+
+ def self.whois(qstring)
+ query(qstring)
+ end
+
+ def self.available?(qstring)
+ query(qstring).available?
+ rescue ParserNotFound => e
+ $stderr.puts "This method is not available for this kind of object.\n" +
+ "Use Whois.query('#{qstring}') instead."
+ nil
+ end
+
+ def self.registered?(qstring)
+ query(qstring).registered?
+ rescue ParserNotFound => e
+ $stderr.puts "This method is not available for this kind of object.\n" +
+ "Use Whois.query('#{qstring}') instead."
+ nil
+ end
+
end
View
19 lib/whois/errors.rb
@@ -25,16 +25,14 @@ class Error < StandardError
class ServerError < Error
end
-
- # Definition not found
+ # Server Definition not found
# Raised when the class hasn't been able to select a valid server
# probably because definitions are outdated.
class ServerNotFound < ServerError
end
-
- # Definition found
+ # Server Definition found
class InterfaceNotSupported < ServerError
end
@@ -50,8 +48,7 @@ class NoInterfaceError < InterfaceNotSupported
class WebInterfaceError < InterfaceNotSupported
end
-
- # Known object, Definition unavailable
+ # Server Known object, Definition unavailable
# Raised when we know about a specific functionality
# but this functionality has not been implemented yet.
@@ -66,5 +63,15 @@ class ServerNotSupported < ServerError
# Raised when unknown AS numer of IP network. (\x06)
class AllocationUnknown < ServerError
end
+
+
+ # Generic Parser exception class.
+ class ParserError < Error
+ end
+
+ # Raised when the class hasn't been able to load a valid parser
+ # according to current settings.
+ class ParserNotFound < ParserError
+ end
end
View
131 lib/whois/response.rb
@@ -0,0 +1,131 @@
+#
+# = Ruby Whois
+#
+# An intelligent pure Ruby WHOIS client.
+#
+#
+# Category:: Net
+# Package:: Whois
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+require 'whois/response/parsers/base'
+
+
+module Whois
+
+ class Response
+ attr_reader :server
+ attr_reader :content
+
+ def initialize(content, server = nil)
+ @content = content
+ @server = server
+ end
+
+ def to_s
+ @content.to_s
+ end
+
+ def inspect
+ @content.inspect
+ end
+
+ # Invokes <tt>match</tt> on response <tt>@content</tt>
+ # and returns the <tt>MatchData</tt> or <tt>nil</tt>.
+ def match(pattern)
+ @content.match(pattern)
+ end
+
+ # Returns true if the <tt>object</tt> is the same object,
+ # or is a string and has the same content.
+ def ==(other)
+ (other.equal?(self)) ||
+ (other.instance_of?(String) && other == self.to_s)
+ end
+
+ # Delegates to ==.
+ def eql?(other)
+ self == other
+ end
+
+
+ # Invokes <tt>match</tt> and returns <tt>true</tt> if <tt>pattern</tt>
+ # matches <tt>@content</tt>, <tt>false</tt> otherwise.
+ def match?(pattern)
+ !@content.match(pattern).nil?
+ end
+
+ # Invokes <tt>match</tt> and returns the first useful match,
+ # nil otherwise.
+ #
+ # "My car is blue".i_m_feeling_lucky(/^My car is (\w+)$/)
+ # # => "blue"
+ # "My car is blue".i_m_feeling_lucky(/^My bike is (\w+)$/)
+ # # => nil
+ #
+ def i_m_feeling_lucky(pattern, &block)
+ if matches = match(pattern)
+ m = matches[1]
+ m = yield(m) if block_given?
+ m
+ end
+ end
+ alias :imfl :i_m_feeling_lucky
+
+
+ # Lazy-loads and returns current response parser.
+ def parser
+ @parser ||= self.class.parser_klass(@server).new(self)
+ end
+
+ protected
+
+ # Delegates all method calls to the internal parser.
+ def method_missing(method, *args, &block)
+ if Parsers::Base.allowed_methods.include?(method)
+ parser.send(method, *args, &block)
+ else
+ super
+ end
+ end
+
+
+ def self.parser_klass(server)
+ raise ParserError,
+ "Unable to select a parser. " +
+ "The server for this response is either nil or invalid." if server.nil? || server.host.nil?
+
+ file = "whois/response/parsers/#{server.host}.rb"
+ require file
+
+ name = host_to_parser(server)
+ Parsers.const_get(name)
+
+ rescue LoadError
+ raise ParserNotFound,
+ "Unable to find a parser for the server `#{server.host}'"
+ end
+
+ def self.host_to_parser(server)
+ server.host.to_s.
+ gsub(/\./, '_').
+ gsub(/(?:^|_)(.)/) { $1.upcase }
+ end
+
+ # available?
+ # registered?
+ # status
+
+ # created_on
+ # updated_on
+ # expires_on
+
+ end
+
+end
View
58 lib/whois/response/parsers/base.rb
@@ -0,0 +1,58 @@
+#
+# = Ruby Whois
+#
+# An intelligent pure Ruby WHOIS client.
+#
+#
+# Category:: Net
+# Package:: Whois
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+require 'time'
+
+
+module Whois
+ class Response
+ module Parsers
+
+ #
+ # = Base Response Parser
+ #
+ # This class is intended to be the base abstract class for all
+ # server-specific parser implementations.
+ #
+ class Base
+
+ @@allowed_methods = [
+ :registered?, :available?, :status,
+ :created_on, :updated_on, :expires_on,
+ ]
+
+ def self.allowed_methods
+ @@allowed_methods
+ end
+
+ attr_reader :response
+
+
+ def initialize(response)
+ @response = response
+ end
+
+ allowed_methods.each do |method|
+ define_method(method) do
+ raise NotImplementedError, "You should overwrite this method."
+ end
+ end
+
+ end
+
+ end
+ end
+end
View
58 lib/whois/response/parsers/whois.nic.it.rb
@@ -0,0 +1,58 @@
+#
+# = Ruby Whois
+#
+# An intelligent pure Ruby WHOIS client.
+#
+#
+# Category:: Net
+# Package:: Whois
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+require 'whois/response/parsers/base'
+
+
+module Whois
+ class Response
+ module Parsers
+
+ class WhoisNicIt < Base
+
+ def status
+ response.i_m_feeling_lucky(/^Status:\s+(.+)$/) { |m| m.downcase.to_sym }
+ end
+
+ def available?
+ response.match?(/^Status:\s+AVAILABLE$/)
+ end
+
+ def registered?
+ !available?
+ end
+
+
+ def created_on
+ return unless registered?
+ response.i_m_feeling_lucky(/^Created:\s+(.+)$/) { |m| Time.parse(m) }
+ end
+
+ def updated_on
+ return unless registered?
+ response.i_m_feeling_lucky(/^Last Update:\s+(.+)$/) { |m| Time.parse(m) }
+ end
+
+ def expires_on
+ return unless registered?
+ response.i_m_feeling_lucky(/^Expire Date:\s+(.+)$/) { |m| Time.parse(m) }
+ end
+
+ end
+
+ end
+ end
+end
View
3  lib/whois/server/adapters/base.rb
@@ -36,7 +36,8 @@ def initialize(type, allocation, host, options = {})
end
def query(qstring)
- request(qstring)
+ response = request(qstring)
+ Response.new(response, self)
end
def request(qstring)
View
4 test/adapters/afilias_test.rb
@@ -13,7 +13,7 @@ def test_query
response = "No match for DOMAIN.FOO."
expected = response
@server.expects(:ask_the_socket).with("domain.foo", "whois.afilias-grs.info", 43).returns(response)
- assert_equal expected, @server.query("domain.foo")
+ assert_equal expected, @server.query("domain.foo").to_s
end
def test_query_with_referral
@@ -22,7 +22,7 @@ def test_query_with_referral
expected = referral + "\n" + response
@server.expects(:ask_the_socket).with("domain.foo", "whois.afilias-grs.info", 43).returns(referral)
@server.expects(:ask_the_socket).with("domain.foo", "whois.belizenic.bz", 43).returns(response)
- assert_equal expected, @server.query("domain.foo")
+ assert_equal expected, @server.query("domain.foo").to_s
end
end
View
4 test/adapters/pir_test.rb
@@ -13,7 +13,7 @@ def test_query
response = "No match for DOMAIN.FOO."
expected = response
@server.expects(:ask_the_socket).with("FULL domain.foo", "whois.publicinterestregistry.net", 43).returns(response)
- assert_equal expected, @server.query("domain.foo")
+ assert_equal expected, @server.query("domain.foo").to_s
end
def test_query_with_referral
@@ -22,7 +22,7 @@ def test_query_with_referral
expected = referral + "\n" + response
@server.expects(:ask_the_socket).with("FULL domain.foo", "whois.publicinterestregistry.net", 43).returns(referral)
@server.expects(:ask_the_socket).with("domain.foo", "whois.iana.org", 43).returns(response)
- assert_equal expected, @server.query("domain.foo")
+ assert_equal expected, @server.query("domain.foo").to_s
end
end
View
10 test/adapters/standard_test.rb
@@ -10,14 +10,16 @@ def setup
end
def test_query
- @server.expects(:ask_the_socket).with("domain.foo", "whois.foo", 43)
- @server.query("domain.foo")
+ @server.expects(:ask_the_socket).with("domain.foo", "whois.foo", 43).returns("Whois Response")
+ response = @server.query("domain.foo")
+ assert_equal response, "Whois Response"
end
def test_query_with_port
@server = @klass.new(:tld, ".foo", "whois.foo", { :port => 20 })
- @server.expects(:ask_the_socket).with("domain.foo", "whois.foo", 20)
- @server.query("domain.foo")
+ @server.expects(:ask_the_socket).with("domain.foo", "whois.foo", 20).returns("Whois Response")
+ response = @server.query("domain.foo")
+ assert_equal response, "Whois Response"
end
end
View
4 test/adapters/verisign_test.rb
@@ -13,7 +13,7 @@ def test_query
response = "No match for DOMAIN.FOO."
expected = response
@server.expects(:ask_the_socket).with("=domain.foo", "whois.foo", 43).returns(response)
- assert_equal expected, @server.query("domain.foo")
+ assert_equal expected, @server.query("domain.foo").to_s
end
def test_query_with_referral
@@ -22,7 +22,7 @@ def test_query_with_referral
expected = referral + "\n" + response
@server.expects(:ask_the_socket).with("=domain.foo", "whois.foo", 43).returns(referral)
@server.expects(:ask_the_socket).with("domain.foo", "whois.tucows.com", 43).returns(response)
- assert_equal expected, @server.query("domain.foo")
+ assert_equal expected, @server.query("domain.foo").to_s
end
end
View
4 test/client_test.rb
@@ -77,8 +77,8 @@ def query(*args)
def test_query_with_domain
response = @client.query("weppos.it")
- assert_match /Domain:\s+weppos\.it/, response
- assert_match /Created:/, response
+ assert response.match?(/Domain:\s+weppos\.it/)
+ assert response.match?(/Created:/)
end
end
View
62 test/parsers/whois_nic_it_test.rb
@@ -0,0 +1,62 @@
+require 'test_helper'
+require 'whois/response/parsers/whois.nic.it.rb'
+
+class WhoisNicItTest < Test::Unit::TestCase
+
+ TESTCASE_PATH = File.expand_path(File.dirname(__FILE__) + '/../testcases/responses/it')
+
+ def setup
+ @klass = Whois::Response::Parsers::WhoisNicIt
+ @server = Whois::Server.factory(:tld, ".it", "whois.nic.it")
+ @response = Whois::Response
+ end
+
+
+ def test_available?
+ assert @klass.new(load_response('/available.txt')).available?
+ assert !@klass.new(load_response('/registered.txt')).available?
+ end
+
+ def test_registered?
+ assert @klass.new(load_response('/registered.txt')).registered?
+ assert !@klass.new(load_response('/available.txt')).registered?
+ end
+
+ def test_status
+ assert_equal :active,
+ @klass.new(load_response('/status_active.txt')).status
+ assert_equal :available,
+ @klass.new(load_response('/status_available.txt')).status
+ end
+
+
+ def test_created_on
+ assert_equal Time.parse("1999-12-10 00:00:00"),
+ @klass.new(load_response('/registered.txt')).created_on
+ assert_equal nil,
+ @klass.new(load_response('/available.txt')).created_on
+ end
+
+ def test_updated_on
+ assert_equal Time.parse("2008-11-27 16:47:22"),
+ @klass.new(load_response('/registered.txt')).updated_on
+ assert_equal nil,
+ @klass.new(load_response('/available.txt')).updated_on
+ end
+
+ def test_expires_on
+ assert_equal Time.parse("2009-11-27"),
+ @klass.new(load_response('/registered.txt')).expires_on
+ assert_equal nil,
+ @klass.new(load_response('/available.txt')).expires_on
+ end
+
+
+
+ protected
+
+ def load_response(path)
+ @response.new(File.read(TESTCASE_PATH + path), @server)
+ end
+
+end
View
85 test/response_test.rb
@@ -0,0 +1,85 @@
+require 'test_helper'
+
+class ResponseTest < Test::Unit::TestCase
+
+ def setup
+ @klass = Whois::Response
+ @server = Whois::Server.factory(:tld, ".foo", "whois.foo")
+ @content = "This is a response for domain.foo."
+ @response = @klass.new(@content, @server)
+ end
+
+ def test_initialize
+ response = @klass.new(@content)
+ assert_instance_of @klass, response
+ assert_equal @content, response.content
+ assert_equal nil, response.server
+ end
+
+ def test_initialize_with_server
+ response = @klass.new(@content, @server)
+ assert_instance_of @klass, response
+ assert_equal @content, response.content
+ assert_equal @server, response.server
+ end
+
+ def test_to_s
+ assert_equal @content, @response.to_s
+ end
+
+ def test_inspect
+ assert_equal @content.inspect, @response.inspect
+ end
+
+ def test_match
+ # Force .to_a otherwise Match will be compared as Object instance
+ # and the test will fail because they actually are different instances.
+ assert_equal @content.match(/domain\.foo/).to_a, @response.match(/domain\.foo/).to_a
+ assert_equal @content.match(/google/), @response.match(/google/)
+ end
+
+ def test_equality
+ assert_equal @response, @content
+ assert_not_equal @content, @response
+ assert @response.eql?(@content)
+ assert !@content.eql?(@response)
+ end
+
+ def test_equality_with_self
+ assert_equal @response, @response
+ assert @response.eql?(@response)
+ end
+
+
+ def test_match?
+ assert @response.match?(/domain\.foo/)
+ assert !@response.match?(/google/)
+ end
+
+ def test_i_m_feeling_lucky
+ assert_equal "domain.foo", @response.i_m_feeling_lucky(/for (.*)\.$/)
+ assert_equal nil, @response.i_m_feeling_lucky(/^invalid (.*)$/)
+ end
+
+
+ def test_parser
+ @server = Whois::Server.factory(:tld, ".it", "whois.nic.it")
+ @response = @klass.new("", @server)
+ assert_instance_of Whois::Response::Parsers::WhoisNicIt,
+ @response.parser
+ end
+
+ def test_parser_should_raise_with_missing_parser
+ @server = Whois::Server.factory(:tld, ".it", "invalid.nic.it")
+ @response = @klass.new("", @server)
+ error = assert_raise(Whois::ParserNotFound) { @response.parser }
+ assert_match /Unable to find a parser/, error.message
+ end
+
+ def test_parser_should_raise_with_missing_server
+ @response = @klass.new("")
+ error = assert_raise(Whois::ParserError) { @response.parser }
+ assert_match /Unable to select a parser/, error.message
+ end
+
+end
View
2  test/testcases/responses/it/available.txt
@@ -0,0 +1,2 @@
+Domain: foobarbaz.it
+Status: AVAILABLE
View
1  test/testcases/responses/it.txt → test/testcases/responses/it/registered.txt
@@ -50,3 +50,4 @@ Nameservers
ns4.google.com
ns2.google.com
ns3.google.com
+
View
53 test/testcases/responses/it/status_active.txt
@@ -0,0 +1,53 @@
+
+
+*********************************************************************
+* Please note that the following result could be a subgroup of *
+* the data contained in the database. *
+* *
+* Additional information can be visualized at: *
+* http://www.nic.it/cgi-bin/Whois/whois.cgi *
+*********************************************************************
+
+Domain: google.it
+Status: ACTIVE
+Created: 1999-12-10 00:00:00
+Last Update: 2008-11-27 16:47:22
+Expire Date: 2009-11-27
+
+Registrant
+ Name: Google Ireland Holdings
+ ContactID: GOOG175-ITNIC
+ Address: 30 Herbert Street
+ Dublin
+ 2
+ IE
+ IE
+ Created: 2008-11-27 16:47:22
+ Last Update: 2008-11-27 16:47:22
+
+Admin Contact
+ Name: Tsao Tu
+ ContactID: TT4277-ITNIC
+ Organization: Tu Tsao
+ Address: 30 Herbert Street
+ Dublin
+ 2
+ IE
+ IE
+ Created: 2008-11-27 16:47:22
+ Last Update: 2008-11-27 16:47:22
+
+Technical Contacts
+ Name: Technical Services
+ ContactID: TS7016-ITNIC
+
+Registrar
+ Organization: Register.it s.p.a.
+ Name: REGISTER-MNT
+
+Nameservers
+ ns1.google.com
+ ns4.google.com
+ ns2.google.com
+ ns3.google.com
+
View
2  test/testcases/responses/it/status_available.txt
@@ -0,0 +1,2 @@
+Domain: foobarbaz.it
+Status: AVAILABLE
View
24 test/whois_test.rb
@@ -2,9 +2,31 @@
class WhoisTest < Test::Unit::TestCase
+ def setup
+ @server = Whois::Server.factory(:tld, ".it", "whois.nic.it")
+ @response = Whois::Response.new("", @server)
+ end
+
+ def test_query
+ Whois::Client.any_instance.expects(:query).with("foo.com")
+ Whois.query("foo.com")
+ end
+
def test_whois
Whois::Client.any_instance.expects(:query).with("foo.com")
Whois.whois("foo.com")
end
-
+
+ def test_available_question
+ @response.expects(:available?).returns(true)
+ Whois::Client.any_instance.expects(:query).with("foo.com").returns(@response)
+ assert Whois.available?("foo.com")
+ end
+
+ def test_registered_question
+ @response.expects(:registered?).returns(true)
+ Whois::Client.any_instance.expects(:query).with("foo.com").returns(@response)
+ assert Whois.registered?("foo.com")
+ end
+
end
View
150 utils/bm_delegation_vs_inheritance.rb
@@ -0,0 +1,150 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+require 'benchmark'
+require 'delegate'
+
+class Foo < DelegateClass(String)
+ attr_reader :optional
+ def initialize(mandatory, optional = nil)
+ super(String.new(mandatory))
+ @optional = optional
+ end
+end
+
+class Bar < String
+ attr_reader :optional
+ def initialize(mandatory, optional = nil)
+ super(mandatory)
+ @optional = optional
+ end
+end
+
+class Baz
+ attr_reader :optional
+ def initialize(mandatory, optional = nil)
+ @string = mandatory.to_s
+ @optional = optional
+ end
+
+ def match(*args)
+ @string.match(*args)
+ end
+
+ def gsub(*args)
+ @string.gsub(*args)
+ end
+
+ def gsub!(*args)
+ @string.gsub!(*args)
+ end
+end
+
+class Option < Struct.new(:qui, :quo, :qua)
+ def to_s
+ "-" * 100
+ end
+end
+
+
+TIMES = 100_000
+CONTENT = <<-LOREM
+Lorem ipsum dolor sit amet, consectetur adipisicing elit,
+sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
+nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+Excepteur sint occaecat cupidatat non proident,
+sunt in culpa qui officia deserunt mollit anim id est laborum.
+LOREM
+
+Benchmark.bmbm do |x|
+ x.report("inheritance =~") do
+ TIMES.times do
+ s = Foo.new(CONTENT, Option.new)
+ s =~ /Duis/
+ end
+ end
+ x.report("delegation =~") do
+ TIMES.times do
+ s = Bar.new(CONTENT, Option.new)
+ s =~ /Duis/
+ end
+ end
+ x.report("composition =~") do
+ # TIMES.times do
+ # s = Baz.new(CONTENT, Option.new)
+ # s =~ /Duis/
+ # end
+ end
+ x.report("inheritance match") do
+ TIMES.times do
+ s = Foo.new(CONTENT, Option.new)
+ s.match(/Status:/)
+ end
+ end
+ x.report("delegation match") do
+ TIMES.times do
+ s = Bar.new(CONTENT, Option.new)
+ s.match(/Status:/)
+ end
+ end
+ x.report("composition match") do
+ TIMES.times do
+ s = Baz.new(CONTENT, Option.new)
+ s.match(/Status:/)
+ end
+ end
+ x.report("inheritance gsub") do
+ TIMES.times do
+ s = Foo.new(CONTENT, Option.new)
+ s.gsub(/ /, '_')
+ end
+ end
+ x.report("delegation gsub") do
+ TIMES.times do
+ s = Bar.new(CONTENT, Option.new)
+ s.gsub(/ /, '_')
+ end
+ end
+ x.report("composition gsub") do
+ TIMES.times do
+ s = Baz.new(CONTENT, Option.new)
+ s.gsub(/ /, '_')
+ end
+ end
+ x.report("inheritance gsub!") do
+ TIMES.times do
+ s = Foo.new(CONTENT, Option.new)
+ s.gsub!(/ /, '_')
+ end
+ end
+ x.report("delegation gsub!") do
+ TIMES.times do
+ s = Bar.new(CONTENT, Option.new)
+ s.gsub!(/ /, '_')
+ end
+ end
+ x.report("composition gsub!") do
+ TIMES.times do
+ s = Baz.new(CONTENT, Option.new)
+ s.gsub!(/ /, '_')
+ end
+ end
+ x.report("inheritance option") do
+ TIMES.times do
+ s = Foo.new(CONTENT, Option.new)
+ s.optional.to_s
+ end
+ end
+ x.report("delegation option") do
+ TIMES.times do
+ s = Bar.new(CONTENT, Option.new)
+ s.optional.to_s
+ end
+ end
+ x.report("composition option") do
+ TIMES.times do
+ s = Baz.new(CONTENT, Option.new)
+ s.optional.to_s
+ end
+ end
+end

0 comments on commit ce867f8

Please sign in to comment.
Something went wrong with that request. Please try again.