savon 2.16.0 → 2.17.4

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: 31b29512f6e1e259e34f95ef7cc0d38dd9ac8911d0b1307ca05bf69736985cbb
4
- data.tar.gz: 3ae386e5a8ade343d99e7dc10d644499cf5315fc51d058a8e3f7cb103ad25872
3
+ metadata.gz: 2bba4a9ced6c62286d20de586b56ce7263413fa5e4b51771e87fded4c2433f39
4
+ data.tar.gz: 9327a65ee629c258691084a822b635738b4d31ea38c5bd38f8e3399f11b16cd9
5
5
  SHA512:
6
- metadata.gz: e18a7a8ee40adfab9ab7e316abce340251ef30ad93dd50348640488a282aa5a462d16b46fd987414747c4efe76a04b68ec3e0e50d95de77ac331c4a630549182
7
- data.tar.gz: 686fc36d67eb071a75ce201d16ec4ba3be443c23c48db90f87ee3bece8d76f05f81768acbc182fa32ffbf1623d022d9ca3b23871aa68c8b86c6ca6bd82df049a
6
+ metadata.gz: a1d50a4b5f588cc1e094189f41aada85a2bbecf7b6dbf92b27c6041d7b3eb882808ec69c0d2f0e907cc572cea73c17bad18f82d74c400e4019836200ce1255e9
7
+ data.tar.gz: 9a61b3a21ae8a2c16761fb851c85355cd3e2575e540447ec7ac0fa5cbd42ae5bfa456c26813a5dce907fb9924f2cd6dece545f841f65a970a87445123fe90005
data/CHANGELOG.md CHANGED
@@ -1,17 +1,113 @@
1
1
  # Savon changelog
2
2
 
