PageRenderTime 65ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/whois/server/adapters/base.rb

https://github.com/jeffbozek/whois
Ruby | 191 lines | 87 code | 28 blank | 76 comment | 11 complexity | e120cc4b4a45ff44db09e2a7aaa2a797 MD5 | raw file
  1. #--
  2. # Ruby Whois
  3. #
  4. # An intelligent pure Ruby WHOIS client and parser.
  5. #
  6. # Copyright (c) 2009-2011 Simone Carletti <weppos@weppos.net>
  7. #++
  8. require 'whois/record/part'
  9. require 'whois/record'
  10. require 'socket'
  11. module Whois
  12. class Server
  13. module Adapters
  14. class Base
  15. # Default WHOIS request port.
  16. DEFAULT_WHOIS_PORT = 43
  17. # Default bind hostname.
  18. DEFAULT_BIND_HOST = "0.0.0.0"
  19. # Array of connection errors to rescue and wrap into a {Whois::ConnectionError}
  20. RESCUABLE_CONNECTION_ERRORS = [
  21. Errno::ECONNRESET,
  22. Errno::EHOSTUNREACH,
  23. Errno::ECONNREFUSED,
  24. SocketError,
  25. ]
  26. # @return [Symbol] The type of WHOIS server
  27. attr_reader :type
  28. # @return [String] The allocation this server is responsible for.
  29. attr_reader :allocation
  30. # @return [String, nil] The server hostname.
  31. attr_reader :host
  32. # @return [Hash] Optional adapter properties.
  33. attr_reader :options
  34. # Temporary internal response buffer.
  35. #
  36. # @api internal
  37. # @return [Array]
  38. attr_reader :buffer
  39. # @param [Symbol] type
  40. # The type of WHOIS adapter to define.
  41. # Known values are :tld, :ipv4, :ipv6.
  42. # @param [String] allocation
  43. # The allocation, range or hostname, this server is responsible for.
  44. # @param [String, nil] host
  45. # The server hostname. Use nil if unknown or not available.
  46. # @param [Hash] options Optional adapter properties.
  47. #
  48. def initialize(type, allocation, host, options = {})
  49. @type = type
  50. @allocation = allocation
  51. @host = host
  52. @options = options || {}
  53. end
  54. # Checks self and other for equality.
  55. #
  56. # @param [The Whois::Server::Adapters::Base] other
  57. #
  58. # @return [Boolean] Returns true if the other is the same object,
  59. # or <tt>other</tt> attributes matches this object attributes.
  60. def ==(other)
  61. (
  62. self.equal?(other)
  63. ) || (
  64. other.is_a?(self.class) &&
  65. self.type == other.type &&
  66. self.allocation == other.allocation &&
  67. self.host == other.host &&
  68. self.options == other.options
  69. )
  70. end
  71. alias_method :eql?, :==
  72. # Merges given +settings+ into current {#options}.
  73. #
  74. # @param [Hash] settings
  75. # @return [Hash] The updated options for this object.
  76. def configure(settings)
  77. options.merge!(settings)
  78. end
  79. # Performs a Whois query for <tt>string</tt>
  80. # using the current server adapter.
  81. #
  82. # @param [String] string The string to be sent as query parameter.
  83. #
  84. # @return [Whois::Record]
  85. #
  86. # Internally, this method calls {#request}
  87. # using the Template Method design pattern.
  88. #
  89. # server.query("google.com")
  90. # # => Whois::Record
  91. #
  92. def query(string)
  93. buffer_start do |buffer|
  94. request(string)
  95. Whois::Record.new(self, buffer)
  96. end
  97. end
  98. # Performs the real WHOIS request.
  99. #
  100. # This method is not implemented in {Whois::Server::Adapters::Base} class,
  101. # it is intended to be overwritten in the concrete subclasses.
  102. # This is the heart of the Template Method design pattern.
  103. #
  104. # @param [String] string The string to be sent as query parameter.
  105. #
  106. # @raise [NotImplementedError]
  107. # @return [void]
  108. # @abstract
  109. def request(string)
  110. raise NotImplementedError
  111. end
  112. private
  113. # Store a record part in {#buffer}.
  114. #
  115. # @param [String] body
  116. # @param [String] host
  117. # @return [void]
  118. #
  119. # @api public
  120. def buffer_append(body, host)
  121. @buffer << Whois::Record::Part.new(body, host)
  122. end
  123. # @api internal
  124. def buffer_start
  125. @buffer = []
  126. result = yield(@buffer)
  127. @buffer = [] # reset
  128. result
  129. end
  130. # @api public
  131. def query_the_socket(query, host, port = nil)
  132. ask_the_socket(
  133. query,
  134. host,
  135. port || options[:port] || DEFAULT_WHOIS_PORT,
  136. options[:bind_host] || DEFAULT_BIND_HOST,
  137. options[:bind_port]
  138. )
  139. rescue *RESCUABLE_CONNECTION_ERRORS => error
  140. if error.instance_of? Errno::ECONNRESET
  141. raise IncompleteResponse, "#{error.class}: #{error.message}" unless options[:allow_incomplete_responses]
  142. else
  143. raise ConnectionError, "#{error.class}: #{error.message}"
  144. end
  145. end
  146. # This method handles the lowest connection
  147. # to the WHOIS server.
  148. #
  149. # This is for internal use only!
  150. #
  151. # @api internal
  152. def ask_the_socket(query, host, port, local_host, local_port)
  153. args = [host, port, local_host, local_port].compact
  154. client = TCPSocket.open(host, port, local_host, local_port)
  155. client.write("#{query}\r\n") # I could use put(foo) and forget the \n
  156. client.read # but write/read is more symmetric than puts/read
  157. ensure # and I really want to use read instead of gets.
  158. client.close if client # If != client something went wrong.
  159. end
  160. end
  161. end
  162. end
  163. end