savon 2.17.4 → 3.0.0.rc1

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.
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+ require "faraday"
3
+
4
+ module Savon
5
+ class HTTPRequest
6
+
7
+ def initialize(globals, connection = nil)
8
+ @globals = globals
9
+ @connection = connection || Faraday::Connection.new
10
+ end
11
+
12
+ private
13
+
14
+ def configure_proxy
15
+ connection.proxy = @globals[:proxy] if @globals.include? :proxy
16
+ end
17
+
18
+ def configure_timeouts
19
+ connection.options.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout
20
+ connection.options.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout
21
+ connection.options.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout
22
+ end
23
+
24
+ def configure_ssl
25
+ connection.ssl.verify = @globals[:ssl_verify] if @globals.include? :ssl_verify
26
+ connection.ssl.ca_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file
27
+ connection.ssl.verify_hostname = @globals[:verify_hostname] if @globals.include? :verify_hostname
28
+ connection.ssl.ca_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path
29
+ connection.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode
30
+ connection.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store
31
+ connection.ssl.client_cert = @globals[:ssl_cert] if @globals.include? :ssl_cert
32
+ connection.ssl.client_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key
33
+ connection.ssl.certificate = @globals[:ssl_certificate] if @globals.include? :ssl_certificate
34
+ connection.ssl.private_key = @globals[:ssl_private_key] if @globals.include? :ssl_private_key
35
+ connection.ssl.verify_depth = @globals[:verify_depth] if @globals.include? :verify_depth
36
+ connection.ssl.version = @globals[:ssl_version] if @globals.include? :ssl_version
37
+ connection.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version
38
+ connection.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version
39
+
40
+ # No Faraday Equivalent out of box, see: https://lostisland.github.io/faraday/#/customization/ssl-options
41
+ # connection.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file
42
+ # connection.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file
43
+ # connection.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert
44
+ # connection.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers
45
+ # connection.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password
46
+
47
+ end
48
+
49
+ def configure_auth
50
+ basic_auth if @globals.include?(:basic_auth)
51
+ ntlm_auth if @globals.include?(:ntlm)
52
+ end
53
+
54
+ def basic_auth
55
+ connection.request(:authorization, :basic, *@globals[:basic_auth])
56
+ end
57
+
58
+ def ntlm_auth
59
+ begin
60
+ require 'rubyntlm'
61
+ require 'faraday/net_http_persistent'
62
+ connection.adapter :net_http_persistent, pool_size: 5
63
+ rescue LoadError
64
+ raise LoadError, 'Using NTLM Auth requires both `rubyntlm` and `faraday-net_http_persistent` to be installed.'
65
+ end
66
+ end
67
+
68
+ def configure_redirect_handling
69
+ if @globals[:follow_redirects]
70
+ require 'faraday/follow_redirects'
71
+ connection.response :follow_redirects
72
+ end
73
+ end
74
+
75
+ def configure_adapter
76
+ connection.adapter(*@globals[:adapter]) unless @globals[:adapter].nil?
77
+ end
78
+
79
+ def configure_logging
80
+ connection.response(:logger, nil, headers: @globals[:log_headers], level: @globals[:logger].level) if @globals[:log]
81
+ end
82
+
83
+ protected
84
+ attr_reader :connection
85
+ end
86
+
87
+ class WSDLRequest < HTTPRequest
88
+
89
+ def build
90
+ configure_proxy
91
+ configure_timeouts
92
+ configure_ssl
93
+ configure_auth
94
+ configure_adapter
95
+ configure_logging
96
+ configure_headers
97
+ connection
98
+ end
99
+
100
+ private
101
+
102
+ def configure_headers
103
+ connection.headers = @globals[:headers] if @globals.include? :headers
104
+ end
105
+ end
106
+
107
+ class SOAPRequest < HTTPRequest
108
+
109
+ CONTENT_TYPE = {
110
+ 1 => "text/xml;charset=%s",
111
+ 2 => "application/soap+xml;charset=%s"
112
+ }
113
+
114
+ def build(options = {})
115
+ configure_proxy
116
+ configure_timeouts
117
+ configure_ssl
118
+ configure_auth
119
+ configure_headers(options[:soap_action], options[:headers])
120
+ configure_cookies(options[:cookies])
121
+ configure_adapter
122
+ configure_logging
123
+ configure_redirect_handling
124
+ yield(connection) if block_given?
125
+ connection
126
+ end
127
+
128
+ private
129
+
130
+ def configure_cookies(cookies)
131
+ connection.headers['Cookie'] = cookies.map do |key, value|
132
+ if value.nil?
133
+ key
134
+ else
135
+ "#{key}=#{value}"
136
+ end
137
+ end.join('; ') if cookies
138
+ end
139
+
140
+ def configure_headers(soap_action, headers)
141
+ connection.headers = @globals[:headers] if @globals.include? :headers
142
+ connection.headers.merge!(headers) if headers
143
+ connection.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action
144
+ connection.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require "savon/log_message"
3
+
4
+ module Savon
5
+ class RequestLogger
6
+
7
+ def initialize(globals)
8
+ @globals = globals
9
+ end
10
+
11
+ def log(request, &http_request)
12
+ log_request(request) if log?
13
+ response = http_request.call
14
+ log_response(response) if log?
15
+
16
+ response
17
+ end
18
+
19
+ def logger
20
+ @globals[:logger]
21
+ end
22
+
23
+ def log?
24
+ @globals[:log]
25
+ end
26
+
27
+ def log_headers?
28
+ @globals[:log_headers]
29
+ end
30
+ def log_request(request)
31
+ return unless log?
32
+ logger.info { "SOAP request: #{request.path}" }
33
+ logger.info { headers_to_log(request.headers) } if log_headers?
34
+ logger.debug { body_to_log(request.body) }
35
+ end
36
+
37
+ def log_response(response)
38
+ return response unless log?
39
+ logger.info { "SOAP response (status #{response.status})" }
40
+ logger.debug { headers_to_log(response.headers) } if log_headers?
41
+ logger.debug { body_to_log(response.body) }
42
+ response
43
+ end
44
+
45
+ private
46
+
47
+
48
+ def headers_to_log(headers)
49
+ headers.map { |key, value| "#{key}: #{value}" }.join("\n")
50
+ end
51
+
52
+ def body_to_log(body)
53
+ LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s
54
+ end
55
+
56
+ end
57
+ end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  require "nori"
4
3
  require "savon/soap_fault"
