whois 0.5.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/CHANGELOG.rdoc +55 -0
  2. data/Manifest +43 -8
  3. data/README.rdoc +53 -20
  4. data/lib/whois.rb +69 -1
  5. data/lib/whois/answer.rb +126 -0
  6. data/lib/whois/answer/contact.rb +55 -0
  7. data/lib/whois/answer/parser.rb +110 -0
  8. data/lib/whois/answer/parser/README.rdoc +21 -0
  9. data/lib/whois/answer/parser/base.rb +144 -0
  10. data/lib/whois/answer/parser/blank.rb +45 -0
  11. data/lib/whois/answer/parser/whois.crsnic.net.rb +209 -0
  12. data/lib/whois/answer/parser/whois.denic.de.rb +202 -0
  13. data/lib/whois/answer/parser/whois.nic.it.rb +330 -0
  14. data/lib/whois/answer/parser/whois.publicinterestregistry.net.rb +232 -0
  15. data/lib/whois/answer/part.rb +35 -0
  16. data/lib/whois/answer/registrar.rb +42 -0
  17. data/lib/whois/answer/super_struct.rb +57 -0
  18. data/lib/whois/client.rb +6 -0
  19. data/lib/whois/definitions/tlds.rb +2 -2
  20. data/lib/whois/errors.rb +25 -8
  21. data/lib/whois/server.rb +0 -1
  22. data/lib/whois/server/adapters/afilias.rb +5 -5
  23. data/lib/whois/server/adapters/base.rb +31 -4
  24. data/lib/whois/server/adapters/formatted.rb +3 -1
  25. data/lib/whois/server/adapters/none.rb +20 -1
  26. data/lib/whois/server/adapters/pir.rb +5 -5
  27. data/lib/whois/server/adapters/standard.rb +13 -2
  28. data/lib/whois/server/adapters/verisign.rb +5 -5
  29. data/lib/whois/server/adapters/web.rb +15 -0
  30. data/lib/whois/version.rb +2 -2
  31. data/lib/whois/whois.rb +2 -2
  32. data/test/answer/parser/base_test.rb +27 -0
  33. data/test/answer/parser/blank_test.rb +19 -0
  34. data/test/answer/parser/whois_crsnic_net_test.rb +175 -0
  35. data/test/answer/parser/whois_denic_de_test.rb +209 -0
  36. data/test/answer/parser/whois_nic_it_test.rb +255 -0
  37. data/test/answer/parser/whois_publicinterestregistry_net_test.rb +210 -0
  38. data/test/answer/parser_test.rb +77 -0
  39. data/test/answer_test.rb +99 -0
  40. data/test/client_test.rb +3 -3
  41. data/test/integration_test.rb +31 -0
  42. data/test/{adapters → server/adapters}/afilias_test.rb +10 -5
  43. data/test/server/adapters/base_test.rb +15 -0
  44. data/test/server/adapters/formatted_test.rb +26 -0
  45. data/test/{adapters → server/adapters}/none_test.rb +0 -0
  46. data/test/{adapters → server/adapters}/not_implemented_test.rb +0 -0
  47. data/test/{adapters → server/adapters}/pir_test.rb +10 -5
  48. data/test/server/adapters/standard_test.rb +29 -0
  49. data/test/{adapters → server/adapters}/verisign_test.rb +10 -5
  50. data/test/{adapters → server/adapters}/web_test.rb +0 -0
  51. data/test/server_test.rb +5 -1
  52. data/test/testcases/responses/super_struct_test.rb +25 -0
  53. data/test/testcases/responses/whois.crsnic.net/available.txt +43 -0
  54. data/test/testcases/responses/whois.crsnic.net/registered.txt +57 -0
  55. data/test/testcases/responses/whois.denic.de/available.txt +30 -0
  56. data/test/testcases/responses/whois.denic.de/registered.txt +77 -0
  57. data/test/testcases/responses/whois.nic.it/available.txt +2 -0
  58. data/test/testcases/responses/{it.txt → whois.nic.it/registered.txt} +1 -0
  59. data/test/testcases/responses/whois.nic.it/status_active.txt +53 -0
  60. data/test/testcases/responses/whois.nic.it/status_available.txt +2 -0
  61. data/test/testcases/responses/whois.publicinterestregistry.net/available.txt +1 -0
  62. data/test/testcases/responses/whois.publicinterestregistry.net/registered.txt +85 -0
  63. data/test/whois_test.rb +23 -1
  64. data/utils/bm_delegation_vs_inheritance.rb +150 -0
  65. data/whois.gemspec +6 -6
  66. metadata +78 -18
  67. data/test/adapters/standard_test.rb +0 -23
