epp-client 0.0.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.simplecov +16 -0
  4. data/.travis.yml +11 -0
  5. data/.yardopts +4 -0
  6. data/Gemfile +20 -0
  7. data/Gemfile.lock +51 -0
  8. data/LICENSE +1 -1
  9. data/README.md +75 -0
  10. data/Rakefile +2 -37
  11. data/epp-client.gemspec +15 -58
  12. data/gemfiles/Gemfile.ruby18 +13 -0
  13. data/lib/epp-client.rb +94 -7
  14. data/lib/epp-client/client.rb +117 -20
  15. data/lib/epp-client/commands/check.rb +11 -0
  16. data/lib/epp-client/commands/command.rb +24 -0
  17. data/lib/epp-client/commands/create.rb +11 -0
  18. data/lib/epp-client/commands/delete.rb +11 -0
  19. data/lib/epp-client/commands/info.rb +11 -0
  20. data/lib/epp-client/commands/login.rb +40 -0
  21. data/lib/epp-client/commands/logout.rb +11 -0
  22. data/lib/epp-client/commands/poll.rb +28 -0
  23. data/lib/epp-client/commands/read_write_command.rb +18 -0
  24. data/lib/epp-client/commands/renew.rb +11 -0
  25. data/lib/epp-client/commands/transfer.rb +25 -0
  26. data/lib/epp-client/commands/update.rb +11 -0
  27. data/lib/epp-client/contact/check.rb +23 -0
  28. data/lib/epp-client/contact/check_response.rb +22 -0
  29. data/lib/epp-client/contact/command.rb +87 -0
  30. data/lib/epp-client/contact/create.rb +34 -0
  31. data/lib/epp-client/contact/create_response.rb +14 -0
  32. data/lib/epp-client/contact/delete.rb +21 -0
  33. data/lib/epp-client/contact/delete_response.rb +9 -0
  34. data/lib/epp-client/contact/info.rb +21 -0
  35. data/lib/epp-client/contact/info_response.rb +74 -0
  36. data/lib/epp-client/contact/response.rb +34 -0
  37. data/lib/epp-client/contact/transfer.rb +21 -0
  38. data/lib/epp-client/contact/transfer_response.rb +26 -0
  39. data/lib/epp-client/contact/update.rb +80 -0
  40. data/lib/epp-client/contact/update_response.rb +9 -0
  41. data/lib/epp-client/domain/check.rb +23 -0
  42. data/lib/epp-client/domain/check_response.rb +22 -0
  43. data/lib/epp-client/domain/command.rb +92 -0
  44. data/lib/epp-client/domain/create.rb +49 -0
  45. data/lib/epp-client/domain/create_response.rb +17 -0
  46. data/lib/epp-client/domain/delete.rb +21 -0
  47. data/lib/epp-client/domain/delete_response.rb +9 -0
  48. data/lib/epp-client/domain/info.rb +21 -0
  49. data/lib/epp-client/domain/info_response.rb +66 -0
  50. data/lib/epp-client/domain/renew.rb +34 -0
  51. data/lib/epp-client/domain/renew_response.rb +14 -0
  52. data/lib/epp-client/domain/response.rb +34 -0
  53. data/lib/epp-client/domain/transfer.rb +27 -0
  54. data/lib/epp-client/domain/transfer_response.rb +29 -0
  55. data/lib/epp-client/domain/update.rb +81 -0
  56. data/lib/epp-client/domain/update_response.rb +9 -0
  57. data/lib/epp-client/host/check.rb +23 -0
  58. data/lib/epp-client/host/check_response.rb +22 -0
  59. data/lib/epp-client/host/command.rb +47 -0
  60. data/lib/epp-client/host/create.rb +24 -0
  61. data/lib/epp-client/host/create_response.rb +14 -0
  62. data/lib/epp-client/host/delete.rb +21 -0
  63. data/lib/epp-client/host/delete_response.rb +9 -0
  64. data/lib/epp-client/host/info.rb +21 -0
  65. data/lib/epp-client/host/info_response.rb +42 -0
  66. data/lib/epp-client/host/response.rb +34 -0
  67. data/lib/epp-client/host/update.rb +76 -0
  68. data/lib/epp-client/host/update_response.rb +9 -0
  69. data/lib/epp-client/request.rb +29 -74
  70. data/lib/epp-client/requests/abstract.rb +30 -0
  71. data/lib/epp-client/requests/command.rb +28 -0
  72. data/lib/epp-client/requests/extension.rb +28 -0
  73. data/lib/epp-client/requests/hello.rb +12 -0
  74. data/lib/epp-client/response.rb +45 -8
  75. data/lib/epp-client/response_helper.rb +25 -0
  76. data/lib/epp-client/server.rb +167 -63
  77. data/lib/epp-client/testing.rb +59 -0
  78. data/lib/epp-client/version.rb +3 -0
  79. data/lib/epp-client/xml_helper.rb +71 -0
  80. data/test/commands/test_check_command.rb +33 -0
  81. data/test/commands/test_create_command.rb +53 -0
  82. data/test/commands/test_delete_command.rb +28 -0
  83. data/test/commands/test_info_command.rb +28 -0
  84. data/test/commands/test_login_command.rb +56 -0
  85. data/test/commands/test_logout_command.rb +22 -0
  86. data/test/commands/test_poll_command.rb +54 -0
  87. data/test/commands/test_renew_command.rb +39 -0
  88. data/test/commands/test_transfer_command.rb +37 -0
  89. data/test/commands/test_update_command.rb +60 -0
  90. data/test/contact/test_contact_check.rb +33 -0
  91. data/test/contact/test_contact_check_response.rb +38 -0
  92. data/test/contact/test_contact_create.rb +70 -0
  93. data/test/contact/test_contact_create_response.rb +33 -0
  94. data/test/contact/test_contact_delete.rb +28 -0
  95. data/test/contact/test_contact_delete_response.rb +23 -0
  96. data/test/contact/test_contact_info.rb +28 -0
  97. data/test/contact/test_contact_info_response.rb +100 -0
  98. data/test/contact/test_contact_transfer.rb +28 -0
  99. data/test/contact/test_contact_transfer_response.rb +100 -0
  100. data/test/contact/test_contact_update.rb +83 -0
  101. data/test/contact/test_contact_update_response.rb +23 -0
  102. data/test/domain/test_domain_check.rb +33 -0
  103. data/test/domain/test_domain_check_response.rb +38 -0
  104. data/test/domain/test_domain_create.rb +53 -0
  105. data/test/domain/test_domain_create_response.rb +39 -0
  106. data/test/domain/test_domain_delete.rb +28 -0
  107. data/test/domain/test_domain_delete_response.rb +23 -0
  108. data/test/domain/test_domain_info.rb +28 -0
  109. data/test/domain/test_domain_info_response.rb +107 -0
  110. data/test/domain/test_domain_renew.rb +39 -0
  111. data/test/domain/test_domain_renew_response.rb +32 -0
  112. data/test/domain/test_domain_transfer.rb +37 -0
  113. data/test/domain/test_domain_transfer_response.rb +112 -0
  114. data/test/domain/test_domain_update.rb +73 -0
  115. data/test/domain/test_domain_update_response.rb +23 -0
  116. data/test/fixtures/responses/contact/check.xml +27 -0
  117. data/test/fixtures/responses/contact/create.xml +19 -0
  118. data/test/fixtures/responses/contact/delete.xml +12 -0
  119. data/test/fixtures/responses/contact/info.xml +49 -0
  120. data/test/fixtures/responses/contact/transfer-query.xml +23 -0
  121. data/test/fixtures/responses/contact/transfer-request.xml +23 -0
  122. data/test/fixtures/responses/contact/update.xml +12 -0
  123. data/test/fixtures/responses/domain/check.xml +27 -0
  124. data/test/fixtures/responses/domain/create.xml +20 -0
  125. data/test/fixtures/responses/domain/delete.xml +12 -0
  126. data/test/fixtures/responses/domain/info-no-exDate.xml +38 -0
  127. data/test/fixtures/responses/domain/info.xml +39 -0
  128. data/test/fixtures/responses/domain/renew.xml +19 -0
  129. data/test/fixtures/responses/domain/transfer-query.xml +24 -0
  130. data/test/fixtures/responses/domain/transfer-request.xml +24 -0
  131. data/test/fixtures/responses/domain/update.xml +12 -0
  132. data/test/fixtures/responses/greeting.xml +25 -0
  133. data/test/fixtures/responses/host/check.xml +27 -0
  134. data/test/fixtures/responses/host/create.xml +19 -0
  135. data/test/fixtures/responses/host/delete.xml +12 -0
  136. data/test/fixtures/responses/host/info.xml +30 -0
  137. data/test/fixtures/responses/host/update.xml +12 -0
  138. data/test/helper.rb +89 -0
  139. data/test/host/test_host_check.rb +33 -0
  140. data/test/host/test_host_check_response.rb +38 -0
  141. data/test/host/test_host_create.rb +37 -0
  142. data/test/host/test_host_create_response.rb +33 -0
  143. data/test/host/test_host_delete.rb +28 -0
  144. data/test/host/test_host_delete_response.rb +23 -0
  145. data/test/host/test_host_info.rb +28 -0
  146. data/test/host/test_host_info_response.rb +72 -0
  147. data/test/host/test_host_update.rb +68 -0
  148. data/test/host/test_host_update_response.rb +23 -0
  149. data/test/requests/test_command_request.rb +16 -0
  150. data/test/requests/test_extension_request.rb +55 -0
  151. data/test/requests/test_hello_request.rb +15 -0
  152. data/test/support/schemas/all.xsd +21 -0
  153. data/test/support/schemas/contact-1.0.xsd +387 -0
  154. data/test/support/schemas/domain-1.0.xsd +432 -0
  155. data/test/support/schemas/epp-1.0.xsd +403 -0
  156. data/test/support/schemas/eppcom-1.0.xsd +93 -0
  157. data/test/support/schemas/host-1.0.xsd +240 -0
  158. data/test/test_client.rb +54 -0
  159. data/test/test_request.rb +15 -0
  160. data/test/test_server.rb +57 -0
  161. metadata +270 -91
  162. data/.document +0 -5
  163. data/README.rdoc +0 -17
  164. data/VERSION +0 -1
  165. data/lib/epp-client/hello_request.rb +0 -9
  166. data/test/test_epp-client.rb +0 -7
