savon 2.15.1 → 3.0.0.rc2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df435a9b3a37060f3dbbfe5ba2f8b36c33ad66b6474afa08771396b6812fb4b3
4
- data.tar.gz: ac8cccabad1588894528b1d2ad073e576224a6b4a5b25d10a0437fb5a807e538
3
+ metadata.gz: 217170b4a884638cf811c1b5334c9acbbfa7e5b88b1554f29c3e043557be5195
4
+ data.tar.gz: c36c7221ec92faa8df1064d4776c21269885d8db5d8d047db69dc3f598f05220
5
5
  SHA512:
6
- metadata.gz: 79a1dd54bd1d171afc117927c9b5419f7c6d2e2a5e2b1cfad0072397e786f5932bb3180b40f7ea9d55a08194c1554afddf3efe558959157cdcc7e8b13243beb9
7
- data.tar.gz: 9e9f944a60f6f655da4abfed31020befb23a9823bb3437ccb46e527b1b8e091fac81c776fc3057f88985a80bbae3c8bb9746bf8c1e9c3966ac8992bc0ee7f936
6
+ metadata.gz: 8da94da891a4d4db8e6a8ee2fbf08d2d230e2fcd7ea709f474649c4a06161c301a989a2174e58b73d27ca05a7141d8f1690e86526555e9f17d78665cca54e645
7
+ data.tar.gz: 1cb489bdd51b813057ad4b5b6c9f81739b89fa08276cc5ddca096271daf530b5c63307723d78b2202970a1f8f23270d71acf9f11daea5f43ca25c523067f7bbc
data/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  ## Unreleased
4
4
  * Add your PR changelog line here
5
5
 
6
+ ## 3.0.0.rc2 (2025-08-12)
7
+ * MTOM support with tests by @pcai in https://github.com/savonrb/savon/pull/1012
8
+ * Upgrade notes on ssl_verify_mode by @ehutzelman in https://github.com/savonrb/savon/pull/1013
9
+ * Pass the provided Savon/custom logger to Faraday by @larskanis in https://github.com/savonrb/savon/pull/1017
10
+ * Add ruby 3.4 to CI by @doconnor-clintel in https://github.com/savonrb/savon/pull/1024
11
+ * Restore support for SSL Ciphers by @doconnor-clintel in https://github.com/savonrb/savon/pull/1020
12
+ * Drop ruby 3.0 from CI by @doconnor-clintel in https://github.com/savonrb/savon/pull/1025
13
+ * Don't block minor updates to faraday by @larskanis in https://github.com/savonrb/savon/pull/1028
14
+ * Add gzip middleware when Accept-Encoding includes gzip by @kjeldahl in https://github.com/savonrb/savon/pull/1030
15
+ * Add option to provide connection middlewares. by @amartinfraguas in https://github.com/savonrb/savon/pull/1026
16
+
17
+ ## 3.0.0.rc1 (2024-07-15)
18
+
19
+ * Use Faraday instead of HTTPI
20
+ * BC BREAKING Cookies are handled differently now
21
+ * BC BREAKING Multiple pieces of functionality will rely on faraday libraries to be provided by the consuming codebase
22
+ * BC BREAKING Adapter overrides now utilize the faraday model
23
+ * BC BREAKING Multiple hard deprecations due to a lack of feature parity between Faraday and HTTPI
24
+ * Deprecates digest auth
25
+ * Deprecates ssl_cert_key_file auth, upgrade path is to read the key
26
+ in and provide it
27
+ * Deprecates encrypted ssl keys, upgrade path is to
28
+ decrypt the key and pass it to faraday in code
29
+ * Deprecates providing a ca cert, upgrade path is to provide a ca cert file
30
+ * deprecates overriding ssl ciphers, as faraday does not support this
31
+
6
32
  ## 2.15.1 (2024-07-08)
7
33
 
8
34
  * Ruby 3.0+ is required in the gemspec.
