PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/whois/server.rb

https://gitlab.com/dandrews/whois
Ruby | 332 lines | 131 code | 36 blank | 165 comment | 24 complexity | 46cae04bd8cfe0ef357fb883a75e97e2 MD5 | raw file
  1. #--
  2. # Ruby Whois
  3. #
  4. # An intelligent pure Ruby WHOIS client and parser.
  5. #
  6. # Copyright (c) 2009-2014 Simone Carletti <weppos@weppos.net>
  7. #++
  8. require 'ipaddr'
  9. require 'json'
  10. require 'whois/server/adapters/base'
  11. module Whois
  12. # The {Whois::Server} class has two important roles:
  13. #
  14. # 1. it acts as a database for the WHOIS server definitions
  15. # 2. it is responsible for selecting the right adapter used to handle the query to the WHOIS server(s).
  16. #
  17. class Server
  18. # The {Whois::Server::Adapters} module is a namespace for all
  19. # WHOIS server adapters. Each adapter is a subclass of {Whois::Server::Adapters::Base},
  20. # customized to handle WHOIS queries for a type or a group of servers.
  21. module Adapters
  22. autoload :Base, "whois/server/adapters/base"
  23. autoload :Arin, "whois/server/adapters/arin"
  24. autoload :Arpa, "whois/server/adapters/arpa"
  25. autoload :Afilias, "whois/server/adapters/afilias"
  26. autoload :Formatted, "whois/server/adapters/formatted"
  27. autoload :None, "whois/server/adapters/none"
  28. autoload :NotImplemented, "whois/server/adapters/not_implemented"
  29. autoload :Standard, "whois/server/adapters/standard"
  30. autoload :Verisign, "whois/server/adapters/verisign"
  31. autoload :Web, "whois/server/adapters/web"
  32. end
  33. # The WHOIS server definitions.
  34. #
  35. # @return [{ Symbol => Array }] The definition Hash.
  36. # @private
  37. @@definitions = {}
  38. # Searches the +/definitions+ folder for definition files and loads them.
  39. # This method is automatically invoked when this file is parsed
  40. # by the Ruby interpreter (scroll down to the bottom of this file).
  41. #
  42. # @return [void]
  43. def self.load_definitions
  44. Dir[File.expand_path("../../../data/*.json", __FILE__)].each { |f| load_json(f) }
  45. end
  46. # Loads the definitions from a JSON file.
  47. #
  48. # @param [String] file The path to the definition file.
  49. #
  50. # @return [void]
  51. def self.load_json(file)
  52. type = File.basename(file, File.extname(file)).to_sym
  53. JSON.load(File.read(file)).each do |allocation, settings|
  54. define(type, allocation, settings.delete("host"), Hash[settings.map { |k,v| [k.to_sym, v] }])
  55. end
  56. end
  57. # Lookup and returns the definition list for given <tt>type</tt>,
  58. # or all definitions if <tt>type</tt> is <tt>nil</tt>.
  59. #
  60. # @param [Symbol] type The type of WHOIS server to lookup.
  61. # Known values are :tld, :ipv4, :ipv6.
  62. #
  63. # @return [{ Symbol => Array }]
  64. # The definition Hash if +type+ is +nil+.
  65. # @return [Array<Hash>]
  66. # The definitions for given +type+ if +type+ is not +nil+ and +type+ exists.
  67. # @return [nil]
  68. # The definitions for given +type+ if +type+ is not +nil+ and +type+ doesn't exist.
  69. #
  70. # @example Return the definition database.
  71. #
  72. # Whois::Server.definitions
  73. # # => { :tld => [...], :ipv4 => [], ... }
  74. #
  75. # @example Return the definitions for given key.
  76. #
  77. # Whois::Server.definitions(:tld)
  78. # # => [...]
  79. #
  80. # Whois::Server.definitions(:invalid)
  81. # # => nil
  82. #
  83. def self.definitions(type = nil)
  84. if type.nil?
  85. @@definitions
  86. else
  87. @@definitions[type]
  88. end
  89. end
  90. # Defines a new server for <tt>:type</tt> queries.
  91. #
  92. # @param [Symbol] type
  93. # The type of WHOIS server to define.
  94. # Known values are :tld, :ipv4, :ipv6.
  95. # @param [String] allocation
  96. # The allocation, range or hostname, this server is responsible for.
  97. # @param [String, nil] host
  98. # The server hostname. Use nil if unknown or not available.
  99. # @param [Hash] options Optional definition properties.
  100. # @option options [Class] :adapter (Whois::Server::Adapters::Standard)
  101. # This option has a special meaning and determines the adapter Class to use.
  102. # Defaults to {Whois::Server::Adapters::Standard} unless specified.
  103. # All the other options are passed directly to the adapter which can decide how to use them.
  104. #
  105. # @return [void]
  106. #
  107. # @example
  108. #
  109. # # Define a server for the .it extension
  110. # Whois::Server.define :tld, ".it", "whois.nic.it"
  111. #
  112. # # Define a new server for an range of IPv4 addresses
  113. # Whois::Server.define :ipv4, "61.192.0.0/12", "whois.nic.ad.jp"
  114. #
  115. # # Define a new server for an range of IPv6 addresses
  116. # Whois::Server.define :ipv6, "2001:2000::/19", "whois.ripe.net"
  117. #
  118. # # Define a new server with a custom adapter
  119. # Whois::Server.define :tld, ".test", nil,
  120. # :adapter => Whois::Server::Adapter::None
  121. #
  122. # # Define a new server with a custom adapter and options
  123. # Whois::Server.define :tld, ".ar", nil,
  124. # :adapter => Whois::Server::Adapters::Web,
  125. # :url => "http://www.nic.ar/"
  126. #
  127. def self.define(type, allocation, host, options = {})
  128. @@definitions[type] ||= []
  129. @@definitions[type] << [allocation, host, options]
  130. end
  131. # Creates a new server adapter from given arguments
  132. # and returns the server instance.
  133. #
  134. # By default, returns a new {Whois::Server::Adapters::Standard} instance.
  135. # You can customize the behavior passing a custom adapter class
  136. # as <tt>:adapter</tt> option.
  137. #
  138. # Whois::Server.factory :tld, ".it", "whois.nic.it"
  139. # # => #<Whois::Servers::Adapter::Standard>
  140. #
  141. # Whois::Server.factory :tld, ".it", "whois.nic.it",
  142. # :option => Whois::Servers::Adapter::Custom
  143. # # => #<Whois::Servers::Adapter::Custom>
  144. #
  145. # Please note that any adapter is responsible for a limited set
  146. # of queries, which should be included in the range of the <tt>allocation</tt> parameter.
  147. # Use {Whois::Server.guess} if you are not sure which adapter
  148. # is the right one for a specific string.
  149. #
  150. # @param [Symbol] type
  151. # The type of WHOIS server to define.
  152. # Known values are :tld, :ipv4, :ipv6.
  153. # @param [String] allocation
  154. # The allocation, range or hostname, this server is responsible for.
  155. # @param [String, nil] host
  156. # The server hostname. Use nil if unknown or not available.
  157. # @param [Hash] options Optional definition properties.
  158. # @option options [Class] :adapter (Whois::Server::Adapters::Standard)
  159. # This option has a special meaning and determines the adapter Class to use.
  160. # Defaults to {Whois::Server::Adapters::Standard} unless specified.
  161. # All the other options are passed directly to the adapter which can decide how to use them.
  162. #
  163. # @return [Whois::Server::Adapters::Standard]
  164. # An adapter that can be used to perform queries.
  165. #
  166. def self.factory(type, allocation, host, options = {})
  167. options = options.dup
  168. adapter = options.delete(:adapter) || Adapters::Standard
  169. adapter = Adapters.const_get(camelize(adapter)) unless adapter.respond_to?(:new)
  170. adapter.new(type, allocation, host, options)
  171. end
  172. # Parses <tt>string</tt> and tries to guess the right server.
  173. #
  174. # It successfully detects the following query types:
  175. # * ipv6
  176. # * ipv4
  177. # * top level domains (e.g. .com, .net, .it)
  178. # * domain names (e.g. google.com, google.net, google.it)
  179. # * emails
  180. #
  181. # Note that not all query types actually have a corresponding adapter.
  182. # For instance, the following request will result in a
  183. # {Whois::ServerNotSupported} exception.
  184. #
  185. # Whois::Server.guess "mail@example.com"
  186. #
  187. #
  188. # @param [String] string
  189. # @return [Whois::Server::Adapters::Base]
  190. # The adapter that can be used to perform
  191. # WHOIS queries for <tt>string</tt>.
  192. #
  193. # @raise [Whois::ServerNotFound]
  194. # When unable to find an appropriate WHOIS adapter
  195. # for <tt>string</tt>. Most of the cases, the <tt>string</tt>
  196. # haven't been recognised as one of the supported query types.
  197. # @raise [Whois::ServerNotSupported]
  198. # When the <tt>string</tt> type is detected,
  199. # but the object type doesn't have any supported WHOIS adapter associated.
  200. #
  201. def self.guess(string)
  202. # Top Level Domain match
  203. if matches_tld?(string)
  204. return factory(:tld, ".", "whois.iana.org")
  205. end
  206. # IP address (secure match)
  207. if matches_ip?(string)
  208. return find_for_ip(string)
  209. end
  210. # Email Address (secure match)
  211. if matches_email?(string)
  212. return find_for_email(string)
  213. end
  214. # Domain Name match
  215. if server = find_for_domain(string)
  216. return server
  217. end
  218. # ASN match
  219. if matches_asn?(string)
  220. return find_for_asn(string)
  221. end
  222. # Game Over
  223. raise ServerNotFound, "Unable to find a WHOIS server for `#{string}'"
  224. end
  225. private
  226. def self.camelize(string)
  227. string.to_s.split("_").collect(&:capitalize).join
  228. end
  229. def self.matches_tld?(string)
  230. string =~ /^\.(xn--)?[a-z0-9]+$/
  231. end
  232. def self.matches_ip?(string)
  233. valid_ipv4?(string) || valid_ipv6?(string)
  234. end
  235. def self.matches_email?(string)
  236. string =~ /@/
  237. end
  238. def self.matches_asn?(string)
  239. string =~ /^as\d+$/i
  240. end
  241. def self.find_for_ip(string)
  242. begin
  243. ip = IPAddr.new(string)
  244. type = ip.ipv4? ? :ipv4 : :ipv6
  245. definitions(type).each do |definition|
  246. if IPAddr.new(definition.first).include?(ip)
  247. return factory(type, *definition)
  248. end
  249. end
  250. rescue ArgumentError
  251. # continue
  252. end
  253. raise AllocationUnknown, "IP Allocation for `#{string}' unknown. Server definitions might be outdated."
  254. end
  255. def self.find_for_email(string)
  256. raise ServerNotSupported, "No WHOIS server is known for email objects"
  257. end
  258. def self.find_for_domain(string)
  259. definitions(:tld).each do |definition|
  260. return factory(:tld, *definition) if /#{Regexp.escape(definition.first)}$/ =~ string
  261. end
  262. nil
  263. end
  264. def self.find_for_asn(string)
  265. asn = string[/\d+/].to_i
  266. asn_type = asn <= 65535 ? :asn16 : :asn32
  267. definitions(asn_type).each do |definition|
  268. if (range = definition.first.split.map(&:to_i)) && asn >= range.first && asn <= range.last
  269. return factory(asn_type, *definition)
  270. end
  271. end
  272. raise AllocationUnknown, "Unknown AS number - `#{asn}'."
  273. end
  274. def self.valid_ipv4?(addr)
  275. if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ addr
  276. return $~.captures.all? {|i| i.to_i < 256}
  277. end
  278. false
  279. end
  280. def self.valid_ipv6?(addr)
  281. # IPv6 (normal)
  282. return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ addr
  283. return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
  284. return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ addr
  285. # IPv6 (IPv4 compat)
  286. return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ addr && valid_ipv4?($')
  287. return true if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
  288. return true if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ addr && valid_ipv4?($')
  289. false
  290. end
  291. end
  292. end
  293. Whois::Server.load_definitions