@@ -0,0 +1,9 @@
1
+ require File.expand_path('../response', __FILE__)
2
+
3
+ module EPP
4
+ module Host
5
+ class UpdateResponse < Response
6
+
7
+ end
8
+ end
9
+ end
@@ -1,96 +1,51 @@
1
1
  module EPP
2
2
  # An EPP XML Request
3
3
  class Request
4
- # Create new instance of EPP::Request.
5
- #
6
- # @overload initialize(command, payload, transaction_id)
7
- # @param [String, #to_s] command EPP Command to call
8
- # @param [XML::Node, XML::Document, String] payload XML Payload to transmit
9
- # @param [String] transaction_id EPP Transaction ID
10
- # @overload initialize(command, transaction_id) {|xml| payload }
11
- # @param [String, #to_s] command EPP Command to call
12
- # @param [String] transaction_id EPP Transaction ID
13
- # @yield [xml] block to construct payload
14
- # @yieldparam [XML::Node] xml XML Node of the command
15
- # for the payload to be added into
16
- def initialize(command, *args, &block)
17
- @command = XML::Node.new(command)
18
-
19
- cmd = XML::Node.new('command')
20
- cmd << @command
21
- xml.root << cmd
4
+ include XMLHelpers
22
5
 
23
- if block_given?
24
- tid, _ = args
25
- case block.arity
26
- when 1
27
- block.call(@command)
28
- else
29
- @command << block.call
30
- end
31
- else
32
- payload, tid = args
33
- unless payload.nil?
34
- @command << case payload.class
35
- when XML::Node
36
- payload
37
- when XML::Document
38
- xml.import(payload.root)
39
- else
40
- doc = XML::Parser.string(payload.to_s).parse
41
- xml.import(doc.root)
42
- end
43
- end
44
- end
45
-
46
- unless command == 'logout'
47
- cmd << XML::Node.new('clTRID', tid || 'ABC-12345')
48
- end
6
+ # Create and return a new EPP Request Payload
7
+ #
8
+ # @param [EPP::Request]
9
+ def initialize(request)
10
+ @request = request
49
11
  end
