savon 2.17.2 → 2.17.3

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: ffcf3a770d4dd548ec193c9f6d8b8723eef3a661273dc50e8bb5a06ba99f0f13
4
- data.tar.gz: d03d1de9d1bc5b1af722b56dc71e99acc1a46f951f45d377b9eea2974016f2ed
3
+ metadata.gz: cde79beaab26839d499267e94eee281c4f35f34906fd6dfdd4fc4590dc2c710a
4
+ data.tar.gz: 93f661f0156d8331a68e440176d9aaef92d2744974858ee4f866e63f14a210a1
5
5
  SHA512:
6
- metadata.gz: 5a5e490cbf30a0e2216439dc9ceb51493c5b5fec107ce3ff4219eb8c5ded1c9e3b612b6bb8b44c4163d12a97c73415f441f33542b35e22bd7b1908f8cb123fa2
7
- data.tar.gz: c6d5c85d39f406f75e5e0b005b250e5a97da5d60e42ca44fe827d72f97b2fd967a054684be73e8134c19506e8da6e8dcc7222769fa017d2b7c02cf2336e6dc12
6
+ metadata.gz: 3f7436bc2d8c53b89a96a373ab015ce7461b86501549034b99a5ae585a8f25d113bd7e1d1897acc613360d8487bb198eeccf5d0cf4835be7e8f8e44dd4b637a3
7
+ data.tar.gz: 8e45f5f6494c8256878b54b4092f7a253686ddc5c9ea1f98cd372c9ed4b0c784a4b7c885604c70036a1398fe0ac5ff08847a13def8ed931c8ce7d5da6f7475df
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.17.3] - 2026-06-23
9
+
10
+ ### Fixed
11
+
12
+ **Fix Savon::HTTPError compatibility with Faraday transport**
13
+
14
+ * **`Savon::HTTPError` works with the Faraday transport** ([#1050](https://github.com/savonrb/savon/issues/1050)). When a the WSDL could not be fetched under `transport: :faraday`, `Savon::HTTPError#to_s` and `#to_hash` raised `NoMethodError: undefined method 'code'` because they were handed a raw `Faraday::Response`, which exposes `#status` rather than `#code`. Transports now normalize adapter-specific response objects into `Savon::Transport::Response` before they reach `Savon::HTTPError`.
15
+
8
16
  ## [2.17.2] - 2026-06-10
9
17
 
10
18
  **Fix CVE-2026-53510 and restore 2.17.0 cookie regressions**
@@ -6,20 +6,22 @@ module Savon
6
6
  http.error?
7
7
  end
8
8
 
9
- def initialize(http)
10
- @http = http
9
+ def initialize(transport_response)
10
+ @transport_response = transport_response
11
11
  end
12
12
 
13
- attr_reader :http
13
+ def http
14
+ @transport_response
15
+ end
14
16
 
15
17
  def to_s
16
- String.new("HTTP error (#{@http.code})").tap do |str_error|
17
- str_error << ": #{@http.body}" unless @http.body.empty?
18
+ String.new("HTTP error (#{http.code})").tap do |str_error|
19
+ str_error << ": #{http.body}" unless http.body.empty?
18
20
  end
19
21
  end
20
22
 
21
23
  def to_hash
22
- { code: @http.code, headers: @http.headers, body: @http.body }
24
+ { code: http.code, headers: http.headers, body: http.body }
23
25
  end
24
26
  end
25
27
  end
@@ -33,19 +33,20 @@ module Savon
33
33
  def self.create(operation_name, wsdl, globals, transport)
34
34
  if wsdl.document?
35
35
  ensure_name_is_symbol! operation_name
36
- ensure_exists! operation_name, wsdl
36
+ ensure_exists! operation_name, wsdl, transport
37
37
  end
38
38
 
39
39
  new(operation_name, wsdl, globals, transport)
40
40
  end
41
41
 
42
- def self.ensure_exists!(operation_name, wsdl)
42
+ # Verifies the operation is provided by the WSDL.
43
+ def self.ensure_exists!(operation_name, wsdl, transport)
43
44
  unless wsdl.soap_actions.include? operation_name
44
45
  raise UnknownOperationError, "Unable to find SOAP operation: #{operation_name.inspect}\n" \
45
46
  "Operations provided by your service: #{wsdl.soap_actions.inspect}"
46
47
  end
47
48
  rescue Wasabi::Resolver::HTTPError => e
48
- raise HTTPError, e.response
49
+ raise HTTPError, transport.normalize_response(e.response)
49
50
  end
50
51
 
51
52
  def self.ensure_name_is_symbol!(operation_name)
@@ -73,19 +74,19 @@ module Savon
73
74
  # Transport::Response (or legacy HTTPI::Response), the HTTP call
74
75
  # is skipped and that response is used directly.
75
76
  def call(locals = {}, &block)
76
- builder = build(locals, &block)
77
- response = Savon.notify_observers(@name, builder, @globals, @locals)
77
+ builder = build(locals, &block)
78
+ observer_response = Savon.notify_observers(@name, builder, @globals, @locals)
78
79
 
79
- response =
80
- if response.nil?
80
+ transport_response =
81
+ if observer_response.nil?
81
82
  body = builder.to_s
82
83
  headers = soap_headers(builder)
83
84
  @transport.post(endpoint.to_s, headers, body, @locals)
84
85
  else
85
- normalize_observer_response(response)
86
+ normalize_observer_response(observer_response)
86
87
  end
87
88
 
88
- create_response(response)
89
+ Response.new(transport_response, @globals, @locals)
89
90
  end
90
91
 
91
92
  # Builds and returns the HTTPI::Request that would be sent for this
@@ -106,10 +107,6 @@ module Savon
106
107
 
107
108
  private
108
109
 
109
- def create_response(response)
110
- Response.new(response, @globals, @locals)
111
- end
112
-
113
110
  def set_locals(locals, block)
114
111
  locals = LocalOptions.new(locals)
115
112
  BlockInterface.new(locals).evaluate(block) if block
@@ -173,17 +170,12 @@ module Savon
173
170
  # Accepts Transport::Response directly (current contract), wraps
174
171
  # HTTPI::Response with a deprecation warning (legacy observer support),
175
172
  # and raises on anything else.
176
- def normalize_observer_response(response)
177
- return response if response.is_a?(Transport::Response)
173
+ def normalize_observer_response(observer_response)
174
+ return observer_response if observer_response.is_a?(Transport::Response)
178
175
 
179
- if response.is_a?(HTTPI::Response)
176
+ if observer_response.is_a?(HTTPI::Response)
180
177
  warn "Observers returning HTTPI::Response is deprecated - return Savon::Transport::Response instead.", uplevel: 1
181
- return Transport::Response.new(
182
- response.code,
183
- response.headers,
184
- response.body,
185
- cookies: HTTPI::Cookie.list_from_headers(response.headers)
186
- )
178
+ return Transport::Response.from_httpi(observer_response)
187
179
  end
188
180
 
189
181
  raise Error, "Observers need to return a Savon::Transport::Response " \
@@ -9,19 +9,23 @@ module Savon
9
9
  CRLF = /\r\n/
10
10
  WSP = /[\t ]/
11
11
 
12
- def initialize(http, globals, locals)
13
- @http = http
14
- @globals = globals
15
- @locals = locals
16
- @attachments = []
17
- @xml = ''
18
- @has_parsed_body = false
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
19
19
 
20
20
  build_soap_and_http_errors!
21
21
  raise_soap_and_http_errors! if @globals[:raise_errors]
22
22
  end
23
23
 
24
- attr_reader :http, :globals, :locals, :soap_fault, :http_error
24
+ attr_reader :globals, :locals, :soap_fault, :http_error
25
+
26
+ def http
27
+ @transport_response
28
+ end
25
29
 
26
30
  def success?
27
31
  !soap_fault? && !http_error?
@@ -29,11 +33,11 @@ module Savon
29
33
  alias successful? success?
30
34
 
31
35
  def soap_fault?
32
- SOAPFault.present?(@http, xml)
36
+ SOAPFault.present?(http, xml)
33
37
  end
34
38
 
35
39
  def http_error?
36
- HTTPError.present? @http
40
+ HTTPError.present? http
37
41
  end
38
42
 
39
43
  def header
@@ -70,7 +74,7 @@ module Savon
70
74
  parse_body unless @has_parsed_body
71
75
  @xml
72
76
  else
73
- @http.body
77
+ http.body
74
78
  end
75
79
  end
76
80
 
@@ -132,8 +136,8 @@ module Savon
132
136
  end
133
137
 
134
138
  def build_soap_and_http_errors!
135
- @soap_fault = SOAPFault.new(@http, nori, xml) if soap_fault?
136
- @http_error = HTTPError.new(@http) if http_error?
139
+ @soap_fault = SOAPFault.new(http, nori, xml) if soap_fault?
140
+ @http_error = HTTPError.new(http) if http_error?
137
141
  end
138
142
 
139
143
  def raise_soap_and_http_errors!
@@ -38,17 +38,20 @@ module Savon
38
38
  log_request(url, headers, body) if log?
39
39
 
40
40
  faraday_response = @connection.post(url, body, headers)
41
- response = Response.new(
42
- faraday_response.status,
43
- faraday_response.headers.to_h,
44
- faraday_response.body,
45
- cookies: self.class.parse_cookies(faraday_response.headers)
46
- )
41
+ response = normalize_response(faraday_response)
47
42
 
48
43
  log_response(response) if log?
49
44
  response
50
45
  end
51
46
 
47
+ # Normalizes a native Faraday::Response into a Transport::Response.
48
+ #
49
+ # @param faraday_response [Faraday::Response]
50
+ # @return [Transport::Response]
51
+ def normalize_response(faraday_response)
52
+ Response.from_faraday(faraday_response)
53
+ end
54
+
52
55
  # Parses Set-Cookie headers into a Hash of name => value. Accepts both
53
56
  # the Array and String form. Attributes after the first ';' are discarded.
54
57
  def self.parse_cookies(headers)
@@ -33,18 +33,21 @@ module Savon
33
33
 
34
34
  log_request(http_request.url, http_request.headers, http_request.body) if log?
35
35
 
36
- http_response = ::HTTPI.post(http_request, @globals[:adapter])
37
- response = Response.new(
38
- http_response.code,
39
- http_response.headers,
40
- http_response.body,
41
- cookies: ::HTTPI::Cookie.list_from_headers(http_response.headers)
42
- )
36
+ httpi_response = ::HTTPI.post(http_request, @globals[:adapter])
37
+ response = normalize_response(httpi_response)
43
38
 
44
39
  log_response(response) if log?
45
40
  response
46
41
  end
47
42
 
43
+ # Normalizes a native HTTPI::Response into a Transport::Response.
44
+ #
45
+ # @param httpi_response [HTTPI::Response]
46
+ # @return [Transport::Response]
47
+ def normalize_response(httpi_response)
48
+ Response.from_httpi(httpi_response)
49
+ end
50
+
48
51
  # Builds a fully-configured HTTPI::Request.
49
52
  #
50
53
  # @param url [String] the SOAP endpoint URL
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "httpi"
4
+ require "savon/transport/faraday"
5
+
3
6
  module Savon
4
7
  module Transport
5
8
  # Transport-agnostic HTTP response value object.
@@ -11,6 +14,32 @@ module Savon
11
14
  # Array of HTTPI::Cookie, while Faraday responses expose a plain Hash so
12
15
  # Faraday users do not depend on HTTPI types.
13
16
  class Response
17
+ # Builds a Response from an HTTPI::Response.
18
+ #
19
+ # @param httpi_response [HTTPI::Response]
20
+ # @return [Transport::Response]
21
+ def self.from_httpi(httpi_response)
22
+ new(
23
+ httpi_response.code,
24
+ httpi_response.headers,
25
+ httpi_response.body,
26
+ cookies: ::HTTPI::Cookie.list_from_headers(httpi_response.headers)
27
+ )
28
+ end
29
+
30
+ # Builds a Response from a Faraday::Response.
31
+ #
32
+ # @param faraday_response [Faraday::Response]
33
+ # @return [Transport::Response]
34
+ def self.from_faraday(faraday_response)
35
+ new(
36
+ faraday_response.status,
37
+ faraday_response.headers.to_h,
38
+ faraday_response.body,
39
+ cookies: Savon::Transport::Faraday.parse_cookies(faraday_response.headers)
40
+ )
41
+ end
42
+
14
43
  # @param code [Integer] HTTP status code
15
44
  # @param headers [Hash] response headers
16
45
  # @param body [String] response body
data/lib/savon/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Savon
4
- VERSION = '2.17.2'
4
+ VERSION = '2.17.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: savon
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.17.2
4
+ version: 2.17.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Harrington