data/README.md CHANGED
@@ -10,6 +10,7 @@ Heavy metal SOAP client
10
10
  [![Code Climate](https://codeclimate.com/github/savonrb/savon.svg)](https://codeclimate.com/github/savonrb/savon)
11
11
  [![Coverage Status](https://coveralls.io/repos/savonrb/savon/badge.svg)](https://coveralls.io/r/savonrb/savon)
12
12
 
13
+ If you're reading this on GitHub, note that this README is for the main branch and that features/changes described here might not correspond to your version. You can find the documentation for your release [at rubydoc.info](https://www.rubydoc.info/find/gems?q=savon).
13
14
 
14
15
  ## Installation
15
16
 
@@ -22,7 +23,7 @@ $ gem install savon
22
23
  or add it to your Gemfile like this:
23
24
 
24
25
  ```
25
- gem 'savon', '~> 2.15.0'
26
+ gem 'savon', '~> 3.0.0'
26
27
  ```
27
28
 
28
29
  ## Usage example
@@ -52,6 +53,10 @@ response.body
52
53
  For more examples, you should check out the
53
54
  [integration tests](https://github.com/savonrb/savon/tree/version2/spec/integration).
54
55
 
56
+ ## Upgrading from v2.x to v3.x
57
+
58
+ See [UPGRADING.md](UPGRADING.md) for more information.
59
+
55
60
  ## Ruby version support
56
61
 
57
62
  Every savon release is tested with contemporary supported versions of ruby. Historical compatibility information:
data/lib/savon/builder.rb CHANGED
@@ -38,18 +38,23 @@ module Savon
38
38
  end
39
39
 
40
40
  def build_document
41
- xml_result = build_xml
41
+ # check if xml was already provided
42
+ if @locals.include? :xml
43
+ xml_result = @locals[:xml]
44
+ else
45
+ xml_result = build_xml
42
46
 
43
- # if we have a signature sign the document
44
- if @signature
45
- @signature.document = xml_result
47
+ # if we have a signature sign the document
48
+ if @signature
49
+ @signature.document = xml_result
46
50
 
47
- 2.times do
48
- @header = nil
49
- @signature.document = build_xml
50
- end
51
+ 2.times do
52
+ @header = nil
53
+ @signature.document = build_xml
54
+ end
51
55
 
52
- xml_result = @signature.document
56
+ xml_result = @signature.document
57
+ end
53
58
  end
54
59
 
55
60
  # if there are attachments for the request, we should build a multipart message according to
@@ -70,7 +75,6 @@ module Savon
70
75
  end
71
76
 
72
77
  def to_s
73
- return @locals[:xml] if @locals.include? :xml
74
78
  build_document
75
79
  end
76
80
 
@@ -254,15 +258,28 @@ module Savon
254
258
 
255
259
  # the mail.body.encoded algorithm reorders the parts, default order is [ "text/plain", "text/enriched", "text/html" ]
256
260
  # should redefine the sort order, because the soap request xml should be the first
257
- multipart_message.body.set_sort_order [ "text/xml" ]
261
+ multipart_message.body.set_sort_order ['application/xop+xml', 'text/xml']
258
262
 
259
263
  multipart_message.body.encoded(multipart_message.content_transfer_encoding)
260
264
  end
261
265
 
262
266
  def init_multipart_message(message_xml)
263
267
  multipart_message = Mail.new
268
+
269
+ # MTOM differs from general SOAP attachments:
270
+ # 1. binary encoding
271
+ # 2. application/xop+xml mime type
272
+ if @locals[:mtom]
273
+ type = "application/xop+xml; charset=#{@globals[:encoding]}; type=\"text/xml\""
274
+
275
+ multipart_message.transport_encoding = 'binary'
276
+ message_xml.force_encoding('BINARY')
277
+ else
278
+ type = 'text/xml'
279
+ end
280
+
264
281
  xml_part = Mail::Part.new do
265
- content_type 'text/xml'
282
+ content_type type
266
283
  body message_xml
267
284
  # in Content-Type the start parameter is recommended (RFC 2387)
268
285
  content_id '<soap-request-body@soap>'
@@ -4,7 +4,7 @@ module Savon
4
4
  class HTTPError < Error
5
5
 
6
6
  def self.present?(http)
7
- http.error?
7
+ !http.success?
8
8
  end
9
9
 
10
10
  def initialize(http)
@@ -14,13 +14,13 @@ module Savon
14
14
  attr_reader :http
15
15
 
16
16
  def to_s
17
- String.new("HTTP error (#{@http.code})").tap do |str_error|
17
+ String.new("HTTP error (#{@http.status})").tap do |str_error|
18
18
  str_error << ": #{@http.body}" unless @http.body.empty?
19
19
  end
20
20
  end
21
21
 
22
22
  def to_hash
23
- { :code => @http.code, :headers => @http.headers, :body => @http.body }
23
+ { :code => @http.status, :headers => @http.headers, :body => @http.body }
24
24
  end
25
25
 
26
26
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require "httpi"
2
+ require "faraday"
3
3
 
4
4
  module Savon
5
5
  class MockExpectation
@@ -41,8 +41,8 @@ module Savon
41
41
  unless @response
42
42
  raise ExpectationError, "This expectation was not set up with a response."
43
43
  end
44
-
45
- HTTPI::Response.new(@response[:code], @response[:headers], @response[:body])
44
+ env = Faraday::Env.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body])
45
+ Faraday::Response.new(env)
46
46
  end
47
47
 
48
48
  private
@@ -75,7 +75,7 @@ module Savon
75
75
  next if (expected_value == :any && msg_real.include?(key))
76
76
  return false if expected_value != msg_real[key]
77
77
  end
78
- return true
78
+ true
79
79
  end
80
80
  end
81
81
  end
@@ -7,6 +7,8 @@ require "savon/response"
7
7
  require "savon/request_logger"
8
8
  require "savon/http_error"
9
9
  require "mail"
10
+ require 'faraday/gzip'
11
+
10
12
 
11
13
  module Savon
12
14
  class Operation
@@ -15,6 +17,7 @@ module Savon
15
17
  1 => "text/xml",
16
18
  2 => "application/soap+xml"
17
19
  }
20
+ SOAP_REQUEST_TYPE_MTOM = "application/xop+xml"
18
21
 
19
22
  def self.create(operation_name, wsdl, globals)
20
23
  if wsdl.document?
@@ -58,16 +61,20 @@ module Savon
58
61
  builder = build(locals, &block)
59
62
 
60
63
  response = Savon.notify_observers(@name, builder, @globals, @locals)
61
- response ||= call_with_logging build_request(builder)
64
+ response ||= call_with_logging build_connection(builder)
62
65
 
63
- raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response)
66
+ raise_expected_faraday_response! unless response.kind_of?(Faraday::Response)
64
67
 
65
68
  create_response(response)
66
69
  end
67
70
 
68
71
  def request(locals = {}, &block)
69
72
  builder = build(locals, &block)
70
- build_request(builder)
73
+ connection = build_connection(builder)
74
+ connection.build_request(:post) do |req|
75
+ req.url(@globals[:endpoint])
76
+ req.body = @locals[:body]
77
+ end
71
78
  end
72
79
 
73
80
  private
@@ -83,37 +90,50 @@ module Savon
83
90
  @locals = locals
84
91
  end
85
92
 
86
- def call_with_logging(request)
87
- @logger.log(request) { HTTPI.post(request, @globals[:adapter]) }
93
+ def call_with_logging(connection)
94
+ ntlm_auth = handle_ntlm(connection) if @globals.include?(:ntlm)
95
+ @logger.log_response(connection.post(@globals[:endpoint]) { |request|
96
+ request.body = @locals[:body]
97
+ request.headers['Authorization'] = "NTLM #{auth.encode64}" if ntlm_auth
98
+ @logger.log_request(request)
99
+ })
88
100
  end
89
101
 
90
- def build_request(builder)
91
- @locals[:soap_action] ||= soap_action
92
- @globals[:endpoint] ||= endpoint
102
+ def handle_ntlm(connection)
103
+ ntlm_message = Net::NTLM::Message
104
+ response = connection.get(@globals[:endpoint]) do |request|
105
+ request.headers['Authorization'] = 'NTLM ' + ntlm_message::Type1.new.encode64
106
+ end
107
+ challenge = response.headers['www-authenticate'][/(?:NTLM|Negotiate) (.*)$/, 1]
108
+ message = ntlm_message::Type2.decode64(challenge)
109
+ message.response([:user, :password, :domain].zip(@globals[:ntlm]).to_h)
110
+ end
93
111
 
94
- request = SOAPRequest.new(@globals).build(
112
+ def build_connection(builder)
113
+ @globals[:endpoint] ||= endpoint
114
+ @locals[:soap_action] ||= soap_action
115
+ @locals[:body] = builder.to_s
116
+ @connection = SOAPRequest.new(@globals).build(
95
117
  :soap_action => soap_action,
96
118
  :cookies => @locals[:cookies],
97
119
  :headers => @locals[:headers]
98
- )
99
-
100
- request.url = endpoint
101
- request.body = builder.to_s
102
-
103
- if builder.multipart
104
- request.gzip
105
- request.headers["Content-Type"] = ["multipart/related",
106
- "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"",
107
- "start=\"#{builder.multipart[:start]}\"",
108
- "boundary=\"#{builder.multipart[:multipart_boundary]}\""].join("; ")
109
- request.headers["MIME-Version"] = "1.0"
110
- end
111
-
112
- # TODO: could HTTPI do this automatically in case the header
113
- # was not specified manually? [dh, 2013-01-04]
114
- request.headers["Content-Length"] = request.body.bytesize.to_s
120
+ ) do |connection|
121
+ if builder.multipart
122
+ ctype_headers = ["multipart/related"]
123
+ if @locals[:mtom]
124
+ ctype_headers << "type=\"#{SOAP_REQUEST_TYPE_MTOM}\""
125
+ ctype_headers << "start-info=\"text/xml\""
126
+ else
127
+ ctype_headers << "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\""
128
+ connection.request :gzip
129
+ end
130
+ connection.headers["Content-Type"] = (ctype_headers + ["start=\"#{builder.multipart[:start]}\"",
131
+ "boundary=\"#{builder.multipart[:multipart_boundary]}\""]).join("; ")
132
+ connection.headers["MIME-Version"] = "1.0"
133
+ end
115
134
 
116
- request
135
+ connection.headers["Content-Length"] = @locals[:body].bytesize.to_s
136
+ end
117
137
  end
118
138
 
119
139
  def soap_action
@@ -138,8 +158,8 @@ module Savon
138
158
  end
139
159
  end
140
160
 
141
- def raise_expected_httpi_response!
142
- raise Error, "Observers need to return an HTTPI::Response to mock " \
161
+ def raise_expected_faraday_response!
162
+ raise Error, "Observers need to return a Faraday::Response to mock " \
143
163
  "the request or nil to execute the request."
144
164
  end
145
165
 
data/lib/savon/options.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require "logger"
3
- require "httpi"
4
3
 
5
4
  module Savon
6
5
  class Options
@@ -10,6 +9,10 @@ module Savon
10
9
  assign options
11
10
  end
12
11
 
12
+ def deprecate(option)
13
+ raise DeprecatedOptionError.new(option)
14
+ end
15
+
13
16
  attr_reader :option_type
14
17
 
15
18
  def [](option)
@@ -89,6 +92,7 @@ module Savon
89
92
  :convert_attributes_to => lambda { |k,v| [k,v] },
90
93
  :multipart => false,
91
94
  :adapter => nil,
95
+ :middlewares => [],
92
96
  :use_wsa_headers => false,
93
97
  :no_message_tag => false,
94
98
  :follow_redirects => false,
@@ -127,7 +131,7 @@ module Savon
127
131
  @options[:namespace] = namespace
128
132
  end
129
133
 
130
- # The namespace identifer.
134
+ # The namespace identifier.
131
135
  def namespace_identifier(identifier)
132
136
  @options[:namespace_identifier] = identifier
133
137
  end
@@ -198,13 +202,11 @@ module Savon
198
202
 
199
203
  # Whether or not to log.
200
204
  def log(log)
201
- HTTPI.log = log
202
205
  @options[:log] = log
203
206
  end
204
207
 
205
208
  # The logger to use. Defaults to a Savon::Logger instance.
206
209
  def logger(logger)
207
- HTTPI.logger = logger
208
210
  @options[:logger] = logger
209
211
  end
210
212
 
@@ -257,6 +259,7 @@ module Savon
257
259
 
258
260
  # Sets the cert key file to use.
259
261
  def ssl_cert_key_file(file)
262
+ deprecate('ssl_cert_key_file')
260
263
  @options[:ssl_cert_key_file] = file
261
264
  end
262
265
 
@@ -267,11 +270,13 @@ module Savon
267
270
 
268
271
  # Sets the cert key password to use.
269
272
  def ssl_cert_key_password(password)
273
+ deprecate('ssl_cert_key_password')
270
274
  @options[:ssl_cert_key_password] = password
271
275
  end
272
276
 
273
277
  # Sets the cert file to use.
274
278
  def ssl_cert_file(file)
279
+ deprecate('ssl_cert_file')
275
280
  @options[:ssl_cert_file] = file
276
281
  end
277
282
 
@@ -287,6 +292,7 @@ module Savon
287
292
 
288
293
  # Sets the ca cert to use.
289
294
  def ssl_ca_cert(cert)
295
+ deprecate('ssl_ca_cert')
290
296
  @options[:ssl_ca_cert] = cert
291
297
  end
292
298
 
@@ -311,6 +317,7 @@ module Savon
311
317
 
312
318
  # HTTP digest auth credentials.
313
319
  def digest_auth(*credentials)
320
+ deprecate('digest_auth')
314
321
  @options[:digest_auth] = credentials.flatten
315
322
  end
316
323
 
@@ -360,7 +367,7 @@ module Savon
360
367
  @options[:multipart] = multipart
361
368
  end
362
369
 
363
- # Instruct Savon what HTTPI adapter it should use instead of default
370
+ # Instruct Savon what Faraday adapter it should use instead of default
364
371
  def adapter(adapter)
365
372
  @options[:adapter] = adapter
366
373
  end
@@ -378,6 +385,25 @@ module Savon
378
385
  def follow_redirects(follow_redirects)
379
386
  @options[:follow_redirects] = follow_redirects
380
387
  end
388
+
389
+ # Provide middlewares for Faraday connections.
390
+ # The argument is an array, with each element being another array
391
+ # that contains the middleware class and its arguments, in the same way
392
+ # as a normal call to Faraday::RackBuilder#use
393
+ #
394
+ # See https://lostisland.github.io/faraday/#/middleware/index?id=using-middleware
395
+ # For example:
396
+ #
397
+ # client = Savon.client(
398
+ # middlewares: [
399
+ # [Faraday::Request::UrlEncoded],
400
+ # [Faraday::Response::Logger, { bodies: true }],
401
+ # [Faraday::Adapter::NetHttp]
402
+ # ]
403
+ # )
404
+ def middlewares(middlewares)
405
+ @options[:middlewares] = middlewares
406
+ end
381
407
  end
382
408
 
383
409
  class LocalOptions < Options
@@ -389,7 +415,9 @@ module Savon
389
415
  defaults = {
390
416
  :advanced_typecasting => true,
391
417
  :response_parser => :nokogiri,
392
- :multipart => false
418
+ :multipart => false,
419
+ :body => false,
420
+ :mtom => false
393
421
  }
394
422
 
395
423
  super defaults.merge(options)
@@ -397,7 +425,7 @@ module Savon
397
425
 
398
426
  # The local SOAP header. Expected to be a Hash or respond to #to_s.
399
427
  # Will be merged with the global SOAP header if both are Hashes.
400
- # Otherwise the local option will be prefered.
428
+ # Otherwise the local option will be preferred.
401
429
  def soap_header(header)
402
430
  @options[:soap_header] = header
403
431
  end
@@ -452,12 +480,21 @@ module Savon
452
480
  @options[:attachments] = attachments
453
481
  end
454
482
 
483
+ # Instruct Savon to send attachments using MTOM https://www.w3.org/TR/soap12-mtom/
484
+ def mtom(mtom)
485
+ @options[:mtom] = mtom
486
+ end
487
+
455
488
  # Value of the SOAPAction HTTP header.
456
489
  def soap_action(soap_action)
457
490
  @options[:soap_action] = soap_action
458
491
  end
459
492
 
460
- # Cookies to be used for the next request.
493
+ # Cookies to be used for the next request
494
+ # @param [Hash] cookies cookies associated to nil will be appended as array cookies, if you need a cookie equal to
495
+ # and empty string, set it to ""
496
+ # @example cookies({accept: 'application/json', some-cookie: 'foo', "empty-cookie": "", HttpOnly: nil})
497
+ # # => "accept=application/json; some-cookie=foo; empty-cookie=; HttpOnly"
461
498
  def cookies(cookies)
462
499
  @options[:cookies] = cookies
463
500
  end
@@ -477,6 +514,11 @@ module Savon
477
514
  @options[:response_parser] = parser
478
515
  end
479
516
 
517
+ # Pass already configured Nori instance.
518
+ def nori(nori)
519
+ @options[:nori] = nori
520
+ end
521
+
480
522
  # Instruct Savon to create a multipart response if available.
481
523
  def multipart(multipart)
482
524
  @options[:multipart] = multipart
@@ -485,5 +527,9 @@ module Savon
485
527
  def headers(headers)
486
528
  @options[:headers] = headers
487
529
  end
530
+
531
+ def body(body)
532
+ @options[:body] = body
533
+ end
488
534
  end
489
535
  end
data/lib/savon/request.rb CHANGED
@@ -1,61 +1,99 @@
1
1
  # frozen_string_literal: true
2
- require "httpi"
2
+ require "faraday"
3
3
 
4
4
  module Savon
5
5
  class HTTPRequest
6
6
 
7
- def initialize(globals, http_request = nil)
7
+ def initialize(globals, connection = nil)
8
8
  @globals = globals
9
- @http_request = http_request || HTTPI::Request.new
10
- end
11
-
12
- def build
13
- @http_request
9
+ @connection = connection || Faraday::Connection.new
14
10
  end
15
11
 
16
12
  private
17
13
 
18
14
  def configure_proxy
19
- @http_request.proxy = @globals[:proxy] if @globals.include? :proxy
15
+ connection.proxy = @globals[:proxy] if @globals.include? :proxy
20
16
  end
21
17
 
22
18
  def configure_timeouts
23
- @http_request.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout
24
- @http_request.read_timeout = @globals[:read_timeout] if @globals.include? :read_timeout
25
- @http_request.write_timeout = @globals[:write_timeout] if @globals.include? :write_timeout
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
26
22
  end
27
23
 
28
24
  def configure_ssl
29
- @http_request.auth.ssl.ssl_version = @globals[:ssl_version] if @globals.include? :ssl_version
30
- @http_request.auth.ssl.min_version = @globals[:ssl_min_version] if @globals.include? :ssl_min_version
31
- @http_request.auth.ssl.max_version = @globals[:ssl_max_version] if @globals.include? :ssl_max_version
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
+ connection.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers
40
+
41
+ # No Faraday Equivalent out of box, see: https://lostisland.github.io/faraday/#/customization/ssl-options
42
+ # connection.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file
43
+ # connection.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file
44
+ # connection.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert
45
+ # connection.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password
32
46
 
33
- @http_request.auth.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode
34
- @http_request.auth.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers
47
+ end
35
48
 
36
- @http_request.auth.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file
37
- @http_request.auth.ssl.cert_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key
38
- @http_request.auth.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file
39
- @http_request.auth.ssl.cert = @globals[:ssl_cert] if @globals.include? :ssl_cert
40
- @http_request.auth.ssl.ca_cert_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file
41
- @http_request.auth.ssl.ca_cert_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path
42
- @http_request.auth.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert
43
- @http_request.auth.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store
49
+ def configure_auth
50
+ basic_auth if @globals.include?(:basic_auth)
51
+ ntlm_auth if @globals.include?(:ntlm)
52
+ end
44
53
 
45
- @http_request.auth.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password
54
+ def basic_auth
55
+ connection.request(:authorization, :basic, *@globals[:basic_auth])
46
56
  end
47
57
 
48
- def configure_auth
49
- @http_request.auth.basic(*@globals[:basic_auth]) if @globals.include? :basic_auth
50
- @http_request.auth.digest(*@globals[:digest_auth]) if @globals.include? :digest_auth
51
- @http_request.auth.ntlm(*@globals[:ntlm]) if @globals.include? :ntlm
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
52
66
  end
53
67
 
54
68
  def configure_redirect_handling
55
- if @globals.include? :follow_redirects
56
- @http_request.follow_redirect = @globals[:follow_redirects]
69
+ if @globals[:follow_redirects]
70
+ require 'faraday/follow_redirects'
71
+ connection.response :follow_redirects
57
72
  end
58
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, @globals[:logger], headers: @globals[:log_headers]) if @globals[:log]
81
+ end
82
+
83
+ def configure_middlewares
84
+ @globals[:middlewares].each do |middleware_args|
85
+ connection.use(*middleware_args)
86
+ end
87
+ end
88
+
89
+ def configure_gzip
90
+ if connection.headers['Accept-Encoding'] && connection.headers['Accept-Encoding'].include?('gzip')
91
+ connection.request :gzip
92
+ end
93
+ end
94
+
95
+ protected
96
+ attr_reader :connection
59
97
  end
