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 +4 -4
- data/CHANGELOG.md +110 -7
- data/README.md +54 -48
- data/Rakefile +6 -2
- data/lib/savon/addressing.rb +60 -0
- data/lib/savon/block_interface.rb +3 -4
- data/lib/savon/builder.rb +54 -23
- data/lib/savon/client.rb +44 -13
- data/lib/savon/effective_options.rb +124 -0
- data/lib/savon/faraday_migration_hint.rb +186 -0
- data/lib/savon/header.rb +87 -40
- data/lib/savon/http_error.rb +8 -8
- data/lib/savon/log_message.rb +2 -3
- data/lib/savon/message.rb +6 -7
- data/lib/savon/mock/expectation.rb +37 -23
- data/lib/savon/mock/spec_helper.rb +7 -11
- data/lib/savon/mock.rb +1 -0
- data/lib/savon/model.rb +25 -25
- data/lib/savon/operation.rb +110 -74
- data/lib/savon/options.rb +274 -157
- data/lib/savon/qualified_message.rb +2 -1
- data/lib/savon/response.rb +40 -31
- data/lib/savon/soap_fault.rb +2 -3
- data/lib/savon/string_utils.rb +3 -4
- data/lib/savon/transport/faraday.rb +101 -0
- data/lib/savon/transport/httpi.rb +138 -0
- data/lib/savon/transport/logging.rb +60 -0
- data/lib/savon/transport/response.rb +73 -0
- data/lib/savon/version.rb +2 -1
- data/lib/savon.rb +2 -3
- metadata +85 -83
- data/lib/savon/request.rb +0 -113
- data/lib/savon/request_logger.rb +0 -54
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2bba4a9ced6c62286d20de586b56ce7263413fa5e4b51771e87fded4c2433f39
|
|
4
|
+
data.tar.gz: 9327a65ee629c258691084a822b635738b4d31ea38c5bd38f8e3399f11b16cd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1d50a4b5f588cc1e094189f41aada85a2bbecf7b6dbf92b27c6041d7b3eb882808ec69c0d2f0e907cc572cea73c17bad18f82d74c400e4019836200ce1255e9
|
|
7
|
+
data.tar.gz: 9a61b3a21ae8a2c16761fb851c85355cd3e2575e540447ec7ac0fa5cbd42ae5bfa456c26813a5dce907fb9924f2cd6dece545f841f65a970a87445123fe90005
|
data/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,113 @@
|
|
|
1
1
|
# Savon changelog
|
|
2
2
|
|
|
3
|
-
|
|
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 `<` 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
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
[](https://github.com/savonrb/savon/actions/workflows/ci.yml)
|
|
9
6
|
[](http://badge.fury.io/rb/savon)
|
|
10
7
|
[](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
|
-
|
|
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
|
-
|
|
13
|
+
## Installation
|
|
22
14
|
|
|
23
|
-
```
|
|
24
|
-
gem 'savon', '~> 2.
|
|
15
|
+
```ruby
|
|
16
|
+
gem 'savon', '~> 2.17'
|
|
25
17
|
```
|
|
26
18
|
|
|
27
|
-
## Usage
|
|
19
|
+
## Usage
|
|
28
20
|
|
|
29
|
-
```
|
|
21
|
+
```ruby
|
|
30
22
|
require 'savon'
|
|
31
23
|
|
|
32
|
-
#
|
|
33
|
-
client = Savon.client(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, :
|
|
29
|
+
# => [:find_user, :create_user]
|
|
43
30
|
|
|
44
|
-
#
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
+
### Authentication
|
|
57
52
|
|
|
58
|
-
|
|
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
|
-
|
|
55
|
+
```ruby
|
|
56
|
+
# HTTP basic auth
|
|
57
|
+
Savon.client(wsdl: '...', basic_auth: ['user', 'secret'])
|
|
65
58
|
|
|
66
|
-
|
|
59
|
+
# WS-Security (WSSE)
|
|
60
|
+
Savon.client(wsdl: '...', wsse_auth: ['user', 'secret', :digest], wsse_timestamp: true)
|
|
61
|
+
```
|
|
67
62
|
|
|
68
|
-
|
|
63
|
+
See [Authentication](https://savonrb.com/version2/globals.html) on the website for HTTP digest, NTLM, and certificate-based options.
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
87
|
+
## Contributing
|
|
82
88
|
|
|
83
|
-
|
|
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
|
-
|
|
14
|
-
task :
|
|
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
|
|
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
|
-
|
|
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
|
-
@
|
|
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(:
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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.
|
|
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.
|
|
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 !=
|
|
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, @
|
|
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?
|
|
141
|
-
|
|
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, :
|
|
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?
|
|
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, :
|
|
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 [
|
|
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
|