savon 2.17.2 → 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,14 +6,14 @@ 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
11
  def initialize(http, globals, locals)
13
- @http = http
14
- @globals = globals
15
- @locals = locals
16
- @attachments = []
17
- @xml = ''
12
+ @http = http
13
+ @globals = globals
14
+ @locals = locals
15
+ @attachments = []
16
+ @xml = ''
18
17
  @has_parsed_body = false
19
18
 
20
19
  build_soap_and_http_errors!
@@ -26,7 +25,7 @@ module Savon
26
25
  def success?
27
26
  !soap_fault? && !http_error?
28
27
  end
29
- alias successful? success?
28
+ alias_method :successful?, :success?
30
29
 
31
30
  def soap_fault?
32
31
  SOAPFault.present?(@http, xml)
@@ -44,21 +43,15 @@ module Savon
44
43
  find('Body')
45
44
  end
46
45
 
47
- alias to_hash body
46
+ alias_method :to_hash, :body
48
47
 
49
48
  def to_array(*path)
50
- result = path.inject(body) { |memo, key|
49
+ result = path.inject body do |memo, key|
51
50
  return [] if memo[key].nil?
52
-
53
51
  memo[key]
54
- }
55
-
56
- result.is_a?(Array) ? result.compact : [result].compact
57
- end
52
+ end
58
53
 
59
- def hash
60
- warn "Savon::Response#hash is deprecated and will be removed in version 3 - use #full_hash instead", uplevel: 1
61
- full_hash
54
+ result.kind_of?(Array) ? result.compact : [result].compact
62
55
  end
63
56
 
64
57
  def full_hash
@@ -74,8 +67,8 @@ module Savon
74
67
  end
75
68
  end
76
69
 
77
- alias to_xml xml
78
- alias to_s xml
70
+ alias_method :to_xml, :xml
71
+ alias_method :to_s, :xml
79
72
 
80
73
  def doc
81
74
  @doc ||= Nokogiri.XML(xml)
@@ -109,20 +102,19 @@ module Savon
109
102
 
110
103
  def boundary
111
104
  return unless multipart?
112
-
113
105
  Mail::Field.new('content-type', http.headers['content-type']).parameters['boundary']
114
106
  end
115
107
 
116
108
  def parse_body
117
109
  http.body.force_encoding Encoding::ASCII_8BIT
118
110
  parts = http.body.split(/(?:\A|\r\n)--#{Regexp.escape(boundary)}(?=(?:--)?\s*$)/)
119
- parts[1..].to_a.each_with_index do |part, index|
111
+ parts[1..-1].to_a.each_with_index do |part, index|
120
112
  header_part, body_part = part.lstrip.split(/#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
121
113
  section = Mail::Part.new(
122
114
  body: body_part
123
115
  )
124
116
  section.header = header_part
125
- if index.zero?
117
+ if index == 0
126
118
  @xml = section.body.to_s
127
119
  else
128
120
  @attachments << section
@@ -142,7 +134,7 @@ module Savon
142
134
  end
143
135
 
144
136
  def raise_invalid_response_error!
145
- raise InvalidResponseError, "Unable to parse response body:\n#{xml.inspect}"
137
+ raise InvalidResponseError, "Unable to parse response body:\n" + xml.inspect
146
138
  end
147
139
 
148
140
  def xml_namespaces
@@ -153,19 +145,17 @@ module Savon
153
145
  return @nori if @nori
154
146
 
155
147
  nori_options = {
156
- delete_namespace_attributes: @globals[:delete_namespace_attributes],
157
- strip_namespaces: @globals[:strip_namespaces],
158
- empty_tag_value: @globals[:empty_tag_value],
159
- convert_dashes_to_underscores: @globals[:convert_dashes_to_underscores],
160
- scrub_xml: @globals[:scrub_xml],
161
- convert_tags_to: @globals[:convert_response_tags_to],
162
- convert_attributes_to: @globals[:convert_attributes_to],
163
- advanced_typecasting: @locals[:advanced_typecasting],
164
- 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]
165
154
  }
166
155
 
167
156
  non_nil_nori_options = nori_options.reject { |_, value| value.nil? }
168
157
  @nori = Nori.new(non_nil_nori_options)
169
158
  end
159
+
170
160
  end
171
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.2'
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"