60
98
 
61
99
  class WSDLRequest < HTTPRequest
@@ -63,18 +101,20 @@ module Savon
63
101
  def build
64
102
  configure_proxy
65
103
  configure_timeouts
66
- configure_headers
67
104
  configure_ssl
68
105
  configure_auth
69
- configure_redirect_handling
70
-
71
- @http_request
106
+ configure_adapter
107
+ configure_middlewares
108
+ configure_logging
109
+ configure_headers
110
+ configure_gzip
111
+ connection
72
112
  end
73
113
 
74
114
  private
75
115
 
76
116
  def configure_headers
77
- @http_request.headers = @globals[:headers] if @globals.include? :headers
117
+ connection.headers = @globals[:headers] if @globals.include? :headers
78
118
  end
79
119
  end
80
120
 
@@ -88,26 +128,36 @@ module Savon
88
128
  def build(options = {})
89
129
  configure_proxy
90
130
  configure_timeouts
91
- configure_headers options[:soap_action], options[:headers]
92
- configure_cookies options[:cookies]
93
131
  configure_ssl
94
132
  configure_auth
133
+ configure_headers(options[:soap_action], options[:headers])
134
+ configure_cookies(options[:cookies])
135
+ configure_adapter
136
+ configure_middlewares
137
+ configure_logging
95
138
  configure_redirect_handling
