Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 162 additions & 13 deletions lib/soap/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

require 'xsd/qname'
require 'soap/baseData'
require 'soap/soapversion'


module SOAP
Expand Down Expand Up @@ -92,14 +93,148 @@ def encode(generator, ns, attrs = {})
end


# The SOAP 1.2 fault shape (env:Fault > Code{Value,Subcode?} +
# Reason{Text}+ + Node? + Role? + Detail?) is structurally different
# enough from SOAP 1.1's flat faultcode/faultstring/faultactor/detail
# (SOAPFault above, left completely untouched) that it gets its own
# class rather than trying to make one class cover both shapes.
#
# Code/Subcode/Reason and their leaf children (Value/Text/Role) are built
# as SOAPElement instances rather than SOAPStruct/SOAPQName: SOAPElement
# defaults its own encodingstyle to LiteralNamespace, which routes
# encoding through LiteralHandler instead of the RPC/Encoded-style
# SOAPHandler -- avoiding a pile of xsi:type/encodingStyle attributes and
# stray namespace declarations that SOAP 1.2 fault content, being plain
# structural XML rather than SOAP-encoded data, has no business carrying.
# SOAPElement's text-content handling also already special-cases QName
# values (used for Value's content, e.g. FaultCode12::Sender) by
# resolving them against the current namespace-to-prefix mapping at
# encode time -- exactly what's needed here, and not something
# SOAPQName's generic path does on its own.
#
# No new decode-dispatch wiring needed despite the nesting: both
# SOAPHandler's and LiteralHandler's decode_parent key struct/element
# children purely by local name (node.elename.name), never by namespace,
# so Code/Subcode/Reason/etc. decode through the exact same generic
# machinery already used for all other body content.
class SOAP12Fault < SOAPStruct
include SOAPEnvelopeElement
include SOAPCompoundtype

public

# Standard fault-code Values (Sender/Receiver/MustUnderstand/
# VersionMismatch) always live in the SOAP 1.2 envelope namespace per
# spec, so those resolve back to a real XSD::QName even after a decode
# round-trip, where the wire value is just a prefixed string ("env:
# Sender") with no automatic QName-content resolution in the generic
# literal-style decode path this reuses (a real, pre-existing gap in
# this codebase, not something specific to fault codes -- no QName-
# typed element content resolves its prefix back to a namespace
# anywhere today without an explicit type-driven decode path). Custom
# Subcodes can live in arbitrary app namespaces, so code_subcode can't
# apply the same trick; it returns the resolved QName only when never
# serialized (same-process construction), and the raw wire string
# ("n1:BadArgument") after a real decode.
STANDARD_FAULT_CODE_LOCAL_NAMES = {
'Sender' => FaultCode12::Sender,
'Receiver' => FaultCode12::Receiver,
'MustUnderstand' => FaultCode12::MustUnderstand,
'VersionMismatch' => FaultCode12::VersionMismatch
}

def code_value
v = self['Code'] && self['Code']['Value']
return nil unless v
data = v.data
return data unless data.is_a?(String)
STANDARD_FAULT_CODE_LOCAL_NAMES[data.sub(/\A[^:]+:/, '')] || data
end

def code_subcode
v = self['Code'] && self['Code']['Subcode'] && self['Code']['Subcode']['Value']
v && v.data
end

def reason_text
v = self['Reason'] && self['Reason']['Text']
v && v.data
end

def node
v = self['Node']
v && v.data
end

def role
v = self['Role']
v && v.data
end

def detail
self['Detail']
end

def detail=(rhs)
self['Detail'] = rhs
end

# code_value/code_subcode are XSD::QName (see FaultCode12::Sender etc);
# reason_text/node/role are plain strings; detail is any SOAP/OM node.
def initialize(code_value = nil, reason_text = nil, code_subcode = nil,
role = nil, detail = nil, reason_lang = 'en')
ns_uri = SOAPVersion1_2.envelope_namespace
super(XSD::QName.new(ns_uri, EleFault))
@elename = XSD::QName.new(ns_uri, EleFault)
@encodingstyle = LiteralNamespace
return unless code_value

