diff --git a/lib/savon/builder.rb b/lib/savon/builder.rb index 41e9e905..ffe6f6e1 100644 --- a/lib/savon/builder.rb +++ b/lib/savon/builder.rb @@ -4,6 +4,7 @@ require "savon/header" require "savon/message" require "savon/effective_options" +require "savon/mtom" require "nokogiri" require "builder" require "gyoku" @@ -18,6 +19,8 @@ module Savon # serialization is delegated to {Savon::Message}, the Header element to # {Savon::Header}, and Hash-to-XML conversion to Gyoku. class Builder + include Mtom + attr_reader :multipart SCHEMA_TYPES = { @@ -277,15 +280,17 @@ 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 + apply_mtom_encoding(multipart_message, message_xml) if @locals[:mtom] + type = xml_part_content_type 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/mtom.rb b/lib/savon/mtom.rb new file mode 100644 index 00000000..a918e47c --- /dev/null +++ b/lib/savon/mtom.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Savon + # MTOM request encoding (https://www.w3.org/TR/soap12-mtom/) for {Savon::Builder}. + module Mtom + private + + def apply_mtom_encoding(multipart_message, message_xml) + multipart_message.transport_encoding = 'binary' + message_xml.force_encoding('BINARY') + end + + def xml_part_content_type + return 'text/xml' unless @locals[:mtom] + + "application/xop+xml; charset=#{@globals[:encoding]}; type=\"text/xml\"" + end + end +end diff --git a/lib/savon/operation.rb b/lib/savon/operation.rb index 6cd92c05..3cfc9ad0 100644 --- a/lib/savon/operation.rb +++ b/lib/savon/operation.rb @@ -133,14 +133,22 @@ def soap_headers(builder) if builder.multipart # RFC 2387 §3 (multipart/related) - SOAP envelope is the root body part - headers["Content-Type"] = [ - "multipart/related", - "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"", - "start=\"#{builder.multipart[:start]}\"", - "boundary=\"#{builder.multipart[:multipart_boundary]}\"" - ].join("; ") + content_type = ["multipart/related"] + + if @locals[:mtom] + # SOAP 1.2 MTOM §3.1 - the root part is an XOP package + content_type << "type=\"application/xop+xml\"" + content_type << "start-info=\"application/soap+xml\"" + else + content_type << "type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"" + end + + content_type << "start=\"#{builder.multipart[:start]}\"" + content_type << "boundary=\"#{builder.multipart[:multipart_boundary]}\"" + + headers["Content-Type"] = content_type.join("; ") headers["MIME-Version"] = "1.0" - headers["Accept-Encoding"] = "gzip,deflate" + headers["Accept-Encoding"] = "gzip,deflate" unless @locals[:mtom] else headers["Content-Type"] = CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding] end diff --git a/lib/savon/options.rb b/lib/savon/options.rb index 8c8e854b..7f54a0cf 100644 --- a/lib/savon/options.rb +++ b/lib/savon/options.rb @@ -499,7 +499,8 @@ def initialize(options = {}) defaults = { advanced_typecasting: true, response_parser: :nokogiri, - multipart: false + multipart: false, + mtom: false } super defaults.merge(options) @@ -562,6 +563,11 @@ def attachments(attachments) @options[:attachments] = attachments end + # MTOM attachments 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 diff --git a/lib/savon/transport/logging.rb b/lib/savon/transport/logging.rb index 72b96804..eb673091 100644 --- a/lib/savon/transport/logging.rb +++ b/lib/savon/transport/logging.rb @@ -53,7 +53,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 end