96
-
97
- @http_request
139
+ configure_gzip
140
+ yield(connection) if block_given?
141
+ connection
98
142
  end
99
143
 
100
144
  private
101
145
 
102
146
  def configure_cookies(cookies)
103
- @http_request.set_cookies(cookies) if cookies
147
+ connection.headers['Cookie'] = cookies.map do |key, value|
148
+ if value.nil?
149
+ key
150
+ else
151
+ "#{key}=#{value}"
152
+ end
153
+ end.join('; ') if cookies
104
154
  end
105
155
 
106
156
  def configure_headers(soap_action, headers)
107
- @http_request.headers = @globals[:headers] if @globals.include? :headers
108
- @http_request.headers.merge!(headers) if headers
109
- @http_request.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action
110
- @http_request.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
157
+ connection.headers = @globals[:headers] if @globals.include? :headers
158
+ connection.headers.merge!(headers) if headers
159
+ connection.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action
160
+ connection.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
111
161
  end
112
162
  end
113
163
  end
@@ -27,27 +27,30 @@ module Savon
27
27
  def log_headers?
28
28
  @globals[:log_headers]
29
29
  end
30
-
31
- private
32
-
33
30
  def log_request(request)
34
- logger.info { "SOAP request: #{request.url}" }
31
+ return unless log?
32
+ logger.info { "SOAP request: #{request.path}" }
35
33
  logger.info { headers_to_log(request.headers) } if log_headers?
