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,202 @@
1
+ #
2
+ # = Ruby Whois
3
+ #
4
+ # An intelligent pure Ruby WHOIS client.
5
+ #
6
+ #
7
+ # Category:: Net
8
+ # Package:: Whois
9
+ # Author:: Aaron Mueller <mail@aaron-mueller.de>
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
+ class WhoisDenicDe < Base
24
+
25
+ register_method :disclaimer do
26
+ ast['Disclaimer']
27
+ end
28
+
29
+ register_method :domain do
30
+ ast['Domain']
31
+ end
32
+
33
+ register_method :domain_id do
34
+ nil
35
+ end
36
+
37
+ register_method :status do
38
+ ast['Status']
39
+ end
40
+
41
+ register_method :registered? do
42
+ !ast['NotFound']
43
+ end
44
+
45
+ register_method :available? do
46
+ ast['NotFound']
47
+ end
48
+
49
+ register_method :created_on do
50
+ nil
51
+ end
52
+
53
+ register_method :expires_on do
54
+ nil
55
+ end
56
+
57
+ register_method :updated_on do
58
+ ast['Changed'] ? Time.parse(ast['Changed']) : nil
59
+ end
60
+
61
+ register_method :registrar do
62
+ return nil unless ast['Zone-C']
63
+ Answer::Registrar.new(
64
+ :id => nil,
65
+ :name => ast['Zone-C'].name,
66
+ :organization => ast['Zone-C'].organization,
67
+ :url => nil
68
+ )
69
+ end
70
+
71
+ register_method :registrant do
72
+ ast['Holder']
73
+ end
74
+
75
+ register_method :admin do
76
+ ast['Admin-C']
77
+ end
78
+
79
+ register_method :technical do
80
+ ast['Tech-C']
81
+ end
82
+
83
+ register_method :nameservers do
84
+ ast['Nserver']
85
+ end
86
+
87
+
88
+ protected
89
+
90
+ def ast
91
+ @ast ||= parse
92
+ end
93
+
94
+ def parse
95
+ Scanner.new(content.to_s).parse
96
+ end
97
+
98
+
99
+ class Scanner
100
+
101
+ def initialize(content)
102
+ @input = StringScanner.new(content.to_s)
103
+ end
104
+
105
+ def parse
106
+ @ast = {}
107
+ while !@input.eos?
108
+ trim_newline ||
109
+ parse_content
110
+ end
111
+ @ast
112
+ end
113
+
114
+ private
115
+
116
+ def trim_newline
117
+ @input.scan(/\n/)
118
+ end
119
+
120
+ def parse_content
121
+ parse_disclaimer ||
122
+ parse_not_found ||
123
+ parse_pair(@ast) ||
124
+ parse_contact ||
125
+ trim_newline ||
126
+ error('Unexpected token')
127
+ end
128
+
129
+ def parse_disclaimer
130
+ if @input.match?(/% Copyright \(c\)2008 by DENIC\n/)
131
+ 8.times { @input.scan(/%(.*)\n/) } # strip junk
132
+ lines = []
133
+ while @input.match?(/%/) && @input.scan(/%(.*)\n/)
134
+ lines << @input[1].strip unless @input[1].strip == ""
135
+ end
136
+ @ast['Disclaimer'] = lines.join(" ")
137
+ true
138
+ end
139
+ false
140
+ end
141
+
142
+ def parse_pair(node)
143
+ if @input.scan(/([^ \[]*):(.*)\n/)
144
+ key, value = @input[1].strip, @input[2].strip
145
+ if node[key].nil?
146
+ node[key] = value
147
+ else
148
+ node[key].is_a?(Array) || node[key] = [node[key]]
149
+ node[key] << value
150
+ end
151
+ true
152
+ else
153
+ false
154
+ end
155
+ end
156
+
157
+ def parse_contact
158
+ if @input.scan(/\[(.*)\]\n/)
159
+ contact_name = @input[1]
160
+ contact = {}
161
+ while parse_pair(contact)
162
+ end
163
+ @ast[contact_name] = Answer::Contact.new(
164
+ :id => nil,
165
+ :name => contact['Name'],
166
+ :organization => contact['Organisation'],
167
+ :address => contact['Address'],
168
+ :city => contact['City'],
169
+ :zip => contact['Pcode'],
170
+ :state => nil,
171
+ :country => nil,
172
+ :country_code => contact['Country'],
173
+ :phone => contact['Phone'],
174
+ :fax => contact['Fax'],
175
+ :email => contact['Email'],
176
+ :created_on => nil,
177
+ :updated_on => contact['Changed']
178
+ )
179
+ true
180
+ else
181
+ false
182
+ end
183
+ end
184
+
185
+ def parse_not_found
186
+ if @input.match?(/% Object "(.*)" not found in database\n/)
187
+ 6.times { @input.scan(/%(.*)\n/) } # strip junk
188
+ return @ast['NotFound'] = true
189
+ end
190
+ @ast['NotFound'] = false
191
+ end
192
+
193
+ def error(message)
194
+ raise "#{message}: #{@input.peek(@input.string.length)}"
195
+ end
196
+
197
+ end
198
+
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,330 @@
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.nic.it parser
26
+ #
27
+ # Parser for the whois.nic.it server.
28
+ #
29
+ class WhoisNicIt < Base
30
+
31
+ # Returns the registry disclaimer that comes with the answer.
32
+ register_method :disclaimer do
33
+ node("Disclaimer")
34
+ end
35
+
36
+
37
+ # If available, returns the domain name as stored by the registry.
38
+ register_method :domain do
39
+ node("Domain") { |raw| raw.downcase }
40
+ end
41
+
42
+ # If available, returns the unique domain ID set by the registry.
43
+ register_method :domain_id do
44
+ nil
45
+ end
46
+
47
+
48
+ # Returns the record status or an array of status,
49
+ # in case the registry supports it.
50
+ register_method :status do
51
+ node("Status") { |raw| raw.downcase.to_sym }
52
+ end
53
+
54
+ # Returns whether this record is available.
55
+ register_method :available? do
56
+ node("Status") == "AVAILABLE"
57
+ end
58
+
59
+ # Returns whether this record is registered.
60
+ register_method :registered? do
61
+ !available?
62
+ end
63
+
64
+
65
+ # If available, returns a Time object representing the date
66
+ # the record was created, according to the registry answer.
67
+ register_method :created_on do
68
+ node("Created") { |raw| Time.parse(raw) }
69
+ end
70
+
71
+ # If available, returns a Time object representing the date
72
+ # the record was last updated, according to the registry answer.
73
+ register_method :updated_on do
74
+ node("Last Update") { |raw| Time.parse(raw) }
75
+ end
76
+
77
+ # If available, returns a Time object representing the date
78
+ # the record is set to expire, according to the registry answer.
79
+ register_method :expires_on do
80
+ node("Expire Date") { |raw| Time.parse(raw) }
81
+ end
82
+
83
+
84
+ # If available, returns a <tt>Whois::Answer::Registrar</tt> record
85
+ # containing the registrar details extracted from the registry answer.
86
+ register_method :registrar do
87
+ node("Registrar") do |raw|
88
+ Answer::Registrar.new(
89
+ :id => raw["Name"],
90
+ :name => raw["Name"],
91
+ :organization => raw["Organization"]
92
+ )
93
+ end
94
+ end
95
+
96
+ # If available, returns a <tt>Whois::Answer::Contact</tt> record
97
+ # containing the registrant details extracted from the registry answer.
98
+ register_method :registrant do
99
+ contact("Registrant")
100
+ end
101
+
102
+ # If available, returns a <tt>Whois::Answer::Contact</tt> record
103
+ # containing the admin contact details extracted from the registry answer.
104
+ register_method :admin do
105
+ contact("Admin Contact")
106
+ end
107
+
108
+ # If available, returns a <tt>Whois::Answer::Contact</tt> record
109
+ # containing the technical contact details extracted from the registry answer.
110
+ register_method :technical do
111
+ contact("Technical Contacts")
112
+ end
113
+
114
+
115
+ # If available, returns an array of name servers entries for this domain
116
+ # if any name server is available in the registry answer.
117
+ # Each name server is a lower case string.
118
+ #
119
+ # ==== Examples
120
+ #
121
+ # nameserver
122
+ # # => nil
123
+ # nameserver
124
+ # # => ["ns2.google.com", "ns1.google.com", "ns3.google.com"]
125
+ #
126
+ register_method :nameservers do
127
+ node("Nameservers")
128
+ end
129
+
130
+
131
+ # Returns whether this answer changed compared to <tt>other</tt>.
132
+ #
133
+ # Comparing the Answer contents is not always as trivial as it seems.
134
+ # Whois servers sometimes inject dynamic method into the whois answer such as
135
+ # the timestamp the request was generated.
136
+ # This causes two answers to be different even if they actually should be considered equal
137
+ # because the registry data didn't change.
138
+ #
139
+ # This method should provide a bulletproof way to detect whether this answer
140
+ # changed if compared with <tt>other</tt>.
141
+ register_method :changed? do |other|
142
+ !unchanged?(other)
143
+ end
144
+
145
+ # The opposite of <tt>changed?</tt>.
146
+ register_method :unchanged? do |other|
147
+ self == other ||
148
+ self.content.to_s == other.content.to_s
149
+ # (self == other) ||
150
+ # (domain == other.domain &&
151
+ # created_on == other.created_on &&
152
+ # updated_on == other.updated_on &&
153
+ # expires_on == other.expires_on )
154
+ end
155
+
156
+
157
+ protected
158
+
159
+ def contact(element)
160
+ node(element) do |raw|
161
+ address = (raw["Address"] || "").split("\n")
162
+ Answer::Contact.new(
163
+ :id => raw["ContactID"],
164
+ :name => raw["Name"],
165
+ :organization => raw["Organization"],
166
+ :address => address[0],
167
+ :city => address[1],
168
+ :country_code => address[3],
169
+ :created_on => raw["Created"] ? Time.parse(raw["Created"]) : nil,
170
+ :updated_on => raw["Last Update"] ? Time.parse(raw["Created"]) : nil
171
+ )
172
+ end
173
+ end
174
+
175
+
176
+ def ast
177
+ @ast ||= parse
178
+ end
179
+
180
+ def node(key, &block)
181
+ if block_given?
182
+ value = ast[key]
183
+ value = yield(value) unless value.nil?
184
+ value
185
+ else
186
+ ast[key]
187
+ end
188
+ end
189
+
190
+ def node?(key)
191
+ !ast[key].nil?
192
+ end
193
+
194
+ def parse
195
+ Scanner.new(content.to_s).parse
196
+ end
197
+
198
+
199
+ class Scanner
200
+
201
+ def initialize(content)
202
+ @input = StringScanner.new(content.to_s)
203
+ end
204
+
205
+ def parse
206
+ @ast = {}
207
+ while !@input.eos?
208
+ trim_newline ||
209
+ parse_content
210
+ end
211
+ @ast
212
+ end
213
+
214
+ private
215
+
216
+ def parse_content
217
+ parse_disclaimer ||
218
+ parse_pair ||
219
+ parse_section ||
220
+ error("Unexpected token")
221
+ end
222
+
223
+ def trim_newline
224
+ @input.scan(/\n/)
225
+ end
226
+
227
+ def parse_pair
228
+ if @input.scan(/(.*?):(.*?)\n/)
229
+ key, value = @input[1].strip, @input[2].strip
230
+ @ast[key] = value
231
+ else
232
+ false
233
+ end
234
+ end
235
+
236
+ def parse_disclaimer
237
+ if @input.match?(/\*(.*?)\*\n/)
238
+ disclaimer = []
239
+ while @input.scan(/\*(.*?)\*\n/)
240
+ matched = @input[1].strip
241
+ disclaimer << matched if matched =~ /\w+/
242
+ end
243
+ @ast["Disclaimer"] = disclaimer.join(" ")
244
+ else
245
+ false
246
+ end
247
+ end
248
+
249
+ def parse_section
250
+ if @input.scan(/([^:]*?)\n/)
251
+ section = @input[1].strip
252
+ content = parse_section_pairs ||
253
+ parse_section_items
254
+ @input.match?(/\n+/) || error("Unexpected end of section")
255
+ @ast[section] = content
256
+ else
257
+ false
258
+ end
259
+ end
260
+
261
+ def parse_section_items
262
+ if @input.match?(/(\s+)([^:]*?)\n/)
263
+ items = []
264
+ indentation = @input[1].length
265
+ while item = parse_section_items_item(indentation)
266
+ items << item
267
+ end
268
+ items
269
+ else
270
+ false
271
+ end
272
+ end
273
+
274
+ def parse_section_items_item(indentation)
275
+ if @input.scan(/\s{#{indentation}}(.*)\n/)
276
+ @input[1]
277
+ else
278
+ false
279
+ end
280
+ end
281
+
282
+ def parse_section_pairs
283
+ contents = {}
284
+ while content = parse_section_pair
285
+ contents.merge!(content)
286
+ end
287
+ if !contents.empty?
288
+ contents
289
+ else
290
+ false
291
+ end
292
+ end
293
+
294
+ def parse_section_pair
295
+ if @input.scan(/(\s+)(.*?):(\s+)(.*?)\n/)
296
+ key = @input[2].strip
297
+ values = [@input[4].strip]
298
+ indentation = @input[1].length + @input[2].length + 1 + @input[3].length
299
+ while value = parse_section_pair_newlinevalue(indentation)
300
+ values << value
301
+ end
302
+ { key => values.join("\n") }
303
+ else
304
+ false
305
+ end
306
+ end
307
+
308
+ def parse_section_pair_newlinevalue(indentation)
309
+ if @input.scan(/\s{#{indentation}}(.*)\n/)
310
+ @input[1]
311
+ else
312
+ false
313
+ end
314
+ end
315
+
316
+ def error(message)
317
+ if @input.eos?
318
+ raise "Unexpected end of input."
319
+ else
320
+ raise "#{message}: #{@input.peek(@input.string.length)}"
321
+ end
322
+ end
323
+
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+ end
330
+ end