50
12
 
51
- # Name of the receivers command
52
- # @return [String] command name
53
- def command
54
- @command.name
13
+ def namespaces
14
+ @namespaces
55
15
  end
56
16
 
57
17
  # Receiver in XML form
58
18
  # @return [XML::Document] XML of the receiver
59
19
  def to_xml
60
- xml
20
+ doc = XML::Document.new('1.0')
21
+ doc.root = XML::Node.new('epp')
22
+ root = doc.root
23
+
24
+ epp_ns = XML::Namespace.new(root, nil, 'urn:ietf:params:xml:ns:epp-1.0')
25
+ root.namespaces.namespace = epp_ns
26
+
27
+ xsi_ns = XML::Namespace.new(root, 'xsi', 'http://www.w3.org/2001/XMLSchema-instance')
28
+ xsi_sL = XML::Attr.new(root, 'schemaLocation', 'urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd')
29
+ xsi_sL.namespaces.namespace = xsi_ns
30
+
31
+ @namespaces = {'epp' => epp_ns, 'xsi' => xsi_ns}
32
+
33
+ @request.set_namespaces(@namespaces) if @request.respond_to?(:set_namespaces)
34
+ root << @request.to_xml
35
+
36
+ doc
61
37
  end
62
38
 
63
39
  # Convert the receiver to a string
