diff --git a/.gitignore b/.gitignore index 7a958395..1c6eb47a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .byebug_history .DS_Store .yardoc +.ruby-version doc rdox coverage diff --git a/lib/savon/builder.rb b/lib/savon/builder.rb index 13a17ef6..69d6dcc1 100644 --- a/lib/savon/builder.rb +++ b/lib/savon/builder.rb @@ -38,18 +38,23 @@ def pretty end def build_document - xml_result = build_xml + # check if xml was already provided + if @locals.include? :xml + xml_result = @locals[:xml] + else + xml_result = build_xml - # if we have a signature sign the document - if @signature - @signature.document = xml_result + # if we have a signature sign the document + if @signature + @signature.document = xml_result - 2.times do - @header = nil - @signature.document = build_xml - end + 2.times do + @header = nil + @signature.document = build_xml + end - xml_result = @signature.document + xml_result = @signature.document + end end # if there are attachments for the request, we should build a multipart message according to @@ -70,7 +75,6 @@ def body_attributes end def to_s - return @locals[:xml] if @locals.include? :xml build_document end @@ -254,15 +258,28 @@ def build_multipart_message(message_xml) # the mail.body.encoded algorithm reorders the parts, default order is [ "text/plain", "text/enriched", "text/html" ] # should redefine the sort order, because the soap request xml should be the first - multipart_message.body.set_sort_order [ "text/xml" ] + multipart_message.body.set_sort_order ['application/xop+xml', 'text/xml'] multipart_message.body.encoded(multipart_message.content_transfer_encoding) end def init_multipart_message(message_xml) multipart_message = Mail.new + + # MTOM differs from general SOAP attachments: + # 1. binary encoding + # 2. application/xop+xml mime type + if @locals[:mtom] + type = "application/xop+xml; charset=#{@globals[:encoding]}; type=\"text/xml\"" + + multipart_message.transport_encoding = 'binary' + message_xml.force_encoding('BINARY') + else + type = 'text/xml' + end + xml_part = Mail::Part.new do - content_type 'text/xml' + content_type type body message_xml # in Content-Type the start parameter is recommended (RFC 2387) content_id '' diff --git a/lib/savon/operation.rb b/lib/savon/operation.rb index 00e40d87..48872baa 100644 --- a/lib/savon/operation.rb +++ b/lib/savon/operation.rb @@ -101,9 +101,10 @@ def build_request(builder) request.body = builder.to_s if builder.multipart - request.gzip + type = @locals[:mtom] ? 'application/xop+xml"; start-info="text/xml' : SOAP_REQUEST_TYPE[@globals[:soap_version]] + request.gzip unless @locals[:mtom] request.headers["Content-Type"] = ["multipart/related", - "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"", + "type=\"#{type}\"", "start=\"#{builder.multipart[:start]}\"", "boundary=\"#{builder.multipart[:multipart_boundary]}\""].join("; ") request.headers["MIME-Version"] = "1.0" diff --git a/lib/savon/options.rb b/lib/savon/options.rb index 71d78666..0f472cb0 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -389,7 +389,8 @@ def initialize(options = {}) defaults = { :advanced_typecasting => true, :response_parser => :nokogiri, - :multipart => false + :multipart => false, + :mtom => false } super defaults.merge(options) @@ -452,6 +453,11 @@ def attachments(attachments) @options[:attachments] = attachments end + # Instruct Savon to send attachments using MTOM https://www.w3.org/TR/soap12-mtom/ + def mtom(mtom) + @options[:mtom] = mtom + end + # Value of the SOAPAction HTTP header. def soap_action(soap_action) @options[:soap_action] = soap_action @@ -477,6 +483,11 @@ def response_parser(parser) @options[:response_parser] = parser end + # Pass already configured Nori instance. + def nori(nori) + @options[:nori] = nori + end + # Instruct Savon to create a multipart response if available. def multipart(multipart) @options[:multipart] = multipart diff --git a/lib/savon/request_logger.rb b/lib/savon/request_logger.rb index 079449ff..5404e82c 100644 --- a/lib/savon/request_logger.rb +++ b/lib/savon/request_logger.rb @@ -47,7 +47,7 @@ def headers_to_log(headers) end def body_to_log(body) - LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s + LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s.force_encoding(@globals[:encoding]) end end diff --git a/lib/savon/response.rb b/lib/savon/response.rb index 9ca4a35b..e148182b 100644 --- a/lib/savon/response.rb +++ b/lib/savon/response.rb @@ -142,20 +142,16 @@ def xml_namespaces end def nori - return @nori if @nori + return @locals[:nori] if @locals[:nori] - nori_options = { - :delete_namespace_attributes => @globals[:delete_namespace_attributes], - :strip_namespaces => @globals[:strip_namespaces], - :convert_tags_to => @globals[:convert_response_tags_to], - :convert_attributes_to => @globals[:convert_attributes_to], - :advanced_typecasting => @locals[:advanced_typecasting], - :parser => @locals[:response_parser] - } - - non_nil_nori_options = nori_options.reject { |_, value| value.nil? } - @nori = Nori.new(non_nil_nori_options) + @nori ||= Nori.new({ + :delete_namespace_attributes => @globals[:delete_namespace_attributes], + :strip_namespaces => @globals[:strip_namespaces], + :convert_tags_to => @globals[:convert_response_tags_to], + :convert_attributes_to => @globals[:convert_attributes_to], + :advanced_typecasting => @locals[:advanced_typecasting], + :parser => @locals[:response_parser] + }.reject { |_, value| value.nil? }) end - end end