36
34
  logger.debug { body_to_log(request.body) }
37
35
  end
38
36
 
39
37
  def log_response(response)
40
- logger.info { "SOAP response (status #{response.code})" }
38
+ return response unless log?
39
+ logger.info { "SOAP response (status #{response.status})" }
41
40
  logger.debug { headers_to_log(response.headers) } if log_headers?
42
41
  logger.debug { body_to_log(response.body) }
42
+ response
43
43
  end
44
44
 
45
+ private
46
+
47
+
45
48
  def headers_to_log(headers)
46
49
  headers.map { |key, value| "#{key}: #{value}" }.join("\n")
47
50
  end
48
51
 
49
52
  def body_to_log(body)
50
- LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s
53
+ LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s.force_encoding(@globals[:encoding])
51
54
  end
52
55
 
53
56
  end
@@ -142,20 +142,16 @@ module Savon
142
142
  end
143
143
 
144
144
  def nori
145
- return @nori if @nori
145
+ return @locals[:nori] if @locals[:nori]
146
146
 
147
- nori_options = {
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]
154
- }
155
-
156
- non_nil_nori_options = nori_options.reject { |_, value| value.nil? }
157
- @nori = Nori.new(non_nil_nori_options)
147
+ @nori ||= Nori.new({
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]
154
+ }.reject { |_, value| value.nil? })
158
155
  end
