savon 2.11.2 → 2.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/CHANGELOG.md +103 -73
- data/CONTRIBUTING.md +15 -19
- data/Gemfile +2 -7
- data/README.md +26 -15
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png +0 -0
- data/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
- data/coverage/assets/0.12.3/application.css +1 -0
- data/coverage/assets/0.12.3/application.js +7 -0
- data/coverage/assets/0.12.3/colorbox/border.png +0 -0
- data/coverage/assets/0.12.3/colorbox/controls.png +0 -0
- data/coverage/assets/0.12.3/colorbox/loading.gif +0 -0
- data/coverage/assets/0.12.3/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.12.3/favicon_green.png +0 -0
- data/coverage/assets/0.12.3/favicon_red.png +0 -0
- data/coverage/assets/0.12.3/favicon_yellow.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.12.3/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.12.3/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.12.3/loading.gif +0 -0
- data/coverage/assets/0.12.3/magnify.png +0 -0
- data/coverage/index.html +21518 -0
- data/lib/savon/block_interface.rb +1 -0
- data/lib/savon/builder.rb +95 -29
- data/lib/savon/client.rb +1 -0
- data/lib/savon/core_ext/string.rb +1 -0
- data/lib/savon/header.rb +2 -6
- data/lib/savon/http_error.rb +4 -4
- data/lib/savon/log_message.rb +1 -0
- data/lib/savon/message.rb +1 -0
- data/lib/savon/mock/expectation.rb +1 -0
- data/lib/savon/mock/spec_helper.rb +1 -0
- data/lib/savon/mock.rb +1 -0
- data/lib/savon/model.rb +1 -0
- data/lib/savon/operation.rb +20 -18
- data/lib/savon/options.rb +70 -1
- data/lib/savon/qualified_message.rb +5 -4
- data/lib/savon/request.rb +18 -3
- data/lib/savon/request_logger.rb +8 -2
- data/lib/savon/response.rb +49 -2
- data/lib/savon/soap_fault.rb +2 -3
- data/lib/savon/version.rb +2 -1
- data/lib/savon.rb +1 -0
- data/savon.gemspec +10 -9
- data/spec/fixtures/response/empty_soap_fault.xml +13 -0
- data/spec/fixtures/response/no_body.xml +1 -0
- data/spec/fixtures/wsdl/elements_in_types.xml +43 -0
- data/spec/integration/support/application.rb +34 -2
- data/spec/integration/support/server.rb +1 -0
- data/spec/integration/zipcode_example_spec.rb +5 -8
- data/spec/savon/builder_spec.rb +2 -1
- data/spec/savon/client_spec.rb +5 -4
- data/spec/savon/core_ext/string_spec.rb +2 -1
- data/spec/savon/features/message_tag_spec.rb +2 -1
- data/spec/savon/http_error_spec.rb +9 -1
- data/spec/savon/log_message_spec.rb +2 -1
- data/spec/savon/message_spec.rb +2 -11
- data/spec/savon/mock_spec.rb +2 -1
- data/spec/savon/model_spec.rb +2 -1
- data/spec/savon/multipart_request_spec.rb +46 -0
- data/spec/savon/observers_spec.rb +2 -1
- data/spec/savon/operation_spec.rb +20 -43
- data/spec/savon/options_spec.rb +84 -5
- data/spec/savon/qualified_message_spec.rb +35 -1
- data/spec/savon/request_logger_spec.rb +2 -1
- data/spec/savon/request_spec.rb +99 -14
- data/spec/savon/response_spec.rb +7 -1
- data/spec/savon/soap_fault_spec.rb +12 -1
- data/spec/savon/softlayer_spec.rb +3 -2
- data/spec/spec_helper.rb +5 -4
- data/spec/support/adapters.rb +1 -0
- data/spec/support/endpoint.rb +1 -0
- data/spec/support/fixture.rb +1 -0
- data/spec/support/integration.rb +2 -1
- data/spec/support/stdout.rb +1 -0
- metadata +86 -33
- data/.travis.yml +0 -19
- data/donate.png +0 -0
- data/spec/integration/centra_spec.rb +0 -66
- data/spec/integration/email_example_spec.rb +0 -32
- data/spec/integration/random_quote_spec.rb +0 -23
- data/spec/integration/ratp_example_spec.rb +0 -28
- data/spec/integration/stockquote_example_spec.rb +0 -34
- data/spec/integration/temperature_example_spec.rb +0 -46
data/lib/savon/builder.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "savon/header"
|
2
3
|
require "savon/message"
|
3
4
|
require "nokogiri"
|
@@ -6,6 +7,7 @@ require "gyoku"
|
|
6
7
|
|
7
8
|
module Savon
|
8
9
|
class Builder
|
10
|
+
attr_reader :multipart
|
9
11
|
|
10
12
|
SCHEMA_TYPES = {
|
11
13
|
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
@@ -36,14 +38,7 @@ module Savon
|
|
36
38
|
end
|
37
39
|
|
38
40
|
def build_document
|
39
|
-
xml_result =
|
40
|
-
tag(xml, :Header, header_attributes) { xml << header.to_s } unless header.empty?
|
41
|
-
if @globals[:no_message_tag]
|
42
|
-
tag(xml, :Body, body_attributes) { xml << message.to_s }
|
43
|
-
else
|
44
|
-
tag(xml, :Body, body_attributes) { xml.tag!(*namespaced_message_tag) { xml << body_message } }
|
45
|
-
end
|
46
|
-
end
|
41
|
+
xml_result = build_xml
|
47
42
|
|
48
43
|
# if we have a signature sign the document
|
49
44
|
if @signature
|
@@ -51,20 +46,19 @@ module Savon
|
|
51
46
|
|
52
47
|
2.times do
|
53
48
|
@header = nil
|
54
|
-
@signature.document =
|
55
|
-
tag(xml, :Header, header_attributes) { xml << header.to_s } unless header.empty?
|
56
|
-
if @globals[:no_message_tag]
|
57
|
-
tag(xml, :Body, body_attributes) { xml << message.to_s }
|
58
|
-
else
|
59
|
-
tag(xml, :Body, body_attributes) { xml.tag!(*namespaced_message_tag) { xml << message.to_s } }
|
60
|
-
end
|
61
|
-
end
|
49
|
+
@signature.document = build_xml
|
62
50
|
end
|
63
51
|
|
64
52
|
xml_result = @signature.document
|
65
53
|
end
|
66
54
|
|
67
|
-
|
55
|
+
# if there are attachments for the request, we should build a multipart message according to
|
56
|
+
# https://www.w3.org/TR/SOAP-attachments
|
57
|
+
if @locals[:attachments]
|
58
|
+
build_multipart_message(xml_result)
|
59
|
+
else
|
60
|
+
xml_result
|
61
|
+
end
|
68
62
|
end
|
69
63
|
|
70
64
|
def header_attributes
|
@@ -117,15 +111,21 @@ module Savon
|
|
117
111
|
@namespaces ||= begin
|
118
112
|
namespaces = SCHEMA_TYPES.dup
|
119
113
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
namespaces["xmlns:#{namespace_identifier}"] = @globals[:namespace] || @wsdl.namespace
|
124
|
-
end
|
114
|
+
# check namespace_identifier
|
115
|
+
namespaces["xmlns#{namespace_identifier.nil? ? '' : ":#{namespace_identifier}"}"] =
|
116
|
+
@globals[:namespace] || @wsdl.namespace
|
125
117
|
|
126
|
-
|
127
|
-
|
128
|
-
|
118
|
+
# check env_namespace
|
119
|
+
namespaces["xmlns#{env_namespace && env_namespace != "" ? ":#{env_namespace}" : ''}"] =
|
120
|
+
SOAP_NAMESPACE[@globals[:soap_version]]
|
121
|
+
|
122
|
+
if @wsdl&.document
|
123
|
+
@wsdl.parser.namespaces.each do |identifier, path|
|
124
|
+
next if namespaces.key?("xmlns:#{identifier}")
|
125
|
+
|
126
|
+
namespaces["xmlns:#{identifier}"] = path
|
127
|
+
end
|
128
|
+
end
|
129
129
|
|
130
130
|
namespaces
|
131
131
|
end
|
@@ -162,18 +162,20 @@ module Savon
|
|
162
162
|
break if @locals[:message].nil?
|
163
163
|
message_locals = @locals[:message][message.snakecase.to_sym]
|
164
164
|
message_content = Message.new(message_tag, namespace_identifier, @types, @used_namespaces, message_locals, :unqualified, @globals[:convert_request_keys_to], @globals[:unwrap]).to_s
|
165
|
-
messages
|
165
|
+
messages += "<#{message} xsi:type=\"#{type.join(':')}\">#{message_content}</#{message}>"
|
166
166
|
end
|
167
167
|
messages
|
168
168
|
end
|
169
169
|
|
170
170
|
def message_tag
|
171
|
-
|
171
|
+
wsdl_tag_name = @wsdl.document? && @wsdl.soap_input(@operation_name.to_sym)
|
172
|
+
|
173
|
+
message_tag = wsdl_tag_name.keys.first if wsdl_tag_name.is_a?(Hash)
|
172
174
|
message_tag ||= @locals[:message_tag]
|
173
|
-
message_tag ||=
|
175
|
+
message_tag ||= wsdl_tag_name
|
174
176
|
message_tag ||= Gyoku.xml_tag(@operation_name, :key_converter => @globals[:convert_request_keys_to])
|
175
177
|
|
176
|
-
|
178
|
+
message_tag.to_sym
|
177
179
|
end
|
178
180
|
|
179
181
|
def message_attributes
|
@@ -227,5 +229,69 @@ module Savon
|
|
227
229
|
end
|
228
230
|
end
|
229
231
|
|
232
|
+
def build_xml
|
233
|
+
tag(builder, :Envelope, namespaces_with_globals) do |xml|
|
234
|
+
tag(xml, :Header, header_attributes) { xml << header.to_s } unless header.empty?
|
235
|
+
tag(xml, :Body, body_attributes) do
|
236
|
+
if @globals[:no_message_tag]
|
237
|
+
xml << message.to_s
|
238
|
+
else
|
239
|
+
xml.tag!(*namespaced_message_tag) { xml << body_message }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def build_multipart_message(message_xml)
|
246
|
+
multipart_message = init_multipart_message(message_xml)
|
247
|
+
add_attachments_to_multipart_message(multipart_message)
|
248
|
+
|
249
|
+
multipart_message.ready_to_send!
|
250
|
+
|
251
|
+
# the mail.body.encoded algorithm reorders the parts, default order is [ "text/plain", "text/enriched", "text/html" ]
|
252
|
+
# should redefine the sort order, because the soap request xml should be the first
|
253
|
+
multipart_message.body.set_sort_order [ "text/xml" ]
|
254
|
+
|
255
|
+
multipart_message.body.encoded(multipart_message.content_transfer_encoding)
|
256
|
+
end
|
257
|
+
|
258
|
+
def init_multipart_message(message_xml)
|
259
|
+
multipart_message = Mail.new
|
260
|
+
xml_part = Mail::Part.new do
|
261
|
+
content_type 'text/xml'
|
262
|
+
body message_xml
|
263
|
+
# in Content-Type the start parameter is recommended (RFC 2387)
|
264
|
+
content_id '<soap-request-body@soap>'
|
265
|
+
end
|
266
|
+
multipart_message.add_part xml_part
|
267
|
+
|
268
|
+
#request.headers["Content-Type"] = "multipart/related; boundary=\"#{multipart_message.body.boundary}\"; type=\"text/xml\"; start=\"#{xml_part.content_id}\""
|
269
|
+
@multipart = {
|
270
|
+
multipart_boundary: multipart_message.body.boundary,
|
271
|
+
start: xml_part.content_id,
|
272
|
+
}
|
273
|
+
|
274
|
+
multipart_message
|
275
|
+
end
|
276
|
+
|
277
|
+
def add_attachments_to_multipart_message(multipart_message)
|
278
|
+
if @locals[:attachments].is_a? Hash
|
279
|
+
# hash example: { 'att1' => '/path/to/att1', 'att2' => '/path/to/att2' }
|
280
|
+
@locals[:attachments].each do |identifier, attachment|
|
281
|
+
add_attachment_to_multipart_message(multipart_message, attachment, identifier)
|
282
|
+
end
|
283
|
+
elsif @locals[:attachments].is_a? Array
|
284
|
+
# array example: [ '/path/to/att1', '/path/to/att2' ]
|
285
|
+
# array example: [ { filename: 'att1.xml', content: '<x/>' }, { filename: 'att2.xml', content: '<y/>' } ]
|
286
|
+
@locals[:attachments].each do |attachment|
|
287
|
+
add_attachment_to_multipart_message(multipart_message, attachment, attachment.is_a?(String) ? File.basename(attachment) : attachment[:filename])
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def add_attachment_to_multipart_message(multipart_message, attachment, identifier)
|
293
|
+
multipart_message.add_file attachment.clone
|
294
|
+
multipart_message.parts.last.content_id = multipart_message.parts.last.content_location = identifier.to_s
|
295
|
+
end
|
230
296
|
end
|
231
297
|
end
|
data/lib/savon/client.rb
CHANGED
data/lib/savon/header.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "akami"
|
2
3
|
require "gyoku"
|
3
4
|
require "securerandom"
|
@@ -61,12 +62,7 @@ module Savon
|
|
61
62
|
convert_to_xml({
|
62
63
|
'wsa:Action' => @locals[:soap_action],
|
63
64
|
'wsa:To' => @globals[:endpoint],
|
64
|
-
'wsa:MessageID' => "urn:uuid:#{SecureRandom.uuid}"
|
65
|
-
attributes!: {
|
66
|
-
'wsa:MessageID' => {
|
67
|
-
"xmlns:wsa" => "http://schemas.xmlsoap.org/ws/2004/08/addressing"
|
68
|
-
}
|
69
|
-
}
|
65
|
+
'wsa:MessageID' => "urn:uuid:#{SecureRandom.uuid}"
|
70
66
|
})
|
71
67
|
end
|
72
68
|
|
data/lib/savon/http_error.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Savon
|
4
4
|
class HTTPError < Error
|
@@ -14,9 +14,9 @@ module Savon
|
|
14
14
|
attr_reader :http
|
15
15
|
|
16
16
|
def to_s
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
String.new("HTTP error (#{@http.code})").tap do |str_error|
|
18
|
+
str_error << ": #{@http.body}" unless @http.body.empty?
|
19
|
+
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def to_hash
|
data/lib/savon/log_message.rb
CHANGED
data/lib/savon/message.rb
CHANGED
data/lib/savon/mock.rb
CHANGED
data/lib/savon/model.rb
CHANGED
data/lib/savon/operation.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "savon/options"
|
2
3
|
require "savon/block_interface"
|
3
4
|
require "savon/request"
|
@@ -5,10 +6,16 @@ require "savon/builder"
|
|
5
6
|
require "savon/response"
|
6
7
|
require "savon/request_logger"
|
7
8
|
require "savon/http_error"
|
9
|
+
require "mail"
|
8
10
|
|
9
11
|
module Savon
|
10
12
|
class Operation
|
11
13
|
|
14
|
+
SOAP_REQUEST_TYPE = {
|
15
|
+
1 => "text/xml",
|
16
|
+
2 => "application/soap+xml"
|
17
|
+
}
|
18
|
+
|
12
19
|
def self.create(operation_name, wsdl, globals)
|
13
20
|
if wsdl.document?
|
14
21
|
ensure_name_is_symbol! operation_name
|
@@ -66,21 +73,7 @@ module Savon
|
|
66
73
|
private
|
67
74
|
|
68
75
|
def create_response(response)
|
69
|
-
|
70
|
-
Multipart::Response.new(response, @globals, @locals)
|
71
|
-
else
|
72
|
-
Response.new(response, @globals, @locals)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def multipart_supported?
|
77
|
-
return false unless @globals[:multipart] || @locals[:multipart]
|
78
|
-
|
79
|
-
if Savon.const_defined? :Multipart
|
80
|
-
true
|
81
|
-
else
|
82
|
-
raise 'Unable to find Savon::Multipart. Make sure the savon-multipart gem is installed and loaded.'
|
83
|
-
end
|
76
|
+
Response.new(response, @globals, @locals)
|
84
77
|
end
|
85
78
|
|
86
79
|
def set_locals(locals, block)
|
@@ -107,6 +100,15 @@ module Savon
|
|
107
100
|
request.url = endpoint
|
108
101
|
request.body = builder.to_s
|
109
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
|
+
|
110
112
|
# TODO: could HTTPI do this automatically in case the header
|
111
113
|
# was not specified manually? [dh, 2013-01-04]
|
112
114
|
request.headers["Content-Length"] = request.body.bytesize.to_s
|
@@ -119,11 +121,11 @@ module Savon
|
|
119
121
|
return if @locals.include?(:soap_action) && !@locals[:soap_action]
|
120
122
|
|
121
123
|
# get the soap_action from local options
|
122
|
-
|
124
|
+
@locals[:soap_action] ||
|
123
125
|
# with no local option, but a wsdl, ask it for the soap_action
|
124
|
-
|
126
|
+
@wsdl.document? && @wsdl.soap_action(@name.to_sym) ||
|
125
127
|
# if there is no soap_action up to this point, fallback to a simple default
|
126
|
-
|
128
|
+
Gyoku.xml_tag(@name, :key_converter => @globals[:convert_request_keys_to])
|
127
129
|
end
|
128
130
|
|
129
131
|
def endpoint
|
data/lib/savon/options.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "logger"
|
2
3
|
require "httpi"
|
3
4
|
|
@@ -78,6 +79,7 @@ module Savon
|
|
78
79
|
:namespaces => {},
|
79
80
|
:logger => Logger.new($stdout),
|
80
81
|
:log => false,
|
82
|
+
:log_headers => true,
|
81
83
|
:filters => [],
|
82
84
|
:pretty_print_xml => false,
|
83
85
|
:raise_errors => true,
|
@@ -137,7 +139,7 @@ module Savon
|
|
137
139
|
|
138
140
|
# Proxy server to use for all requests.
|
139
141
|
def proxy(proxy)
|
140
|
-
@options[:proxy] = proxy
|
142
|
+
@options[:proxy] = proxy unless proxy.nil?
|
141
143
|
end
|
142
144
|
|
143
145
|
# A Hash of HTTP headers.
|
@@ -155,6 +157,11 @@ module Savon
|
|
155
157
|
@options[:read_timeout] = read_timeout
|
156
158
|
end
|
157
159
|
|
160
|
+
# Write timeout in seconds.
|
161
|
+
def write_timeout(write_timeout)
|
162
|
+
@options[:write_timeout] = write_timeout
|
163
|
+
end
|
164
|
+
|
158
165
|
# The encoding to use. Defaults to "UTF-8".
|
159
166
|
def encoding(encoding)
|
160
167
|
@options[:encoding] = encoding
|
@@ -213,6 +220,11 @@ module Savon
|
|
213
220
|
@options[:logger].level = levels[level]
|
214
221
|
end
|
215
222
|
|
223
|
+
# To log headers or not.
|
224
|
+
def log_headers(log_headers)
|
225
|
+
@options[:log_headers] = log_headers
|
226
|
+
end
|
227
|
+
|
216
228
|
# A list of XML tags to filter from logged SOAP messages.
|
217
229
|
def filters(*filters)
|
218
230
|
@options[:filters] = filters.flatten
|
@@ -228,6 +240,16 @@ module Savon
|
|
228
240
|
@options[:ssl_version] = version
|
229
241
|
end
|
230
242
|
|
243
|
+
# Specifies the SSL version to use.
|
244
|
+
def ssl_min_version(version)
|
245
|
+
@options[:ssl_min_version] = version
|
246
|
+
end
|
247
|
+
|
248
|
+
# Specifies the SSL version to use.
|
249
|
+
def ssl_max_version(version)
|
250
|
+
@options[:ssl_max_version] = version
|
251
|
+
end
|
252
|
+
|
231
253
|
# Whether and how to to verify the connection.
|
232
254
|
def ssl_verify_mode(verify_mode)
|
233
255
|
@options[:ssl_verify_mode] = verify_mode
|
@@ -268,6 +290,19 @@ module Savon
|
|
268
290
|
@options[:ssl_ca_cert] = cert
|
269
291
|
end
|
270
292
|
|
293
|
+
def ssl_ciphers(ciphers)
|
294
|
+
@options[:ssl_ciphers] = ciphers
|
295
|
+
end
|
296
|
+
|
297
|
+
# Sets the ca cert path.
|
298
|
+
def ssl_ca_cert_path(path)
|
299
|
+
@options[:ssl_ca_cert_path] = path
|
300
|
+
end
|
301
|
+
|
302
|
+
# Sets the ssl cert store.
|
303
|
+
def ssl_cert_store(store)
|
304
|
+
@options[:ssl_cert_store] = store
|
305
|
+
end
|
271
306
|
|
272
307
|
# HTTP basic auth credentials.
|
273
308
|
def basic_auth(*credentials)
|
@@ -383,6 +418,40 @@ module Savon
|
|
383
418
|
@options[:attributes] = attributes
|
384
419
|
end
|
385
420
|
|
421
|
+
# Attachments for the SOAP message (https://www.w3.org/TR/SOAP-attachments)
|
422
|
+
#
|
423
|
+
# should pass an Array or a Hash; items should be path strings or
|
424
|
+
# { filename: 'file.name', content: 'content' } objects
|
425
|
+
# The Content-ID in multipart message sections will be the filename or the key if Hash is given
|
426
|
+
#
|
427
|
+
# usage examples:
|
428
|
+
#
|
429
|
+
# response = client.call :operation1 do
|
430
|
+
# message param1: 'value'
|
431
|
+
# attachments [
|
432
|
+
# { filename: 'x1.xml', content: '<xml>abc</xml>'},
|
433
|
+
# { filename: 'x2.xml', content: '<xml>abc</xml>'}
|
434
|
+
# ]
|
435
|
+
# end
|
436
|
+
# # Content-ID will be x1.xml and x2.xml
|
437
|
+
#
|
438
|
+
# response = client.call :operation1 do
|
439
|
+
# message param1: 'value'
|
440
|
+
# attachments 'x1.xml' => '/tmp/1281ab7d7d.xml', 'x2.xml' => '/tmp/4c5v8e833a.xml'
|
441
|
+
# end
|
442
|
+
# # Content-ID will be x1.xml and x2.xml
|
443
|
+
#
|
444
|
+
# response = client.call :operation1 do
|
445
|
+
# message param1: 'value'
|
446
|
+
# attachments [ '/tmp/1281ab7d7d.xml', '/tmp/4c5v8e833a.xml']
|
447
|
+
# end
|
448
|
+
# # Content-ID will be 1281ab7d7d.xml and 4c5v8e833a.xml
|
449
|
+
#
|
450
|
+
# The Content-ID is important if you want to refer to the attachments from the SOAP request
|
451
|
+
def attachments(attachments)
|
452
|
+
@options[:attachments] = attachments
|
453
|
+
end
|
454
|
+
|
386
455
|
# Value of the SOAPAction HTTP header.
|
387
456
|
def soap_action(soap_action)
|
388
457
|
@options[:soap_action] = soap_action
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "gyoku"
|
2
3
|
|
3
4
|
module Savon
|
@@ -9,7 +10,7 @@ module Savon
|
|
9
10
|
end
|
10
11
|
|
11
12
|
def to_hash(hash, path)
|
12
|
-
return unless hash
|
13
|
+
return hash unless hash
|
13
14
|
return hash.map { |value| to_hash(value, path) } if hash.is_a?(Array)
|
14
15
|
return hash.to_s unless hash.is_a?(Hash)
|
15
16
|
|
@@ -26,7 +27,7 @@ module Savon
|
|
26
27
|
translated_key = translate_tag(key)
|
27
28
|
newkey = add_namespaces_to_values(key, path).first
|
28
29
|
newpath = path + [translated_key]
|
29
|
-
newhash[newkey] = to_hash(value, newpath)
|
30
|
+
newhash[newkey] = to_hash(value, @types[newpath] ? [@types[newpath]] : newpath)
|
30
31
|
end
|
31
32
|
end
|
32
33
|
newhash
|
@@ -43,8 +44,8 @@ module Savon
|
|
43
44
|
Array(values).collect do |value|
|
44
45
|
translated_value = translate_tag(value)
|
45
46
|
namespace_path = path + [translated_value]
|
46
|
-
namespace = @used_namespaces[namespace_path]
|
47
|
-
namespace.
|
47
|
+
namespace = @used_namespaces[namespace_path] || ''
|
48
|
+
namespace.empty? ? value : "#{namespace}:#{translated_value}"
|
48
49
|
end
|
49
50
|
end
|
50
51
|
end
|
data/lib/savon/request.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "httpi"
|
2
3
|
|
3
4
|
module Savon
|
@@ -21,18 +22,25 @@ module Savon
|
|
21
22
|
def configure_timeouts
|
22
23
|
@http_request.open_timeout = @globals[:open_timeout] if @globals.include? :open_timeout
|
23
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
|
24
26
|
end
|
25
27
|
|
26
28
|
def configure_ssl
|
27
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
|
32
|
+
|
28
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
|
29
35
|
|
30
36
|
@http_request.auth.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file
|
31
|
-
@http_request.auth.ssl.cert_key = @globals[:ssl_cert_key]
|
37
|
+
@http_request.auth.ssl.cert_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key
|
32
38
|
@http_request.auth.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file
|
33
|
-
@http_request.auth.ssl.cert = @globals[:ssl_cert]
|
39
|
+
@http_request.auth.ssl.cert = @globals[:ssl_cert] if @globals.include? :ssl_cert
|
34
40
|
@http_request.auth.ssl.ca_cert_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file
|
35
|
-
@http_request.auth.ssl.
|
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
|
36
44
|
|
37
45
|
@http_request.auth.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password
|
38
46
|
end
|
@@ -55,12 +63,19 @@ module Savon
|
|
55
63
|
def build
|
56
64
|
configure_proxy
|
57
65
|
configure_timeouts
|
66
|
+
configure_headers
|
58
67
|
configure_ssl
|
59
68
|
configure_auth
|
60
69
|
configure_redirect_handling
|
61
70
|
|
62
71
|
@http_request
|
63
72
|
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def configure_headers
|
77
|
+
@http_request.headers = @globals[:headers] if @globals.include? :headers
|
78
|
+
end
|
64
79
|
end
|
65
80
|
|
66
81
|
class SOAPRequest < HTTPRequest
|
data/lib/savon/request_logger.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "savon/log_message"
|
2
3
|
|
3
4
|
module Savon
|
@@ -23,21 +24,26 @@ module Savon
|
|
23
24
|
@globals[:log]
|
24
25
|
end
|
25
26
|
|
27
|
+
def log_headers?
|
28
|
+
@globals[:log_headers]
|
29
|
+
end
|
30
|
+
|
26
31
|
private
|
27
32
|
|
28
33
|
def log_request(request)
|
29
34
|
logger.info { "SOAP request: #{request.url}" }
|
30
|
-
logger.info { headers_to_log(request.headers) }
|
35
|
+
logger.info { headers_to_log(request.headers) } if log_headers?
|
31
36
|
logger.debug { body_to_log(request.body) }
|
32
37
|
end
|
33
38
|
|
34
39
|
def log_response(response)
|
35
40
|
logger.info { "SOAP response (status #{response.code})" }
|
41
|
+
logger.debug { headers_to_log(response.headers) } if log_headers?
|
36
42
|
logger.debug { body_to_log(response.body) }
|
37
43
|
end
|
38
44
|
|
39
45
|
def headers_to_log(headers)
|
40
|
-
headers.map { |key, value| "#{key}: #{value}" }.join("
|
46
|
+
headers.map { |key, value| "#{key}: #{value}" }.join("\n")
|
41
47
|
end
|
42
48
|
|
43
49
|
def body_to_log(body)
|
data/lib/savon/response.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "nori"
|
2
3
|
require "savon/soap_fault"
|
3
4
|
require "savon/http_error"
|
4
5
|
|
5
6
|
module Savon
|
6
7
|
class Response
|
8
|
+
CRLF = /\r\n/
|
9
|
+
WSP = /[#{%Q|\x9\x20|}]/
|
7
10
|
|
8
11
|
def initialize(http, globals, locals)
|
9
12
|
@http = http
|
10
13
|
@globals = globals
|
11
14
|
@locals = locals
|
15
|
+
@attachments = []
|
16
|
+
@xml = ''
|
17
|
+
@has_parsed_body = false
|
12
18
|
|
13
19
|
build_soap_and_http_errors!
|
14
20
|
raise_soap_and_http_errors! if @globals[:raise_errors]
|
@@ -53,7 +59,12 @@ module Savon
|
|
53
59
|
end
|
54
60
|
|
55
61
|
def xml
|
56
|
-
|
62
|
+
if multipart?
|
63
|
+
parse_body unless @has_parsed_body
|
64
|
+
@xml
|
65
|
+
else
|
66
|
+
@http.body
|
67
|
+
end
|
57
68
|
end
|
58
69
|
|
59
70
|
alias_method :to_xml, :xml
|
@@ -69,13 +80,49 @@ module Savon
|
|
69
80
|
|
70
81
|
def find(*path)
|
71
82
|
envelope = nori.find(hash, 'Envelope')
|
72
|
-
raise_invalid_response_error! unless envelope
|
83
|
+
raise_invalid_response_error! unless envelope.is_a?(Hash)
|
73
84
|
|
74
85
|
nori.find(envelope, *path)
|
75
86
|
end
|
76
87
|
|
88
|
+
def attachments
|
89
|
+
if multipart?
|
90
|
+
parse_body unless @has_parsed_body
|
91
|
+
@attachments
|
92
|
+
else
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def multipart?
|
98
|
+
!(http.headers['content-type'] =~ /^multipart/im).nil?
|
99
|
+
end
|
100
|
+
|
77
101
|
private
|
78
102
|
|
103
|
+
def boundary
|
104
|
+
return unless multipart?
|
105
|
+
Mail::Field.new('content-type', http.headers['content-type']).parameters['boundary']
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_body
|
109
|
+
http.body.force_encoding Encoding::ASCII_8BIT
|
110
|
+
parts = http.body.split(/(?:\A|\r\n)--#{Regexp.escape(boundary)}(?=(?:--)?\s*$)/)
|
111
|
+
parts[1..-1].to_a.each_with_index do |part, index|
|
112
|
+
header_part, body_part = part.lstrip.split(/#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
|
113
|
+
section = Mail::Part.new(
|
114
|
+
body: body_part
|
115
|
+
)
|
116
|
+
section.header = header_part
|
117
|
+
if index == 0
|
118
|
+
@xml = section.body.to_s
|
119
|
+
else
|
120
|
+
@attachments << section
|
121
|
+
end
|
122
|
+
end
|
123
|
+
@has_parsed_body = true
|
124
|
+
end
|
125
|
+
|
79
126
|
def build_soap_and_http_errors!
|
80
127
|
@soap_fault = SOAPFault.new(@http, nori, xml) if soap_fault?
|
81
128
|
@http_error = HTTPError.new(@http) if http_error?
|