64
40
  #
65
41
  # @param [Hash] opts Formatting options, passed to the XML::Document
66
42
  def to_s(opts = {})
67
- xml.to_s({:indent => false}.merge(opts))
43
+ to_xml.to_s({:indent => false}.merge(opts))
68
44
  end
69
45
 
70
46
  # @see Object#inspect
71
- def inspect
72
- xml.inspect
73
- end
74
-
75
- private
76
- # Request XML Payload
77
- # @see prepare_request
78
- def xml
79
- @xml ||= prepare_request
80
- end
81
-
82
- # Prepares the base XML for the request
83
- #
84
- # @return [XML::Document]
85
- def prepare_request
86
- xml = XML::Document.new('1.0')
87
- xml.root = XML::Node.new('epp')
88
- xml.root.namespaces.namespace =
89
- XML::Namespace.new(xml.root, nil, 'urn:ietf:params:xml:ns:epp-1.0')
90
- XML::Namespace.new(xml.root, 'xsi', 'http://www.w3.org/2001/XMLSchema-instance')
91
- xml.root['xsi:schemaLocation'] = "urn:ietf:params:xml:ns:epp-1.0 epp-1.0.xsd"
92
-
93
- xml
94
- end
47
+ # def inspect
48
+ # "#<#{self.class}>"
49
+ # end
95
50
  end
96
51
  end
@@ -0,0 +1,30 @@
1
+ module EPP
2
+ module Requests
3
+ # @note Abstract class, do not instanciate manually
4
+ class Abstract
5
+ include XMLHelpers
6
+ attr_reader :namespaces
7
+
8
+ def set_namespaces(namespaces)
9
+ @namespaces = namespaces
10
+ end
11
+
12
+ def name
13
+ raise NotImplementedError, "#name must be overriden by subclasses"
14
+ end
15
+
16
+ # Receiver in XML form
17
+ # @return [XML::Document] XML of the receiver
18
+ def to_xml
19
+ epp_node(name, @namespaces || {})
20
+ end
21
+
22
+ # Convert the receiver to a string
23
+ #
24
+ # @param [Hash] opts Formatting options, passed to the XML::Document
25
+ def to_s(opts = {})
26
+ to_xml.to_s({:indent => false}.merge(opts))
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../abstract', __FILE__)
2
+
3
+ module EPP
4
+ module Requests
5
+ class Command < Abstract
6
+ def initialize(tid, command, extension = nil)
7
+ @tid, @command, @extension = tid, command, extension
8
+ end
9
+
10
+ def name
11
+ 'command'
12
+ end
13
+
14
+ def to_xml
15
+ node = super
16
+
17
+ @command.set_namespaces(@namespaces) if @command.respond_to?(:set_namespaces)
18
+ @extension.set_namespaces(@namespaces) if @extension && @extension.respond_to?(:set_namespaces)
19
+
20
+ node << as_xml(@command)
21
+ node << as_xml(@extension) if @extension
22
+ node << epp_node('clTRID', @tid, @namespaces)
23
+
24
+ node
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require File.expand_path('../abstract', __FILE__)
2
+
3
+ module EPP
4
+ module Requests
5
+ class Extension < Abstract
6
+ def initialize(*extensions)
7
+ raise ArgumentError, "you must provide at least one extension" if extensions.compact.empty?
8
+ @extensions = extensions.compact
9
+ end
10
+
11
+ def name
12
+ 'extension'
13
+ end
14
+
15
+ def to_xml
16
+ node = super
17
+
18
+ Array(@extensions).each do |extension|
19
+ next if extension.nil?
20
+ extension.set_namespaces(@namespaces) if extension.respond_to?(:set_namespaces)
21
+ node << as_xml(extension)
22
+ end
23
+
24
+ node
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../abstract', __FILE__)
2
+
3
+ module EPP
4
+ module Requests
5
+ # An EPP Hello Request
6
+ class Hello < Abstract
7
+ def name
8
+ 'hello'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -15,6 +15,10 @@ module EPP
15
15
  code == 1000