159
-
160
156
  end
161
157
  end
@@ -3,9 +3,16 @@ module Savon
3
3
  class SOAPFault < Error
4
4
 
5
5
  def self.present?(http, xml = nil)
6
- xml ||= http.body
6
+ xml_orig ||= http.body
7
+ if xml_orig.valid_encoding?
8
+ xml = xml_orig
9
+ else
10
+ xml = xml_orig.encode(
11
+ 'UTF-8', "ISO-8859-1", invalid: :replace, undef: :replace, replace: ''
12
+ )
13
+ end
7
14
  fault_node = xml.include?("Fault>")
8
- soap1_fault = xml.match(/faultcode\/?\>/) && xml.match(/faultstring\/?\>/)
15
+ soap1_fault = xml.match(/faultcode\/?>/) && xml.match(/faultstring\/?>/)
9
16
  soap2_fault = xml.include?("Code>") && xml.include?("Reason>")
10
17
 
11
18
  fault_node && (soap1_fault || soap2_fault)
data/lib/savon/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Savon
3
- VERSION = '2.15.1'
3
+ VERSION = '3.0.0.rc2'
4
4
  end
data/lib/savon.rb CHANGED
@@ -7,6 +7,14 @@ module Savon
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
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: savon
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.1
4
+ version: 3.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Harrington
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-07-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: nori
@@ -25,45 +24,61 @@ dependencies:
25
24
  - !ruby/object:Gem::Version