code = SOAPElement.new(XSD::QName.new(ns_uri, 'Code'))
code.add(SOAPElement.new(XSD::QName.new(ns_uri, 'Value'), code_value))
if code_subcode
subcode = SOAPElement.new(XSD::QName.new(ns_uri, 'Subcode'))
subcode.add(SOAPElement.new(XSD::QName.new(ns_uri, 'Value'), code_subcode))
code.add(subcode)
end
self.add('Code', code)

reason = SOAPElement.new(XSD::QName.new(ns_uri, 'Reason'))
text = SOAPElement.new(XSD::QName.new(ns_uri, 'Text'), reason_text)
text.extraattr[XSD::QName.new(XSD::NS::Namespace, 'lang')] = reason_lang
reason.add(text)
self.add('Reason', reason)

if role
self.add('Role', SOAPElement.new(XSD::QName.new(ns_uri, 'Role'), role))
end
if detail
detail.elename = XSD::QName.new(ns_uri, 'Detail') if detail.respond_to?(:elename=)
self.add('Detail', detail)
end
end

def encode(generator, ns, attrs = {})
Generator.assign_ns(attrs, ns, SOAPVersion1_2.envelope_namespace)
name = ns.name(@elename)
generator.encode_tag(name, attrs)
yield(self['Code'])
yield(self['Reason'])
yield(self['Node']) if self['Node']
yield(self['Role']) if self['Role']
yield(self['Detail']) if self['Detail']
generator.encode_tag_end(name, true)
end
end


class SOAPBody < SOAPStruct
include SOAPEnvelopeElement

attr_reader :is_fault

def initialize(data = nil, is_fault = false)
def initialize(data = nil, is_fault = false, soap_version = SOAPVersion1_1)
super(nil)
@elename = EleBodyName
@elename = XSD::QName.new(soap_version.envelope_namespace, EleBody)
@encodingstyle = nil
if data
if data.respond_to?(:to_xmlpart)
Expand Down Expand Up @@ -154,14 +289,18 @@ class SOAPHeaderItem < XSD::NSDBase
attr_accessor :mustunderstand
attr_accessor :encodingstyle
attr_accessor :actor
attr_accessor :relay

def initialize(element, mustunderstand = true, encodingstyle = nil, actor = nil)
def initialize(element, mustunderstand = true, encodingstyle = nil, actor = nil,
relay = nil, soap_version = SOAPVersion1_1)
super()
@type = nil
@element = element
@mustunderstand = mustunderstand
@encodingstyle = encodingstyle
@actor = actor
@relay = relay
@soap_version = soap_version
element.parent = self if element
element.qualified = true
end
Expand All @@ -172,7 +311,8 @@ def encode(generator, ns, attrs = {})
end
# to remove mustUnderstand attribute, set it to nil
unless @mustunderstand.nil?
@element.extraattr[AttrMustUnderstandName] = (@mustunderstand ? '1' : '0')
@element.extraattr[@soap_version.mustunderstand_attr_name] =
(@mustunderstand ? '1' : '0')
end
if @encodingstyle
@element.extraattr[AttrEncodingStyleName] = @encodingstyle
Expand All @@ -181,7 +321,12 @@ def encode(generator, ns, attrs = {})
@element.encodingstyle = @encodingstyle
end
if @actor
@element.extraattr[AttrActorName] = @actor
@element.extraattr[@soap_version.role_attr_name] = @actor
end
# relay has no SOAP 1.1 equivalent -- @soap_version.relay_attr_name is
# nil there, so this is a no-op unless actually running under 1.2.
unless @relay.nil? || @soap_version.relay_attr_name.nil?
@element.extraattr[@soap_version.relay_attr_name] = (@relay ? 'true' : 'false')
end
yield(@element)
end
Expand All @@ -192,12 +337,14 @@ class SOAPHeader < SOAPStruct
include SOAPEnvelopeElement