3
- ## 2.16.0 (2026-05-18)
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.17.4] - 2026-07-03
9
+
10
+ **Restore WS-Addressing headers and fix `:wsse_signature` resolution**
11
+
12
+ ### Fixed
13
+
14
+ * **WS-Addressing headers are populated from the WSDL again** ([#1057](https://github.com/savonrb/savon/issues/1057)). With `use_wsa_headers: true` and no explicit `:soap_action` or `:endpoint`, Savon emitted empty `<wsa:Action xsi:nil="true"/>` and `<wsa:To xsi:nil="true"/>` elements instead of the operation's SOAPAction and service endpoint. Up to 2.16.0 those values were populated as a side effect of building the HTTP request. The 2.17.0 transport refactor removed that step.
15
+ * **A global `:host` override no longer rewrites the WSDL's endpoint.** Resolving the request endpoint with `:host` set replaced host and port of the parsed WSDL address in place, visible as a permanently changed `client.wsdl.endpoint` after the first call. Endpoint and SOAPAction resolution moved into `Savon::EffectiveOptions`, which applies the override to a copy and never touches the WSDL document.
16
+ * **A local `:wsse_signature` no longer crashes when set to `false`.** Passing `wsse_signature: false` to a call raised `NoMethodError: undefined method 'have_document?' for false` while building the WSSE header. The envelope builder and the SOAP header also resolved the option with different rules, so they could disagree on which signature applies. Both now read it through `Savon::EffectiveOptions`, the one place that resolves options settable in both the global and the local scope. A falsy local `:wsse_signature` falls back to the global one. Setting a signature object in either scope is unaffected.
17
+
18
+ ### Deprecated
19
+
20
+ * **`Savon::Builder::WSA_NAMESPACE`.** WS-Addressing emission moved into `Savon::Addressing`, which owns the namespace as `Savon::Addressing::NAMESPACE`. The constant on `Savon::Builder` stays available as an alias and will be removed in Savon 3.
21
+
22
+ ## [2.17.3] - 2026-06-23
23
+
24
+ **Fix Savon::HTTPError compatibility with Faraday transport**
25
+
26
+ ### Fixed
27
+
28
+ * **`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`.
29
+
30
+ ## [2.17.2] - 2026-06-10
31
+
32
+ **Fix CVE-2026-53510 and restore 2.17.0 cookie regressions**
33
+
34
+ ### Fixed
35
+
36
+ * **Fix CVE-2026-53510** `Savon::Model` generated SOAP operation methods by interpolating operation names into Ruby source passed to `module_eval`. An attacker who can control the operation names of a WSDL, can inject Ruby code that executes in the application process. This affects only the `.all_operations` class method provided by `Savon::Model` to automatically register all operations provided by the WSDL. Configuring `Savon::Model` with trusted operation names via `.operations` is safe. Thanks to @connorshea for securely disclosing this, providing a proof and a great report.
37
+ * **`:cookies` request option works again.** The 2.17.0 transport refactor reimplemented cookie handling on top of `Array#map`, which broke callers passing an object that responds to `#cookies` and lost cookie-name de-duplication via `HTTPI::CookieStore`. The HTTPI transport delegates to `HTTPI::Request#set_cookies` again, restoring both shapes.
38
+ * **`response.http.cookies` works again.** 2.17.0's `Savon::Transport::Response` only exposed `code`, `headers`, and `body`. The HTTPI transport now returns `Array<HTTPI::Cookie>` (matching 2.12.1). The Faraday transport returns `Hash<String, String>` so Faraday callers do not need HTTPI types.
39
+ * **`:attachments` now works with a user-supplied `:xml` envelope** ([#761](https://github.com/savonrb/savon/pull/761)). Multipart support shipped in 2.13.0 but only wrapped envelopes Savon built itself. When a caller passed their own `:xml`, attachments were silently dropped.
40
+
41
+ ### Added
42
+
43
+ * **Faraday `:cookies` option accepts a `String` or `Hash`.** Strings are used verbatim, Hashes are formatted as `"name=value; name=value"`. Round-trippable with the Faraday response shape.
44
+ * **Three Nori response-parsing options exposed as Savon globals:** `:empty_tag_value` (default `nil`), `:convert_dashes_to_underscores` (default `true`), and `:scrub_xml` (default `true`). Defaults match Nori's own for backwards compatibility.
45
+
46
+ ### Changed
47
+
48
+ * **Minimum Nori version is now `~> 2.7`** (was `~> 2.4`). Needed for the new parsing options (`:empty_tag_value` arrived in Nori 2.6.0, `:scrub_xml` in 2.7.0). The 2.5–2.7 series also brings fixes callers benefit from automatically: invalid byte sequences parse instead of raising, REXML no longer turns `&lt;` inside CDATA into `<`, `xs:date`/`xs:time`/`xs:dateTime` typecasting was corrected, and Nori stopped monkey-patching `String` and `Object`.
49
+ * **Faraday migration hints are now value-aware and verified.** Each hint prints the caller's actual option value and spells out the full gem/require/setup where needed. Fixed several incorrect examples and added tests to verify every hint.
50
+
51
+ ### Deprecated
52
+
53
+ * Deprecated the global and local `:multipart` options. They have been no-ops since v2.13.0. Specifically since commit 4e7ae5e. Savon detects multipart responses by checking the `Content-Type` header.
54
+
55
+ ## [2.17.1] - 2026-05-21
56
+
57
+ ### Fixed
58
+
59
+ * [#1008](https://github.com/savonrb/savon/pull/1008) - The HTTPI and Faraday transports no longer set an explicit `Content-Length` request header. The underlying HTTP library already computes it from the body. Sending it as well produced a duplicate header on adapters that do not deduplicate (e.g. httpclient), which some servers reject.
60
+ * Requests using `attachments` were sent with a plain `text/xml` Content-Type instead of `multipart/related`. The 2.17.0 transport refactor assembled the request headers before the multipart body was built, leaving `Builder#multipart` empty at header time, so servers received a multipart body labelled as plain XML. 2.16.x and earlier are unaffected.
61
+
62
+ ## [2.17.0] - 2026-05-19
63
+
64
+ **Add opt-in Faraday transport**
65
+
66
+ Callers who set `transport: :faraday` get a memoized `Faraday::Connection` via `client.faraday` and full control over middleware, SSL, auth, and timeouts. Callers who do not set this option see no behavior change. HTTPI remains the default for 2.x.
67
+
68
+ ### Added
69
+
70
+ * `transport: :faraday` global option. Defaults to `:httpi` (#992).
71
+ * `client.faraday` returns a memoized `Faraday::Connection` for configuring middleware, SSL, auth, and timeouts when using the Faraday transport.
72
+ * `Savon.client` raises if `transport: :faraday` is set but the faraday gem is not installed, or if any httpi-specific global option (`proxy`, timeouts, `ssl`, auth, `adapter`) is set alongside it. All conflicts are reported with their Faraday equivalents.
73
+
74
+ ### Changed
75
+
76
+ * Observers must return `Savon::Transport::Response` (or `nil`) instead of `HTTPI::Response`.
77
+
78
+ ### Deprecated
79
+
80
+ * The HTTPI transport (currently the default) and all HTTPI-specific global options enumerated in `Savon::FaradayMigrationHint::OPTIONS` (`proxy`, `open_timeout`, `read_timeout`, `write_timeout`, the `ssl_*` family, `basic_auth`, `digest_auth`, `ntlm`, `follow_redirects`, `adapter`) will be removed in 3.0. Migrate to `transport: :faraday`. `FaradayMigrationHint` shows the Faraday equivalent for each option.
81
+ * Returning `HTTPI::Response` from observers emits a deprecation warning and will be removed in 3.0. Return `Savon::Transport::Response` instead.
82
+
83
+ The Faraday transport unblocks:
84
+
85
+ * redirect following for WSDL fetches via `faraday-follow-redirects` middleware (#1033, savonrb/wasabi#18)
86
+ * digest authentication via `faraday-digestauth` middleware (#1021, savonrb/httpi#250)
87
+ * proxy authentication with special characters in passwords (#941)
88
+ * and setting an `Accept` header for WSDL requests from Rails apps (savonrb/wasabi#115)
89
+
90
+ ## [2.16.0] - 2026-05-18
4
91
 
5
92
  **Restore compatibility**
6
93
 
7
94
  If you stayed on 2.12.1 because a later version broke something, this release is for you. The fixes below target the most commonly reported upgrade blockers. Existing code should work without modification.
8
95
 
9
- * Fix: Restore `Savon::Response#hash` removed in 2.14.0 (#985). Callers on 2.12.1 that use `response.hash` get the soap body back instead of Ruby's integer object id. A deprecation warning is emitted on each call. Use `#full_hash` going forward.
10
- * Fix: Require wasabi >= 5.1.0 (#1015, #1016). Wasabi 4.x used message names as soap body element names and SOAPAction header values instead of operation names (savonrb/wasabi#122), causing servers to return a fault or reject the action for operations whose message name carried an `In` suffix.
11
- * Fix: Stop dumping all WSDL namespaces into every soap envelope (#1014, #942). 2.13.0 injected every namespace from the entire WSDL document into each request, including structural ones that have no place in a request body. Strict servers reject envelopes with unexpected or duplicate declarations.
12
- * Fix: Raise a proper `SOAPFault` instead of a raw exception when `soap:Fault` contains invalid encoding (#923).
13
- * Fix: `SOAPFault.present?` was ignoring its `xml` argument and always operating on the instance's own body.
14
- * Change: Added Ruby 3.4 (#1024) and Ruby 4.0 (#1039) to the CI test matrix.
96
+ ### Fixed
97
+
98
+ * Restore `Savon::Response#hash` removed in 2.14.0 (#985). Callers on 2.12.1 that use `response.hash` get the soap body back instead of Ruby's integer object id.
99
+ * Require wasabi >= 5.1.0 (#1015, #1016). Wasabi 4.x used message names as soap body element names and SOAPAction header values instead of operation names (savonrb/wasabi#122), causing servers to return a fault or reject the action for operations whose message name carried an `In` suffix.
100
+ * Stop dumping all WSDL namespaces into every soap envelope (#1014, #942). 2.13.0 injected every namespace from the entire WSDL document into each request, including structural ones that have no place in a request body. Strict servers reject envelopes with unexpected or duplicate declarations.
101
+ * Raise a proper `SOAPFault` instead of a raw exception when `soap:Fault` contains invalid encoding (#923).
102
+ * `SOAPFault.present?` was ignoring its `xml` argument and always operating on the instance's own body.
103
+
104
+ ### Changed
105
+
106
+ * Added Ruby 3.4 (#1024) and Ruby 4.0 (#1039) to the CI test matrix.
107
+
108
+ ### Deprecated
109
+
110
+ * `Savon::Response#hash` emits a deprecation warning on each call. Use `#full_hash` going forward. Will be removed in 3.0.
15
111
 
16
112
  ## 2.15.1 (2024-07-08)
17
113
 
@@ -1165,3 +1261,10 @@ Pay attention to the following list and read the updated Wiki: http://wiki.githu
1165
1261
  ## 0.5.0 (2009-11-29)
1166
1262
 
1167
1263
  * Complete rewrite and public release.
1264
+
1265
+ [2.17.4]: https://github.com/savonrb/savon/compare/v2.17.3...v2.17.4
1266
+ [2.17.3]: https://github.com/savonrb/savon/compare/v2.17.2...v2.17.3
1267
+ [2.17.2]: https://github.com/savonrb/savon/compare/v2.17.1...v2.17.2
1268
+ [2.17.1]: https://github.com/savonrb/savon/compare/v2.17.0...v2.17.1
1269
+ [2.17.0]: https://github.com/savonrb/savon/compare/v2.16.0...v2.17.0
1270
+ [2.16.0]: https://github.com/savonrb/savon/compare/v2.15.1...v2.16.0
data/README.md CHANGED
@@ -2,82 +2,88 @@
2
2
 
3
3
  Heavy metal SOAP client
4
4
 
5
- [Documentation](https://www.rubydoc.info/gems/savon/) | [Support](https://stackoverflow.com/questions/tagged/savon) |
6
- [Mailing list](https://groups.google.com/forum/#!forum/savonrb)
7
-
8
5
  [![Ruby](https://github.com/savonrb/savon/actions/workflows/ci.yml/badge.svg)](https://github.com/savonrb/savon/actions/workflows/ci.yml)
9
6
  [![Gem Version](https://badge.fury.io/rb/savon.svg)](http://badge.fury.io/rb/savon)
10
7
  [![Coverage Status](https://coveralls.io/repos/savonrb/savon/badge.svg)](https://coveralls.io/r/savonrb/savon)
11
8
 
9
+ Savon is a SOAP client for Ruby. [SOAP is the protocol](https://www.w3.org/TR/soap/) spoken by many enterprise systems. When they hand you a WSDL URL or file instead of a REST spec, it's SOAP. Savon reads the WSDL, maps available operations to Ruby symbols, converts Ruby hashes to SOAP envelopes and turns XML responses into hashes you can work with.
12
10
 
13
- ## Installation
14
-
15
- Savon is available through [Rubygems](http://rubygems.org/gems/savon) and can be installed via:
16
-
17
- ```
18
- $ gem install savon
19
- ```
11
+ Full documentation is at [savonrb.com](https://savonrb.com).
20
12
 
21
- or add it to your Gemfile like this:
13
+ ## Installation
22
14
 
23
- ```
24
- gem 'savon', '~> 2.15.0'
15
+ ```ruby
16
+ gem 'savon', '~> 2.17'
25
17
  ```
26
18
 
27
- ## Usage example
19
+ ## Usage
28
20
 
29
- ``` ruby
21
+ ```ruby
30
22
  require 'savon'
31
23
 
32
- # create a client for the service
33
- client = Savon.client(wsdl: 'http://service.example.com?wsdl')
34
-
35
- # or: create a client with a wsdl provided as a string
36
- client = Savon.client do |config|
37
- wsdl_content = File.read("/path/to/wsdl")
38
- config.wsdl wsdl_content
39
- end
24
+ # Point Savon to a local or remote WSDL document
25
+ client = Savon.client(wsdl: 'https://service.example.com?wsdl')
40
26
 
27
+ # See what the service exposes
41
28
  client.operations
42
- # => [:find_user, :list_users]
29
+ # => [:find_user, :create_user]
43
30
 
44
- # call the 'findUser' operation
31
+ # Make a request and work with the response
45
32
  response = client.call(:find_user, message: { id: 42 })
33
+ response.body[:find_user_response]
34
+ # => { id: 42, name: "Hoff" }
46
35
 
47
- response.body
48
- # => { find_user_response: { id: 42, name: 'Hoff' } }
36
+ # Savon raises a Savon::SOAPFault when the server returns a SOAP fault
37
+ rescue Savon::SOAPFault => e
38
+ puts e.to_hash.dig(:fault, :faultstring)
49
39
  ```
50
40
 
51
- For more examples, you should check out the
52
- [integration tests](https://github.com/savonrb/savon/tree/version2/spec/integration).
41
+ Enable logging to see the raw SOAP envelopes. That's probably the single most useful thing while getting a new integration working:
53
42
 
54
- ## Ruby version support
43
+ ```ruby
44
+ client = Savon.client(
45
+ wsdl: 'https://service.example.com?wsdl',
46
+ pretty_print_xml: true,
47
+ log: true
48
+ )
49
+ ```
55
50
 
56
- Every savon release is tested with contemporary supported versions of ruby. Historical compatibility information:
51
+ ### Authentication
57
52
 
58
- * `main` - same support as Ruby
59
- * 2.15.x - MRI 3.0, 3.1, 3.2, 3.3
60
- * 2.13.x, 2.14.x - MRI 2.7, 3.0, 3.1
61
- * 2.12.x - MRI 2.2, 2.3, 2.4, 2.5
62
- * 2.11.x - MRI 2.0, 2.1, 2.2, and 2.3
53
+ Most enterprise services require authentication. Common options:
63
54
 
64
- If you are running MRI 1.8.7, try a 2.6.x release.
55
+ ```ruby
56
+ # HTTP basic auth
57
+ Savon.client(wsdl: '...', basic_auth: ['user', 'secret'])
65
58
 
66
- Most changes are not backported to older versions of savon, or unsupported ruby versions.
59
+ # WS-Security (WSSE)
60
+ Savon.client(wsdl: '...', wsse_auth: ['user', 'secret', :digest], wsse_timestamp: true)
61
+ ```
67
62
 
68
- ## Running tests
63
+ See [Authentication](https://savonrb.com/version2/globals.html) on the website for HTTP digest, NTLM, and certificate-based options.
69
64
 
70
- ```bash
71
- $ bundle install
72
- $ bundle exec rspec
73
- ```
65
+ ### Transport
66
+
67
+ Savon uses [HTTPI](https://github.com/savonrb/httpi) for HTTP by default. Since v2.17.0 you can opt-in to use Faraday by passing `transport: :faraday` which exposes the Faraday connection at `client.faraday` for you to configure. Savon is only adding some SOAP-specifics on top.
68
+
69
+ ## Ruby support
70
+
71
+ Savon 2.x requires Ruby >= 3.0.0, kept as the lower bound for backward compatibility. Note that Ruby 3.0–3.3 are EOL or security-only. Ruby 3.4+ is the minimum version with active maintenance.
72
+
73
+ ## Versioning & stability
74
+
75
+ Savon follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). The changelog format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
76
+
77
+ **The 2.x line is anchored on 2.12.1.** Every 2.x release is supposed to be safe to upgrade to from 2.12.1, and anything that worked in 2.12.1 keeps working. We do not remove or rename public APIs in the 2.x line. New behavior is opt-in and requires an explicit option change. If you found a problem with that, please let us know.
78
+
79
+ We only soft-deprecate APIs we plan to remove. The `Deprecated` sections of the [changelog](CHANGELOG.md) list every API that is planned to be removed with version 3.0.
74
80
 
75
- ## FAQ
81
+ Callers on 2.13.0–2.15.x may see specific post-2.12.1 behaviors restored to the 2.12.1 contract. This is intentional.
76
82
 
77
- * URI::InvalidURIError -- if you see this error, then it is likely that the http client you are using cannot parse the URI for your WSDL. Try `gem install httpclient` or add it to your `Gemfile`.
78
- - See https://github.com/savonrb/savon/issues/488 for more info
83
+ ## Known limitations
79
84
 
85
+ **WSDL imports are not followed.** Savon parses only the root WSDL document via [Wasabi](https://github.com/savonrb/wasabi). Messages, port types, and bindings defined in imported files are invisible to Savon. If you control the WSDL, merge the imported elements into the root document and pass that to Savon as a string. If you need full import support, the [WSDL](https://rubygems.org/gems/wsdl) gem is an alternative.
80
86
 
81
- ## Documentation
87
+ ## Contributing
82
88
 
83
- Please be sure to [read the documentation](https://www.rubydoc.info/github/savonrb/savon/).
89
+ See [CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
@@ -10,5 +12,7 @@ RSpec::Core::RakeTask.new "spec:integration" do |t|
10
12
  t.pattern = "spec/integration/**/*_spec.rb"
11
13
  end
12
14
 
13
- task :default => :spec
14
- task :test => :spec
15
+ desc "Alias for spec task"
16
+ task test: :spec
17
+
18
+ task default: :spec
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "gyoku"
4
+ require "securerandom"
5
+
6
+ module Savon
7
+ # Emits the WS-Addressing message addressing properties of a request
8
+ # (WS-Addressing 1.0 - Core §3.2): +wsa:Action+, +wsa:To+ and +wsa:MessageID+.
9
+ #
10
+ # This object owns everything Savon knows about WS-Addressing: the namespace,
11
+ # whether the headers are emitted at all, and how the resolved SOAPAction and
12
+ # endpoint map onto the addressing properties. It works on plain values.
13
+ # {Savon::Header} resolves them via {Savon::EffectiveOptions} and constructs
14
+ # this object, keeping option resolution and WSA emission separate.
15
+ class Addressing
16
+ # The WS-Addressing 1.0 namespace,
17
+ # https://www.w3.org/TR/ws-addr-core/#namespaces.
18
+ NAMESPACE = "http://www.w3.org/2005/08/addressing"
19
+
20
+ # @param enabled [Object] whether the headers are emitted, any truthy value
21
+ # counts (the +:use_wsa_headers+ global)
22
+ # @param action [String, nil] the resolved SOAPAction, emitted as
23
+ # +wsa:Action+, or +nil+ when the caller disabled it
24
+ # @param to [URI, String, nil] the resolved endpoint, emitted as +wsa:To+
25
+ def initialize(enabled:, action: nil, to: nil)
26
+ @enabled = enabled
27
+ @action = action
28
+ @to = to
29
+ end
30
+
31
+ # @return [Boolean] whether the WS-Addressing headers are emitted
32
+ def enabled?
33
+ !!@enabled
34
+ end
35
+
36
+ # Returns the namespace declaration the enclosing Header element needs.
37
+ # The +wsa+ prefix is declared once on the Header, not on each child.
38
+ #
39
+ # @return [Hash{String => String}] +{"xmlns:wsa" => NAMESPACE}+, or an
40
+ # empty Hash when disabled
41
+ def namespace_attributes
42
+ enabled? ? { "xmlns:wsa" => NAMESPACE } : {}
43
+ end
44
+
45
+ # Renders the three addressing properties, each rendering with a freshly
46
+ # generated +wsa:MessageID+. A +nil+ action or destination is emitted as
47
+ # an +xsi:nil+ element rather than being dropped.
48
+ #
49
+ # @return [String] the header elements, or an empty String when disabled
50
+ def to_xml
51
+ return "" unless enabled?
52
+
53
+ Gyoku.xml(
54
+ "wsa:Action" => @action,
55
+ "wsa:To" => @to,
56
+ "wsa:MessageID" => "urn:uuid:#{SecureRandom.uuid}"
57
+ )
58
+ end
59
+ end
60
+ end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Savon
3
4
  class BlockInterface
4
-
5
5
  def initialize(target)
6
6
  @target = target
7
7
  end
8
8
 
9
9
  def evaluate(block)
10
- if block.arity > 0
10
+ if block.arity.positive?
11
11
  block.call(@target)
12
12
  else
13
- @original = eval("self", block.binding)
13
+ @original = eval("self", block.binding, __FILE__, __LINE__)
14
14
  instance_eval(&block)
15
15
  end
16
16
  end
@@ -22,6 +22,5 @@ module Savon
22
22
  rescue NoMethodError
23
23
  @original.send(method, *args, &block)
24
24
  end
25
-
26
25
  end
27
26
  end
data/lib/savon/builder.rb CHANGED
@@ -1,40 +1,59 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require "savon/addressing"
2
4
  require "savon/header"
3
5
  require "savon/message"
6
+ require "savon/effective_options"
4
7
  require "nokogiri"
5
8
  require "builder"
6
9
  require "gyoku"
7
10
 
8
11
  module Savon
12
+ # Builds the SOAP request for an operation: the XML envelope, or
13
+ # a multipart/related message when the request carries attachments
14
+ # (SOAP with Attachments, https://www.w3.org/TR/SOAP-attachments).
15
+ #
16
+ # It owns the envelope structure, its namespace declarations, and the body,
17
+ # and signs the document when a WSSE signature is present. Message-body
18
+ # serialization is delegated to {Savon::Message}, the Header element to
19
+ # {Savon::Header}, and Hash-to-XML conversion to Gyoku.
9
20
  class Builder
10
21
  attr_reader :multipart
11
22
 
12
23
  SCHEMA_TYPES = {
13
24
  "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
14
25
  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
15
- }
26
+ }.freeze
16
27
 
17
28
  SOAP_NAMESPACE = {
18
29
  1 => "http://schemas.xmlsoap.org/soap/envelope/",
19
30
  2 => "http://www.w3.org/2003/05/soap-envelope"
20
- }
31
+ }.freeze
21
32
 
22
- WSA_NAMESPACE = "http://www.w3.org/2005/08/addressing"
33
+ # @deprecated Use {Savon::Addressing::NAMESPACE} instead. This alias will
34
+ # be removed in Savon 3.0.
35
+ WSA_NAMESPACE = Addressing::NAMESPACE
23
36
 
37
+ # @param operation_name [Symbol] the SOAP operation being called
38
+ # @param wsdl [Wasabi::Document] the parsed WSDL, or an empty document when
39
+ # the client was configured without one
40
+ # @param globals [Savon::GlobalOptions] client-level options
41
+ # @param locals [Savon::LocalOptions] per-request options
24
42
  def initialize(operation_name, wsdl, globals, locals)
25
43
  @operation_name = operation_name
26
44
 
27
45
  @wsdl = wsdl
28
46
  @globals = globals
29
47
  @locals = locals
30
- @signature = @locals[:wsse_signature] || @globals[:wsse_signature]
48
+ @effective = EffectiveOptions.new(operation_name, wsdl, globals, locals)
49
+ @signature = @effective.wsse_signature
31
50
 
32
51
  @types = convert_type_definitions_to_hash
33
52
  @used_namespaces = convert_type_namespaces_to_hash
34
53
  end
35
54
 
36
55
  def pretty
37
- Nokogiri.XML(to_s).to_xml(:indent => 2)
56
+ Nokogiri.XML(to_s).to_xml(indent: 2)
38
57
  end
39
58
 
40
59
  def build_document
@@ -61,40 +80,50 @@ module Savon
61
80
  end
62
81
  end
63
82
 
83
+ # Returns the XML attributes of the envelope's Header element, provided
84
+ # by the header's sections.
85
+ #
86
+ # @return [Hash{String => String}]
64
87
  def header_attributes
65
- @globals[:use_wsa_headers] ? { 'xmlns:wsa' => WSA_NAMESPACE } : {}
88
+ header.attributes
66
89
  end
67
90
 
68
91
  def body_attributes
69
92
  @body_attributes ||= @signature.nil? ? {} : @signature.body_attributes
70
93
  end
71
94
 
95
+ # Returns the request body as a String. When the caller supplies a pre-built
96
+ # envelope via the :xml local option it is used verbatim, but it must still
97
+ # be wrapped in a multipart message when :attachments are present.
98
+ # Otherwise the attachments are silently dropped.
72
99
  def to_s
73
- return @locals[:xml] if @locals.include? :xml
74
- build_document
100
+ if @locals.include?(:xml)
101
+ xml = @locals[:xml]
102
+ @locals[:attachments] ? build_multipart_message(xml) : xml
103
+ else
104
+ build_document
105
+ end
75
106
  end
76
107
 
77
108
  private
78
109
 
79
110
  def convert_type_definitions_to_hash
80
- @wsdl.type_definitions.inject({}) do |memo, (path, type)|
111
+ @wsdl.type_definitions.each_with_object({}) do |(path, type), memo|
81
112
  memo[path] = type
82
- memo
83
113
  end
84
114
  end
85
115
 
86
116
  def convert_type_namespaces_to_hash
87
- @wsdl.type_namespaces.inject({}) do |memo, (path, uri)|
117
+ @wsdl.type_namespaces.each_with_object({}) do |(path, uri), memo|
88
118
  key, value = use_namespace(path, uri)
89
119
  memo[key] = value
90
- memo
91
120
  end
92
121
  end
93
122
 
94
123
  def use_namespace(path, uri)
95
124
  @internal_namespace_count ||= 0
96
125
 
97
- unless identifier = namespace_by_uri(uri)
126
+ unless (identifier = namespace_by_uri(uri))
98
127
  wsdl_identifier = @wsdl.document? ? @wsdl.parser.namespaces.key(uri) : nil
99
128
  # The prefix may already be taken by the target namespace or a user-supplied
100
129
  # :namespaces override - fall back to ins0, ins1... rather than overwriting it.
@@ -120,7 +149,7 @@ module Savon
120
149
  @globals[:namespace] || @wsdl.namespace
121
150
 
122
151
  # check env_namespace
123
- namespaces["xmlns#{env_namespace && env_namespace != "" ? ":#{env_namespace}" : ''}"] =
152
+ namespaces["xmlns#{env_namespace && env_namespace != '' ? ":#{env_namespace}" : ''}"] =
124
153
  SOAP_NAMESPACE[@globals[:soap_version]]
125
154
 
126
155
  namespaces
@@ -132,13 +161,14 @@ module Savon
132
161
  end
133
162
 
134
163
  def header
135
- @header ||= Header.new(@globals, @locals)
164
+ @header ||= Header.new(@globals, @effective)
136
165
  end
137
166
 
138
167
  def namespaced_message_tag
139
168
  tag_name = message_tag
140
- return [tag_name] if @wsdl.document? and @wsdl.soap_input(@operation_name.to_sym).is_a?(Hash)
141
- if namespace_identifier == nil
169
+ return [tag_name] if @wsdl.document? && @wsdl.soap_input(@operation_name.to_sym).is_a?(Hash)
170
+
171
+ if namespace_identifier.nil?
142
172
  [tag_name, message_attributes]
143
173
  elsif @used_namespaces[[tag_name.to_s]]
144
174
  [@used_namespaces[[tag_name.to_s]], tag_name, message_attributes]
@@ -156,6 +186,7 @@ module Savon
156
186
  message_tag = serialized_message_tag[1]
157
187
  @wsdl.soap_input(@operation_name.to_sym)[message_tag].each_pair do |message, type|
158
188
  break if @locals[:message].nil?
189
+
159
190
  message_locals = @locals[:message][StringUtils.snakecase(message).to_sym]
160
191
  message_content = Message.new(message_tag, namespace_identifier, @types, @used_namespaces, message_locals, :unqualified, @globals[:convert_request_keys_to], @globals[:unwrap]).to_s
161
192
  messages += "<#{message} xsi:type=\"#{type.join(':')}\">#{message_content}</#{message}>"
@@ -169,7 +200,7 @@ module Savon
169
200
  message_tag = wsdl_tag_name.keys.first if wsdl_tag_name.is_a?(Hash)
170
201
  message_tag ||= @locals[:message_tag]
171
202
  message_tag ||= wsdl_tag_name
172
- message_tag ||= Gyoku.xml_tag(@operation_name, :key_converter => @globals[:convert_request_keys_to])
203
+ message_tag ||= Gyoku.xml_tag(@operation_name, key_converter: @globals[:convert_request_keys_to])
173
204
 
174
205
  message_tag.to_sym
175
206
  end
@@ -179,7 +210,7 @@ module Savon
179
210
  end
180
211
 
181
212
  def body_message
182
- if @wsdl.document? and @wsdl.soap_input(@operation_name.to_sym).is_a?(Hash)
213
+ if @wsdl.document? && @wsdl.soap_input(@operation_name.to_sym).is_a?(Hash)
183
214
  serialized_messages
184
215
  else
185
216
  message.to_s
@@ -213,7 +244,7 @@ module Savon
213
244
 
214
245
  def builder
215
246
  builder = ::Builder::XmlMarkup.new
216
- builder.instruct!(:xml, :encoding => @globals[:encoding])
247
+ builder.instruct!(:xml, encoding: @globals[:encoding])
217
248
  builder
218
249
  end
219
250
 
@@ -246,7 +277,7 @@ module Savon
246
277
 
247
278
  # the mail.body.encoded algorithm reorders the parts, default order is [ "text/plain", "text/enriched", "text/html" ]
248
279
  # should redefine the sort order, because the soap request xml should be the first
249
- multipart_message.body.set_sort_order [ "text/xml" ]
280
+ multipart_message.body.set_sort_order ["text/xml"]
250
281
 
251
282
  multipart_message.body.encoded(multipart_message.content_transfer_encoding)
252
283
  end
@@ -261,10 +292,10 @@ module Savon
261
292
  end
262
293
  multipart_message.add_part xml_part
263
294
 
264
- #request.headers["Content-Type"] = "multipart/related; boundary=\"#{multipart_message.body.boundary}\"; type=\"text/xml\"; start=\"#{xml_part.content_id}\""
295
+ # request.headers["Content-Type"] = "multipart/related; boundary=\"#{multipart_message.body.boundary}\"; type=\"text/xml\"; start=\"#{xml_part.content_id}\""
265
296
  @multipart = {
266
297
  multipart_boundary: multipart_message.body.boundary,
267
- start: xml_part.content_id,
298
+ start: xml_part.content_id
268
299
  }
269
300
 
270
301
  multipart_message