26
25
  version: '2.4'
27
26
  - !ruby/object:Gem::Dependency
28
- name: httpi
27
+ name: faraday
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - ">="
30
+ - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: '4'
34
- - - "<"
32
+ version: '2.11'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
35
38
  - !ruby/object:Gem::Version
36
- version: '5'
39
+ version: '2.11'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday-gzip
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
37
47
  type: :runtime
38
48
  prerelease: false
39
49
  version_requirements: !ruby/object:Gem::Requirement
40
50
  requirements:
41
- - - ">="
51
+ - - "~>"
42
52
  - !ruby/object:Gem::Version
43
- version: '4'
44
- - - "<"
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: faraday-follow_redirects
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
45
59
  - !ruby/object:Gem::Version
46
- version: '5'
60
+ version: '0.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.3'
47
68
  - !ruby/object:Gem::Dependency
48
69
  name: wasabi
49
70
  requirement: !ruby/object:Gem::Requirement
50
71
  requirements:
51
- - - ">="
72
+ - - ">"
52
73
  - !ruby/object:Gem::Version
53
- version: '3.7'
54
- - - "<"
55
- - !ruby/object:Gem::Version
56
- version: '6'
74
+ version: '5'
57
75
  type: :runtime
58
76
  prerelease: false
59
77
  version_requirements: !ruby/object:Gem::Requirement