16
16
  end
17
17
 
18
+ def pending?
19
+ code == 1001
20
+ end
21
+
18
22
  # Response code
19
23
  # @return [Integer] response code
20
24
  def code
@@ -27,14 +31,30 @@ module EPP
27
31
  @message ||= result.find('e:msg/text()').first.content.strip
28
32
  end
29
33
 
34
+ # Descriptive Error Information
35
+ # @return [XML::Node] error information
36
+ def error_value
37
+ @error_value ||= result.find('e:extValue/e:value/node()').first
38
+ end
39
+
40
+ # Error reason
41
+ # @return [String] error reason
42
+ def error_reason
43
+ unless defined?(@error_reason)
44
+ reason = result.find('e:extValue/e:reason/text()').first
45
+ @error_reason = reason && reason.content.strip
46
+ end
47
+
48
+ @error_reason
49
+ end
50
+
30
51
  # Response data
31
- # @return [String] response data
52
+ # @return [XML::Node, Array<XML::Node>] response data
32
53
  def data
33
- unless @data
34
- list = @xml.find('/e:epp/e:response/e:resData/node()').reject{|n| n.empty?}
35
- @data = list.size > 1 ? list : list[0]
54
+ @data ||= begin
55
+ list = @xml.find('/e:epp/e:response/e:resData/node()').reject { |n| n.empty? }
56
+ list.size > 1 ? list : list[0]
36
57
  end
37
- @data
38
58
  end
39
59
 
40
60
  # Response Message Queue
@@ -43,15 +63,32 @@ module EPP
43
63
  @msgQ ||= @xml.find('/e:epp/e:response/e:msgQ').first
44
64
  end
45
65
 
66
+ # Response extension block
67
+ # @return [XML::Node, Array<XML::Node>] extension
68
+ def extension
69
+ @extension ||= begin
70
+ list = @xml.find('/e:epp/e:response/e:extension/node()').reject { |n| n.empty? }
71
+ list.size > 1 ? list : list[0]
72
+ end
73
+ end
74
+
46
75
  # @see Object#inspect
47
76
  def inspect
48
77
  @xml.inspect
49
78
  end
50
79
 
51
- # Returns a the formatted response XML
52
- # @return [String] formatted XML response
80
+ # Returns the XML response document
81
+ # @return [XML::Document] XML response document
53
82
  def to_xml
54
- @xml.to_s(:indent => true)
83
+ @xml
84
+ end
85
+
86
+ # Convert the receiver to a string
87
+ #
88
+ # @param [Hash] opts Formatting options, passed to the XML::Document
89
+ # @return [String] formatted XML response
90
+ def to_s(opts = {})
91
+ @xml.to_s({:indent => false}.merge(opts))
55
92
  end
56
93
 
57
94
  private
@@ -0,0 +1,25 @@
1
+ module EPP
2
+ module ResponseHelper
3
+ def value_for_xpath(xpath, base = nil, &block)
4
+ values_for_xpath(xpath, base, &block).first
5
+ end
6
+ def values_for_xpath(xpath, base = nil)
7
+ nodes_for_xpath(xpath, base).map do |node|
8
+ if block_given?
9
+ yield node
10
+ else
11
+ case node
12
+ when XML::Node
13
+ node.content.strip
14
+ when XML::Attr
15
+ node.value
16
+ end
17
+ end
18
+ end
19
+ end
20
+ def nodes_for_xpath(xpath, base = nil)
21
+ base ||= @response.data
22
+ base.find(xpath, namespaces)
23
+ end
24
+ end
25
+ end
@@ -1,18 +1,47 @@
1
1
  module EPP
2
2
  # A server error