attr_writer :force_encode
attr_reader :soap_version

def initialize
def initialize(soap_version = SOAPVersion1_1)
super(nil)
@elename = EleHeaderName
@elename = XSD::QName.new(soap_version.envelope_namespace, EleHeader)
@encodingstyle = nil
@force_encode = false
@soap_version = soap_version
end

def encode(generator, ns, attrs = {})
Expand All @@ -210,12 +357,14 @@ def encode(generator, ns, attrs = {})
end

def add(name, value)
actor = value.extraattr[AttrActorName]
mu = value.extraattr[AttrMustUnderstandName]
actor = value.extraattr[@soap_version.role_attr_name]
mu = value.extraattr[@soap_version.mustunderstand_attr_name]
encstyle = value.extraattr[AttrEncodingStyleName]
mu_value = mu.nil? ? nil : (mu == '1')
mu_value = mu.nil? ? nil : (mu == '1' || mu == 'true')
relay = @soap_version.relay_attr_name && value.extraattr[@soap_version.relay_attr_name]
relay_value = relay.nil? ? nil : (relay == 'true' || relay == '1')
# to remove mustUnderstand attribute, set it to nil
item = SOAPHeaderItem.new(value, mu_value, encstyle, actor)
item = SOAPHeaderItem.new(value, mu_value, encstyle, actor, relay_value, @soap_version)
super(name, item)
end

Expand All @@ -238,10 +387,10 @@ class SOAPEnvelope < XSD::NSDBase
attr_reader :body
attr_reader :external_content

def initialize(header = nil, body = nil)
def initialize(header = nil, body = nil, soap_version = SOAPVersion1_1)
super()
@type = nil
@elename = EleEnvelopeName
@elename = XSD::QName.new(soap_version.envelope_namespace, EleEnvelope)
@encodingstyle = nil
@header = header
@body = body
Expand Down
13 changes: 10 additions & 3 deletions lib/soap/mimemessage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@


require 'soap/attachment'
require 'soap/soapversion'


module SOAP
Expand Down Expand Up @@ -147,11 +148,17 @@ def to_s
end
end

def initialize
# soap_version governs the media type this message's parts declare
# (root part and the outer multipart's own "type" parameter) -- defaults
# to SOAPVersion1_1 so parsing an incoming message (which doesn't care
# what media type the parts claim; see #root) and any other caller that
# never had a version to give aren't affected.
def initialize(soap_version = SOAPVersion1_1)
@parts = []
@headers = Headers.new
@root = nil
@boundary = nil
@soap_version = soap_version
end

def self.parse(head, str)
Expand All @@ -163,7 +170,7 @@ def self.parse(head, str)
def close
@headers.add(
"Content-Type",
"multipart/related; type=\"text/xml\"; boundary=\"#{boundary}\"; start=\"#{@parts[0].contentid}\""
"multipart/related; type=\"#{@soap_version.media_type}\"; boundary=\"#{boundary}\"; start=\"#{@parts[0].contentid}\""
)
end

Expand Down Expand Up @@ -202,7 +209,7 @@ def boundary
def add_part(content)
part = Part.new
part.headers.add("Content-Type",
"text/xml; charset=" + XSD::Charset.xml_encoding_label)
"#{@soap_version.media_type}; charset=" + XSD::Charset.xml_encoding_label)
part.headers.add("Content-ID", Attachment.contentid(part))
part.body = content
@parts.unshift(part)
Expand Down
4 changes: 3 additions & 1 deletion lib/soap/ns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require 'xsd/datatypes'
require 'xsd/ns'
require 'soap/soap'
require 'soap/soapversion'