60
78
  requirements:
61
- - - ">="
62
- - !ruby/object:Gem::Version
63
- version: '3.7'
64
- - - "<"
79
+ - - ">"
65
80
  - !ruby/object:Gem::Version
66
- version: '6'
81
+ version: '5'
67
82
  - !ruby/object:Gem::Dependency
68
83
  name: akami
69
84
  requirement: !ruby/object:Gem::Requirement
@@ -134,6 +149,34 @@ dependencies:
134
149
  - - "~>"
135
150
  - !ruby/object:Gem::Version
136
151
  version: '2.5'
152
+ - !ruby/object:Gem::Dependency
153
+ name: faraday-net_http_persistent
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '2.1'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '2.1'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rubyntlm
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0.6'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0.6'
137
180
  - !ruby/object:Gem::Dependency
138
181
  name: rack
139
182
  requirement: !ruby/object:Gem::Requirement
@@ -168,6 +211,48 @@ dependencies:
168
211
  - - "<"
169
212
  - !ruby/object:Gem::Version
170
213
  version: '7'
214
+ - !ruby/object:Gem::Dependency
215
+ name: httpclient
216
+ requirement: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ type: :development
222
+ prerelease: false
223
+ version_requirements: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0'
228
+ - !ruby/object:Gem::Dependency
229
+ name: mutex_m
230
+ requirement: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ type: :development
236
+ prerelease: false
237
+ version_requirements: !ruby/object:Gem::Requirement
238
+ requirements:
239
+ - - ">="
240
+ - !ruby/object:Gem::Version
241
+ version: '0'
242
+ - !ruby/object:Gem::Dependency
243
+ name: ostruct
244
+ requirement: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - "~>"
247
+ - !ruby/object:Gem::Version
248
+ version: '0.6'
249
+ type: :development
250
+ prerelease: false
251
+ version_requirements: !ruby/object:Gem::Requirement
252
+ requirements:
253
+ - - "~>"
254
+ - !ruby/object:Gem::Version
255
+ version: '0.6'
171
256
  - !ruby/object:Gem::Dependency
172
257
  name: byebug
173
258
  requirement: !ruby/object:Gem::Requirement
@@ -274,7 +359,6 @@ licenses:
274
359
  - MIT
275
360
  metadata:
276
361
  rubygems_mfa_required: 'true'
277
- post_install_message:
278
362
  rdoc_options: []
279
363
  require_paths:
280
364
  - lib
@@ -289,8 +373,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
289
373
  - !ruby/object:Gem::Version
290
374
  version: '0'
291
375
  requirements: []
292
- rubygems_version: 3.5.6
293
- signing_key:
376
+ rubygems_version: 3.6.7
294
377
  specification_version: 4
295
378
  summary: Heavy metal SOAP client
296
379
  test_files: []