3
- class ServerError < RuntimeError; end
3
+ class ServerError < Error; end
4
+
5
+ # A connection error
6
+ class ConnectionError < Error
7
+ attr_reader :error, :addr, :peeraddr
8
+ def initialize(message, addr, peeraddr, error)
9
+ super(message)
10
+ @error = error
11
+ @addr = addr
12
+ @peeraddr = peeraddr
13
+ end
14
+ end
4
15
 
5
16
  # Handles sending and receiving data to EPP servers.
6
17
  # Supports new style EPP servers which include length of payloads in transmission.
7
18
  class Server
19
+ # @!attribute DEFAULT_SERVICES
20
+ # Provided for legacy clients who might be using it.
21
+ # The constant has been moved into the EPP::Client class which
22
+ # is the primary client facing API.
23
+ #
24
+ # @deprecated please use EPP::Client::DEFAULT_SERVICES
25
+ # @see EPP::Client::DEFAULT_SERVICES
8
26
 
9
- # Default Service URNs
10
- DEFAULT_SERVICES = %w(urn:ietf:params:xml:ns:domain-1.0
11
- urn:ietf:params:xml:ns:contact-1.0 urn:ietf:params:xml:ns:host-1.0)
27
+ # Handles emitting warnings for deprecated constants.
28
+ #
29
+ # @private
30
+ # @param [Symbol] const_name Name of the missing constant
31
+ # @see Module.const_missing
32
+ def self.const_missing(const_name)
33
+ case const_name
34
+ when :DEFAULT_SERVICES
35
+ warn "EPP::Server::DEFAULT_SERVICES has been deprecated, please use EPP::Client::DEFAULT_SERVICES"
36
+ EPP::Client::DEFAULT_SERVICES
37
+ else
38
+ super
39
+ end
40
+ end
12
41
 
13
42
  # Default connection options
14
43
  DEFAULTS = { :port => 700, :compatibility => false, :lang => 'en', :version => '1.0',
15
- :extensions => [], :services => DEFAULT_SERVICES }
44
+ :extensions => [], :services => EPP::Client::DEFAULT_SERVICES, :address_family => nil }
16
45
 
17
46
  # Receive frame header length
18
47
  # @private
@@ -28,6 +57,9 @@ module EPP
28
57
  # @option options [String] :version EPP protocol version, default '1.0'
29
58
  # @option options [Array<String>] :extensions EPP Extension URNs
30
59
  # @option options [Array<String>] :services EPP Service URNs
60
+ # @option options [String] :address_family 'AF_INET' or 'AF_INET6' or either of the
61
+ # appropriate socket constants. Will cause connections to be
62
+ # limited to this address family. Default try all addresses.
31
63
  def initialize(tag, passwd, host, options = {})
32
64
  @tag, @passwd, @host = tag, passwd, host
33
65
  @options = DEFAULTS.merge(options)
@@ -36,7 +68,9 @@ module EPP
36
68
  # Sends a Hello Request to the server
37
69
  # @return [Boolean] True if greeting was returned
38
70
  def hello
39
- send_frame(HelloRequest.new.to_s)
71
+ hello = EPP::Requests::Hello.new
72
+ request = EPP::Request.new(hello)
73
+ send_frame(request)
40
74
  return true if recv_frame =~ /<greeting>/
41
75
  false
42
76
  end
@@ -52,14 +86,31 @@ module EPP
52
86
  # @yieldparam [XML::Node] xml XML Node of the command
53
87
  # for the payload to be added into
54
88
  # @return [Response] EPP Response object
55
- def request(command, payload = nil, &block)
56
- @req = if payload.nil? && block_given?
57
- Request.new(command, next_tid, &block)
58
- else
59
- Request.new(command, payload, next_tid)
60
- end
89
+ # def request(command, payload = nil, extension = nil, &block)
90
+ # @req = if payload.nil? && block_given?
91
+ # Request.new(command, req_tid, &block)
92
+ # else
93
+ # Request.new(command, payload, extension, req_tid)
94
+ # end
95
+ #
96
+ # @resp = send_recv_frame(@req)
97
+ # end
98
+
99
+ # @note Primarily an internal method, exposed to enable testing
100
+ # @param [] command
101
+ # @param [] extension
102
+ # @return [EPP::Request]
103
+ # @see request
104
+ def prepare_request(command, extension = nil)
105
+ cmd = EPP::Requests::Command.new(req_tid, command, extension)
106
+ EPP::Request.new(cmd)
107
+ end
61
108
 