5
4
  require "savon/http_error"
@@ -7,37 +6,33 @@ require "savon/http_error"
7
6
  module Savon
8
7
  class Response
9
8
  CRLF = /\r\n/
10
- WSP = /[\t ]/
9
+ WSP = /[#{%Q|\x9\x20|}]/
11
10
 
12
- def initialize(transport_response, globals, locals)
13
- @transport_response = transport_response
14
- @globals = globals
15
- @locals = locals
16
- @attachments = []
17
- @xml = ''
18
- @has_parsed_body = false
11
+ def initialize(http, globals, locals)
12
+ @http = http
13
+ @globals = globals
14
+ @locals = locals
15
+ @attachments = []
16
+ @xml = ''
17
+ @has_parsed_body = false
19
18
 
20
19
  build_soap_and_http_errors!
21
20
  raise_soap_and_http_errors! if @globals[:raise_errors]
22
21
  end
23
22
 
24
- attr_reader :globals, :locals, :soap_fault, :http_error
25
-
26
- def http
27
- @transport_response
28
- end
23
+ attr_reader :http, :globals, :locals, :soap_fault, :http_error
29
24
 
30
25
  def success?
31
26
  !soap_fault? && !http_error?
32
27
  end
33
- alias successful? success?
28
+ alias_method :successful?, :success?
34
29
 
35
30
  def soap_fault?
36
- SOAPFault.present?(http, xml)
31
+ SOAPFault.present?(@http, xml)
37
32
  end
38
33
 
39
34
  def http_error?
40
- HTTPError.present? http
35
+ HTTPError.present? @http
41
36
  end
42
37
 
43
38
  def header
@@ -48,21 +43,15 @@ module Savon
48
43
  find('Body')
49
44
  end
50
45
 
51
- alias to_hash body
46
+ alias_method :to_hash, :body
52
47
 
53
48
  def to_array(*path)
54
- result = path.inject(body) { |memo, key|
49
+ result = path.inject body do |memo, key|
55
50
  return [] if memo[key].nil?
56
-
57
51
  memo[key]
58
- }
59
-
60
- result.is_a?(Array) ? result.compact : [result].compact
61
- end
52
+ end
62
53
 
63
- def hash
64
- warn "Savon::Response#hash is deprecated and will be removed in version 3 - use #full_hash instead", uplevel: 1
65
- full_hash
54
+ result.kind_of?(Array) ? result.compact : [result].compact
66
55
  end
67
56
 
68
57
  def full_hash
@@ -74,12 +63,12 @@ module Savon
74
63
  parse_body unless @has_parsed_body
75
64
  @xml
76
65
  else
77
- http.body
66
+ @http.body
78
67
  end
79
68
  end
80
69
 
81
- alias to_xml xml
82
- alias to_s xml
70
+ alias_method :to_xml, :xml
71
+ alias_method :to_s, :xml
83
72
 
84
73
  def doc
85
74
  @doc ||= Nokogiri.XML(xml)
@@ -113,20 +102,19 @@ module Savon
113
102
 
114
103
  def boundary
115
104
  return unless multipart?
116
-
117
105
  Mail::Field.new('content-type', http.headers['content-type']).parameters['boundary']
118
106
  end
119
107
 
120
108
  def parse_body
121
109
  http.body.force_encoding Encoding::ASCII_8BIT
122
110
  parts = http.body.split(/(?:\A|\r\n)--#{Regexp.escape(boundary)}(?=(?:--)?\s*$)/)
123
- parts[1..].to_a.each_with_index do |part, index|
111
+ parts[1..-1].to_a.each_with_index do |part, index|
124
112
  header_part, body_part = part.lstrip.split(/#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
125
113
  section = Mail::Part.new(
126
114
  body: body_part
127
115
  )
128
116
  section.header = header_part
129
- if index.zero?
117
+ if index == 0
130
118
  @xml = section.body.to_s
131
119
  else
132
120
  @attachments << section
@@ -136,8 +124,8 @@ module Savon
136
124
  end
137
125
 
138
126
  def build_soap_and_http_errors!
139
- @soap_fault = SOAPFault.new(http, nori, xml) if soap_fault?
140
- @http_error = HTTPError.new(http) if http_error?
127
+ @soap_fault = SOAPFault.new(@http, nori, xml) if soap_fault?
128
+ @http_error = HTTPError.new(@http) if http_error?
141
129
  end
142
130
 
143
131
  def raise_soap_and_http_errors!
@@ -146,7 +134,7 @@ module Savon
146
134
  end
147
135
 
148
136
  def raise_invalid_response_error!
149
- raise InvalidResponseError, "Unable to parse response body:\n#{xml.inspect}"
137
+ raise InvalidResponseError, "Unable to parse response body:\n" + xml.inspect
150
138
  end
151
139
 
152
140
  def xml_namespaces
@@ -157,19 +145,17 @@ module Savon
157
145
  return @nori if @nori
158
146
 
159
147
  nori_options = {
160
- delete_namespace_attributes: @globals[:delete_namespace_attributes],
161
- strip_namespaces: @globals[:strip_namespaces],
162
- empty_tag_value: @globals[:empty_tag_value],
163
- convert_dashes_to_underscores: @globals[:convert_dashes_to_underscores],
164
- scrub_xml: @globals[:scrub_xml],
165
- convert_tags_to: @globals[:convert_response_tags_to],
166
- convert_attributes_to: @globals[:convert_attributes_to],
167
- advanced_typecasting: @locals[:advanced_typecasting],
168
- parser: @locals[:response_parser]
148
+ :delete_namespace_attributes => @globals[:delete_namespace_attributes],
149
+ :strip_namespaces => @globals[:strip_namespaces],
150
+ :convert_tags_to => @globals[:convert_response_tags_to],
151
+ :convert_attributes_to => @globals[:convert_attributes_to],
152
+ :advanced_typecasting => @locals[:advanced_typecasting],
153
+ :parser => @locals[:response_parser]
169
154
  }
170
155
 
171
156
  non_nil_nori_options = nori_options.reject { |_, value| value.nil? }
172
157
  @nori = Nori.new(non_nil_nori_options)
173
158
  end
159
+
174
160
  end
175
161
  end
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  module Savon
4
3
  class SOAPFault < Error
4
+
5
5
  def self.present?(http, xml = nil)
6
- body = xml || http.body
7
- body = body.scrub('') unless body.valid_encoding?
8
- fault_node = body.include?("Fault>")
9
- soap1_fault = body.match(%r{faultcode/?>}) && body.match(%r{faultstring/?>})
10
- soap2_fault = body.include?("Code>") && body.include?("Reason>")
6
+ xml ||= http.body
7
+ fault_node = xml.include?("Fault>")
8
+ soap1_fault = xml.match(/faultcode\/?>/) && xml.match(/faultstring\/?>/)
9
+ soap2_fault = xml.include?("Code>") && xml.include?("Reason>")
11
10
 
12
11
  fault_node && (soap1_fault || soap2_fault)
13
12
  end
@@ -45,5 +44,6 @@ module Savon
45
44
  "(#{code}) #{text}"
46
45
  end
47
46
  end
47
+
48
48
  end
49
49
  end
@@ -4,9 +4,9 @@ module Savon
4
4
  module StringUtils
5
5
  def self.snakecase(inputstring)
6
6
  str = inputstring.dup
7
- str.gsub!(/::/, '/')
8
- str.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
9
- str.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
7
+ str.gsub! /::/, '/'
8
+ str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
9
+ str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
10
10
  str.tr! ".", "_"
11
11
  str.tr! "-", "_"
12
12
  str.downcase!
@@ -14,3 +14,4 @@ module Savon
14
14
  end
15
15
  end
16
16
  end
17
+
data/lib/savon/version.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  module Savon
4
- VERSION = '2.17.4'
3
+ VERSION = '3.0.0.rc1'
5
4
  end
data/lib/savon.rb CHANGED
@@ -1,12 +1,20 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  module Savon
3
+
4
4
  Error = Class.new(RuntimeError)
5
5
  InitializationError = Class.new(Error)
6
6
  UnknownOptionError = Class.new(Error)
7
7
  UnknownOperationError = Class.new(Error)
8
8
  InvalidResponseError = Class.new(Error)
9
9
 
10
+ class DeprecatedOptionError < Error
11
+ attr_accessor :option
12
+ def initialize(option)
13
+ @option = option
14
+ super("#{option} is deprecated as it is not supported in Faraday. See https://github.com/savonrb/savon/blob/main/UPGRADING.md for more information.")
15
+ end
16
+ end
17
+
10
18
  def self.client(globals = {}, &block)
11
19
  Client.new(globals, &block)
12
20
  end
@@ -16,10 +24,11 @@ module Savon
16
24
  end
17
25
 
18
26
  def self.notify_observers(operation_name, builder, globals, locals)
19
- observers.inject(nil) do |_response, observer|
27
+ observers.inject(nil) do |response, observer|
20
28
  observer.notify(operation_name, builder, globals, locals)
21
29
  end
22
30
  end
31
+
23
32
  end
24
33
 
25
34
  require "savon/version"