@@ -0,0 +1,55 @@
1
+ #
2
+ # = Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client.
5
+ #
6
+ #
7
+ # Category:: Net
8
+ # Package:: Whois
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # License:: MIT License
11
+ #
12
+ #--
13
+ #
14
+ #++
15
+
16
+
17
+ require 'whois/answer/super_struct'
18
+
19
+
20
+ module Whois
21
+ class Answer
22
+
23
+ #
24
+ # = Contacts
25
+ #
26
+ # Holds the details of a Contact extracted from the WHOIS answer.
27
+ #
28
+ # A Contact is composed by the following attributes:
29
+ #
30
+ # <tt>:id</tt>::
31
+ # <tt>:name</tt>::
32
+ # <tt>:organization</tt>::
33
+ # <tt>:address</tt>::
34
+ # <tt>:city</tt>::
35
+ # <tt>:zip</tt>::
36
+ # <tt>:state</tt>::
37
+ # <tt>:country</tt>::
38
+ # <tt>:country_code</tt>::
39
+ # <tt>:phone</tt>::
40
+ # <tt>:fax</tt>::
41
+ # <tt>:email</tt>::
42
+ # <tt>:created_on</tt>::
43
+ # <tt>:updated_on</tt>::
44
+ #
45
+ # Be aware that every WHOIS server can return a different number of details
46
+ # or no details at all.
47
+ #
48
+ class Contact < SuperStruct.new(:id, :name, :organization,
49
+ :address, :city, :zip, :state, :country, :country_code,
50
+ :phone, :fax, :email,
51
+ :created_on, :updated_on)
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,110 @@
1
+ #
2
+ # = Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client.
5
+ #
6
+ #
7
+ # Category:: Net
8
+ # Package:: Whois
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # License:: MIT License
11
+ #
12
+ #--
13
+ #
14
+ #++
15
+
16
+
17
+ module Whois
18
+ class Answer
19
+
20
+ #
21
+ # = Parser
22
+ #
23
+ class Parser
24
+
25
+ @@registrable_methods = [
26
+ :disclaimer,
27
+ :domain, :domain_id,
28
+ :referral_whois, :referral_url,
29
+ :status, :registered?, :available?,
30
+ :created_on, :updated_on, :expires_on,
31
+ :registrar, :registrant, :admin, :technical,
32
+ :nameservers,
33
+ ]
34
+
35
+ # Returns an array containing the name of all methods
36
+ # that can be registered and should be implemented by
37
+ # server-specific parsers.
38
+ def self.registrable_methods
39
+ @@registrable_methods
40
+ end
41
+
42
+ attr_reader :answer
43
+
44
+
45
+ def initialize(answer)
46
+ @answer = answer
47
+ end
48
+
49
+ def parsers
50
+ @parsers ||= init_parsers
51
+ end
52
+
53
+
54
+ protected
55
+
56
+ def method_missing(method, *args, &block)
57
+ if Parser.registrable_methods.include?(method)
58
+ if parsers.empty?
59
+ raise ParserError, "Unable to select a parser because the answer is empty"
60
+ elsif parser = select_parser(method)
61
+ parser.send(method, *args, &block)
62
+ else
63
+ raise PropertyNotSupported, "Unable to find a parser for `#{method}'"
64
+ end
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ # Loops through all answer parts, for each parts tries to guess
71
+ # the appropriate Whois::Answer::Parser::<parser> if it exists
72
+ # and returns the final array of server-specific parsers.
73
+ def init_parsers
74
+ answer.parts.map { |part| self.class.parser_for(part) }
75
+ end
76
+
77
+ def select_parser(method)
78
+ parsers.reverse.each do |parser|
79
+ return parser if parser.method_registered?(method)
80
+ end
81
+ nil
82
+ end
83
+
84
+
85
+ def self.parser_for(part)
86
+ parser_klass(part.host).new(part)
87
+ end
88
+
89
+ def self.parser_klass(host)
90
+ file = "whois/answer/parser/#{host}"
91
+ require file
92
+
93
+ name = host_to_parser(host)
94
+ Parser.const_get(name)
95
+
96
+ rescue LoadError
97
+ require "whois/answer/parser/blank"
98
+ Parser::Blank
99
+ end
100
+
101
+ def self.host_to_parser(host)
102
+ host.to_s.
103
+ gsub(/\./, '_').
104
+ gsub(/(?:^|_)(.)/) { $1.upcase }
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,21 @@
1
+ disclaimer String
2
+
3
+ domain String
4
+ domain_id String
5
+
6
+ status Enumerator/Array
7
+ available? Boolean
8
+ registered? Boolean
9
+
10
+ created_on Time
11
+ updated_on Time
12
+ expires_on Time
13
+
14
+ registrar <Registrar :id, :name, :organization, :url, ...>
15
+
16
+
17
+ registrant <Contact :id, :name, :organization, ...>
18
+ admin <Contact :id, :name, :organization, ...>
19
+ tech <Contact :id, :name, :organization, ...>
20
+
21
+ nameservers Array
@@ -0,0 +1,144 @@
1
+ #
2
+ # = Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client.
5
+ #
6
+ #
7
+ # Category:: Net
8
+ # Package:: Whois
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # License:: MIT License
11
+ #
12
+ #--
13
+ #
14
+ #++
15
+
16
+
17
+ require 'strscan'
18
+ require 'time'
19
+ require 'whois/answer/contact'
20
+ require 'whois/answer/registrar'
21
+
22
+
23
+ module Whois
24
+ class Answer
25
+ class Parser
26
+
27
+ #
28
+ # = Base Answer Parser
29
+ #
30
+ # This class is intended to be the base abstract class for all
31
+ # server-specific parser implementations.
32
+ #
33
+ # == Available Methods
34
+ #
35
+ # The Base class is for the most part auto-generated via meta programming.
36
+ # This is the reason why RDoc can't detect and document all available methods.
37
+ #
38
+ class Base
39
+
40
+ attr_reader :part
41
+
42
+
43
+ def initialize(part)
44
+ @part = part
45
+ end
46
+
47
+ ::Whois::Answer::Parser.registrable_methods.each do |method|
48
+ define_method(method) do
49
+ raise PropertyNotImplemented, "You should overwrite this method."
50
+ end
51
+ end
52
+
53
+
54
+ # This is an internal method primaly used as a common access point
55
+ # to get the content to be parsed as a string.
56
+ #
57
+ # The main reason behind this method is because I changed the internal
58
+ # representation of the data to be parsed more than once
59
+ # and I always had to rewrite all single parsers in order to reflect these changes.
60
+ # Now, as far as the parser access the data via the content method,
61
+ # there's no need to change each single implementation in case the content source changes.
62
+ #
63
+ # That said, the only constraints about this method is to return the data to be parsed as string.
64
+ #
65
+ def content
66
+ part.response
67
+ end
68
+
69
+
70
+ @@method_registry = {}
71
+
72
+ #
73
+ # :call-seq:
74
+ # method_registry => hash
75
+ # method_registry(:key) => array
76
+ #
77
+ # Returns the <tt>@@method_registry</tt> if <tt>key</tt> is nil,
78
+ # otherwise returns the value in <tt>@@method_registry</tt> for given <tt>key</tt>.
79
+ #
80
+ # <tt>@@method_registry</tt> is always a Hash while <tt>@@method_registry[:key]</tt>
81
+ # is always an array. If <tt>@@method_registry[:key]</tt> doesn't exist, this method
82
+ # automatically initializes it to an empty array.
83
+ #
84
+ def self.method_registry(key = nil)
85
+ if key.nil?
86
+ @@method_registry
87
+ else
88
+ @@method_registry[key] ||= []
89
+ end
90
+ end
91
+
92
+ # Returns true if <tt>method</tt> is registered for current class.
93
+ #
94
+ # method_registered?(:disclaimer)
95
+ # # => false
96
+ #
97
+ # register_method(:discaimer) {}
98
+ # method_registered?(:disclaimer)
99
+ # # => true
100
+ #
101
+ def self.method_registered?(method)
102
+ method_registry(self).include?(method)
103
+ end
104
+
105
+ #
106
+ # :call-seq:
107
+ # register_method(:method) { }
108
+ # register_method(:method) { |parameter| ... }
109
+ # register_method(:method) { |parameter, ...| ... }
110
+ #
111
+ # Creates <tt>method</tt> with the content of <tt>block</tt>
112
+ # and automatically registers <tt>method</tt> for current class.
113
+ #
114
+ # register_method(:discaimer) do
115
+ # ...
116
+ # end
117
+ #
118
+ # register_method(:changed?) do |other|
119
+ # ...
120
+ # end
121
+ #
122
+ # method_registered?(:disclaimer)
123
+ # # => true
124
+ #
125
+ def self.register_method(method, &block)
126
+ method_registry(self) << method
127
+ define_method(method, &block)
128
+ end
129
+
130
+ # Instance-level version of <tt>Base.method_registered?</tt>.
131
+ def method_registered?(method)
132
+ self.class.method_registered?(method)
133
+ end
134
+
135
+ # Instance-level version of <tt>Base.register_method</tt>.
136
+ def register_method(method, &block)
137
+ self.class.register_method(method, &block)
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # = Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client.
5
+ #
6
+ #
7
+ # Category:: Net
8
+ # Package:: Whois
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # License:: MIT License
11
+ #
12
+ #--
13
+ #
14
+ #++
15
+
16
+
17
+ require 'whois/answer/parser/base'
18
+
19
+
20
+ module Whois
21
+ class Answer
22
+ class Parser
23
+
24
+ #
25
+ # = Blank parser
26
+ #
27
+ # The Blank parser isn't a real parser. It's just a fake parser
28
+ # that acts as a parser but doesn't provide any special capability.
29
+ # It doesn't register itself in the parser_registry, it doesn't scan any string,
30
+ # it only exists to be initialized in case an answer needs to create a parser
31
+ # for a whois server not supported yet.
32
+ #
33
+ class Blank < Base
34
+
35
+ ::Whois::Answer::Parser.registrable_methods.each do |method|
36
+ define_method(method) do
37
+ raise ParserNotFound, "Unable to find a parser for the server `#{part.host}'"
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,209 @@
1
+ #
2
+ # = Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client.
5
+ #
6
+ #
7
+ # Category:: Net
8
+ # Package:: Whois
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # License:: MIT License
11
+ #
12
+ #--
13
+ #
14
+ #++
15
+
16
+
17
+ require 'whois/answer/parser/base'
18
+
19
+
20
+ module Whois
21
+ class Answer
22
+ class Parser
23
+
24
+ #
25
+ # = whois.crsnic.net.rb parser
26
+ #
27
+ # Parser for the whois.crsnic.net.rb server.
28
+ #
29
+ class WhoisCrsnicNet < Base
30
+
31
+ register_method :disclaimer do
32
+ node("Disclaimer")
33
+ end
34
+
35
+
36
+ register_method :domain do
37
+ node("Domain Name") { |raw| raw.downcase }
38
+ end
39
+
40
+ register_method :domain_id do
41
+ nil
42
+ end
43
+
44
+
45
+ register_method :referral_whois do
46
+ node("Whois Server")
47
+ end
48
+
49
+ register_method :referral_url do
50
+ node("Referral URL")
51
+ end
52
+
53
+
54
+ register_method :status do
55
+ node("Status")
56
+ end
57
+
58
+ register_method :available? do
59
+ node("Registrar").nil?
60
+ end
61
+
62
+ register_method :registered? do
63
+ !available?
64
+ end
65
+
66
+
67
+ register_method :created_on do
68
+ node("Creation Date") { |raw| Time.parse(raw) }
69
+ end
70
+
71
+ register_method :updated_on do
72
+ node("Updated Date") { |raw| Time.parse(raw) }
73
+ end
74
+
75
+ register_method :expires_on do
76
+ node("Expiration Date") { |raw| Time.parse(raw) }
77
+ end
78
+
79
+
80
+ register_method :registrar do
81
+ # Return nil because when the response contains more than one registrar section
82
+ # the response can be messy. See, for instance, the Verisign response for google.com.
83
+ nil
84
+ end
85
+
86
+
87
+ protected
88
+
89
+ def ast
90
+ @ast ||= parse
91
+ end
92
+
93
+ def node(key, &block)
94
+ if block_given?
95
+ value = ast[key]
96
+ value = yield(value) unless value.nil?
97
+ value
98
+ else
99
+ ast[key]
100
+ end
101
+ end
102
+
103
+ def node?(key)
104
+ !ast[key].nil?
105
+ end
106
+
107
+ def parse
108
+ Scanner.new(content.to_s).parse
109
+ end
110
+
111
+
112
+ class Scanner
113
+
114
+ def initialize(content)
115
+ content = content.to_s.gsub("\r", "")
116
+ @input = StringScanner.new(content.to_s)
117
+ end
118
+
119
+ def parse
120
+ @ast = {}
121
+ while !@input.eos?
122
+ parse_content
123
+ end
124
+ @ast
125
+ end
126
+
127
+ private
128
+
129
+ def parse_content
130
+ trim_newline ||
131
+ parse_not_found ||
132
+ parse_disclaimer ||
133
+ parse_notice ||
134
+ parse_pair ||
135
+ trim_last_update ||
136
+ trim_fuffa ||
137
+ error("Unexpected token")
138
+ end
139
+
140
+ def trim_newline
141
+ @input.scan(/\n/)
142
+ end
143
+
144
+ def trim_last_update
145
+ @input.scan(/>>>(.*?)<<<\n/)
146
+ end
147
+
148
+ def trim_fuffa
149
+ @input.scan(/^\w(.*)\n/) ||
150
+ (@input.scan(/^\w(.*)/) and @input.eos?)
151
+ end
152
+
153
+ def parse_not_found
154
+ if @input.scan(/No match for "(.*?)"\.\n/)
155
+ @ast["Domain Name"] = @input[1].strip
156
+ end
157
+ end
158
+
159
+ # NOTE: parse_notice and parse_disclaimer are similar!
160
+ def parse_notice
161
+ if @input.match?(/NOTICE:/)
162
+ lines = []
163
+ while !@input.match?(/\n/) && @input.scan(/(.*)\n/)
164
+ lines << @input[1].strip
165
+ end
166
+ @ast["Notice"] = lines.join(" ")
167
+ else
168
+ false
169
+ end
170
+ end
171
+
172
+ def parse_disclaimer
173
+ if @input.match?(/TERMS OF USE:/)
174
+ lines = []
175
+ while !@input.match?(/\n/) && @input.scan(/(.*)\n/)
176
+ lines << @input[1].strip
177
+ end
178
+ @ast["Disclaimer"] = lines.join(" ")
179
+ else
180
+ false
181
+ end
182
+ end
183
+
184
+ def parse_pair
185
+ if @input.scan(/\s+(.*?):(.*?)\n/)
186
+ key, value = @input[1].strip, @input[2].strip
187
+ if @ast[key].nil?
188
+ @ast[key] = value
189
+ else
190
+ @ast[key].is_a?(Array) || @ast[key] = [@ast[key]]
191
+ @ast[key] << value
192
+ end
193
+ else
194
+ false
195
+ end
196
+ end
197
+
198
+ def error(message)
199
+ raise "#{message}: #{@input.peek(@input.string.length)}"
200
+ end
201
+
202
+ end
203
+
204
+
205
+ end
206
+
207
+ end
208
+ end
209
+ end