62
- @resp = send_recv_frame(@req.to_s)
109
+ def request(command, extension = nil)
110
+ @req = command.is_a?(EPP::Request) ? command :
111
+ prepare_request(command, extension)
112
+
113
+ @resp = send_recv_frame(@req)
63
114
  end
64
115
 
65
116
  # Return the Request object created by the last call to #request
@@ -74,13 +125,33 @@ module EPP
74
125
  def last_response
75
126
  @resp
76
127
  end
128
+ # Return the error from the last login or logout request
129
+ #
130
+ # @return [ResponseError] last error from login or logout
131
+ def last_error
132
+ @error
133
+ end
134
+ # Return the options the receiver was initialized with
135
+ #
136
+ # @return [Hash] configuration options
137
+ def options
138
+ @options
139
+ end
140
+ # Return the greeting XML received during the last connection
141
+ #
142
+ # @return [String]
143
+ def greeting
144
+ @greeting
145
+ end
77
146
 
78
147
  # Runs a block while logged into the receiver
79
148
  #
80
149
  # @yield logged in EPP server session
81
150
  # @example typical usage
82
- # with_login do
83
- # # .. do stuff with logged in session ..
151
+ # connection do
152
+ # with_login do
153
+ # # .. do stuff with logged in session ..
154
+ # end
84
155
  # end
85
156
  def with_login
86
157
  login!
@@ -106,57 +177,81 @@ module EPP
106
177
  # end
107
178
  # end
108
179
  def connection
109
- @conn = TCPSocket.new(@host, @options[:port])
110
- @sock = OpenSSL::SSL::SSLSocket.new(@conn)
111
- @sock.sync_close
180
+ @connection_errors = []
181
+ addrinfo.each do |_,port,_,addr,_,_,_|
182
+ retried = false
183
+ begin
184
+ @conn = TCPSocket.new(addr, port)
185
+ rescue Errno::EINVAL => e
186
+ if retried
187
+ message = e.message.split(" - ")[1]
188
+ raise Errno::EINVAL, "#{message}: TCPSocket.new(#{addr.inspect}, #{port.inspect})"
189
+ end
112
190
 
113
- begin
114
- @sock.connect
115
- recv_frame # Perform initial recv
191
+ retried = true
192
+ retry
193
+ end
116
194
 
117
- yield
118
- ensure
119
- @sock.close
120
- @conn.close
121
- conn = sock = nil
195
+ @sock = OpenSSL::SSL::SSLSocket.new(@conn)
196
+ @sock.sync_close = true
197
+
198
+ begin
199
+ @sock.connect
200
+ @greeting = recv_frame # Perform initial recv
201
+
202
+ return yield
203
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EHOSTUNREACH => e
204
+ @connection_errors << e
205
+ next # try the next address in the list
206
+ rescue OpenSSL::SSL::SSLError => e
207
+ # Connection error, most likely the IP isn't in the allow list
208
+ if e.message =~ /returned=5 errno=0/
209
+ @connection_errors << ConnectionError.new("SSL Connection error, IP may not be permitted to connect to #{@host}",
210
+ @conn.addr, @conn.peeraddr, e)
211
+ next
212
+ else
213
+ raise e
214
+ end
215
+ ensure
216
+ @sock.close # closes @conn
217
+ @conn = @sock = nil
218
+ end
122
219
  end
220
+
221
+ # Should only get here if we didn't return from the block above
222
+
223
+ addrinfo(true) # Update our addrinfo in case the DNS has changed
224
+ raise @connection_errors.last unless @connection_errors.empty?
225
+ raise Errno::EHOSTUNREACH, "Failed to connect to host #{@host}"
123
226
  end
124
227
  private
125
- # @return [String] next transaction id
126
- def next_tid
127
- @tid ||= 0
128
- @tid += 1
129
- "%s-%06d" % [@tag, @tid]
228
+ def addrinfo(refresh = false)
229
+ @addrinfo = nil if refresh
230
+ @addrinfo ||= resolve_addrinfo
130
231
  end