module SOAP
Expand All @@ -21,7 +22,8 @@ module SOAP

class NS < XSD::NS
KNOWN_TAG = XSD::NS::KNOWN_TAG.dup.update(
SOAP::EnvelopeNamespace => 'env'
SOAP::EnvelopeNamespace => 'env',
SOAP::SOAPVersion1_2.envelope_namespace => 'env'
)

def initialize(tag2ns = nil)
Expand Down
19 changes: 12 additions & 7 deletions lib/soap/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

require 'xsd/xmlparser'
require 'soap/soap'
require 'soap/soapversion'
require 'soap/ns'
require 'soap/baseData'
require 'soap/encodingstyle/handler'
Expand Down Expand Up @@ -74,6 +75,7 @@ def update(ns, name, node, encodingstyle, handler)
attr_accessor :default_encodingstyle
attr_accessor :decode_typemap
attr_accessor :allow_unqualified_element
attr_accessor :soap_version

def initialize(opt = {})
@opt = opt
Expand All @@ -82,7 +84,9 @@ def initialize(opt = {})
@recycleframe = nil
@lastnode = nil
@handlers = {}
@envelopenamespace = opt[:envelopenamespace] || EnvelopeNamespace
@soap_version = opt[:soap_version] || SOAPVersion1_1
@envelopenamespace =
opt[:envelopenamespace] || @soap_version.envelope_namespace
@default_encodingstyle = opt[:default_encodingstyle] || EncodingNamespace
@decode_typemap = opt[:decode_typemap] || nil
@allow_unqualified_element = opt[:allow_unqualified_element] || false
Expand Down Expand Up @@ -202,31 +206,32 @@ def decode_text(ns, text, handler)
def decode_soap_envelope(ns, ele, attrs, parent)
o = nil
if ele.name == EleEnvelope
o = SOAPEnvelope.new
o = SOAPEnvelope.new(nil, nil, @soap_version)
if ext = @opt[:external_content]
ext.each do |k, v|
o.external_content[k] = v
end
end
elsif ele.name == EleHeader
return nil unless parent.node.is_a?(SOAPEnvelope)
o = SOAPHeader.new
o = SOAPHeader.new(@soap_version)
parent.node.header = o
elsif ele.name == EleBody
return nil unless parent.node.is_a?(SOAPEnvelope)
o = SOAPBody.new
o = SOAPBody.new(nil, false, @soap_version)
parent.node.body = o
elsif ele.name == EleFault
fault_class = (@soap_version == SOAPVersion1_2) ? SOAP12Fault : SOAPFault
if parent.node.is_a?(SOAPBody)
o = SOAPFault.new
o = fault_class.new
parent.node.fault = o
elsif parent.node.is_a?(SOAPEnvelope)
# live.com server returns SOAPFault as a direct child of SOAPEnvelope.
# support it even if it's not spec compliant.
warn("Fault must be a child of Body.")
body = SOAPBody.new
body = SOAPBody.new(nil, false, @soap_version)
parent.node.body = body
o = SOAPFault.new
o = fault_class.new
body.fault = o
else
return nil
Expand Down
3 changes: 2 additions & 1 deletion lib/soap/rpc/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Driver
attr_proxy :literal_mapping_registry, true
attr_proxy :allow_unqualified_element, true
attr_proxy :default_encodingstyle, true
attr_proxy :soap_version, true
attr_proxy :generate_explicit_type, true
attr_proxy :use_default_namespace, true
attr_proxy :return_response_as_xml, true
Expand Down Expand Up @@ -165,7 +166,7 @@ def set_wiredump_file_base(name)
end

def create_header(headers)
header = SOAPHeader.new()
header = SOAPHeader.new(@proxy.soap_version)
headers.each do |content, mustunderstand, encodingstyle|
header.add(SOAPHeaderItem.new(content, mustunderstand, encodingstyle))
end
Expand Down
Loading
Loading