232
+ def resolve_addrinfo
233
+ family = case @options[:address_family]
234
+ when 'AF_INET', Socket::AF_INET then Socket::AF_INET
235
+ when 'AF_INET6', Socket::AF_INET6 then Socket::AF_INET6
236
+ else nil end
131
237
 
132
- # @return [Request] Login Request Payload
133
- def login_request
134
- Request.new('login', next_tid) do |login|
135
- login << XML::Node.new('clID', @tag)
136
- login << XML::Node.new('pw', @passwd)
137
-
138
- options = XML::Node.new('options')
139
- options << XML::Node.new('version', @options[:version])
140
- options << XML::Node.new('lang', @options[:lang])
141
- login << options
142
-
143
- svcs = XML::Node.new('svcs')
144
- @options[:services].each { |uri| svcs << XML::Node.new('objURI', uri) }
145
- login << svcs
146
-
147
- unless @options[:extensions].empty?
148
- ext = XML::Node.new('svcExtension')
149
- @options[:extensions].each do |uri|
150
- ext << XML::Node.new('extURI', uri)
151
- end
152
- svcs << ext
153
- end
154
- end
238
+ Socket.getaddrinfo(@host, @options[:port], family, Socket::SOCK_STREAM)
155
239
  end
156
240
 
157
- # @return [Request] Logout Request Payload
158
- def logout_request
159
- Request.new('logout')
241
+ # @return [String] next transaction id
242
+ def req_tid
243
+ @req_tid ||= 0
244
+ @req_tid += 1
245
+ date = Time.now.strftime("%Y%m%d")
246
+ "%s-%s%06d" % [@tag, date, @req_tid]
247
+ end
248
+
249
+ # @return [String] next auth transaction id
250
+ def auth_tid
251
+ @auth_tid ||= 0
252
+ @auth_tid += 1
253
+ date = Time.now.strftime("%Y%m%d")
254
+ "%s-AUTH-%s%06d" % [@tag, date, @auth_tid]
160
255
  end
161
256
 
162
257
  # Perform login
@@ -165,10 +260,14 @@ module EPP
165
260
  # @raise [ResponseError] login failed
166
261
  # @see login_request
167
262
  def login!
168
- response = send_recv_frame(login_request.to_s)
263
+ @error = nil
264
+ login = EPP::Commands::Login.new(@tag, @passwd, @options)
265
+ command = EPP::Requests::Command.new(auth_tid, login)
266
+ request = EPP::Request.new(command)
267
+ response = send_recv_frame(request)
169
268
 
170
269
  return true if response.code == 1000
171
- raise ResponseError.new(response.code, response.message, response.to_xml)
270
+ raise @error = ResponseError.new(response.code, response.message, response.to_xml)
172
271
  end
173
272
 
174
273
  # Perform logout
@@ -177,10 +276,13 @@ module EPP
177
276
  # @raise [ResponseError] logout failed
178
277
  # @see logout_request
179
278
  def logout!
180
- response = send_recv_frame(logout_request.to_s)
279
+ logout = EPP::Commands::Logout.new
280
+ command = EPP::Requests::Command.new(auth_tid, logout)
281
+ request = EPP::Request.new(command)
282
+ response = send_recv_frame(request)
181
283
 
182
284
  return true if response.code == 1500
183
- raise ResponseError.new(response.code, response.message, response.to_xml)
285
+ raise @error = ResponseError.new(response.code, response.message, response.to_xml)
184
286
  end
185
287
 
186
288
  # Send a frame and receive its response
@@ -195,9 +297,11 @@ module EPP
195
297
  end
196
298
 
197
299
  # Send XML frame
300
+ # @param [String,Request] xml Payload to send
198
301
  # @return [Integer] number of bytes written
199
302
  def send_frame(xml)
200
- @sock.write([xml.size + 4].pack("N") + xml)
303
+ xml = xml.to_s if xml.kind_of?(Request)
304
+ @sock.write([xml.size + HEADER_LEN].pack("N") + xml)
201
305
  end
202
306
 
203
307
  # Receive XML frame
@@ -211,7 +315,7 @@ module EPP
211
315
  raise ServerError, "Failed to read header from remote host"
212
316
  else
213
317
  len = header.unpack('N')[0]
214
-
318
+
215
319
  raise ServerError, "Bad frame header from server, should be greater than #{HEADER_LEN}" unless len > HEADER_LEN
216
320
  response = @sock.read(len - HEADER_LEN)
217
321
  end