From 07d07f3cffe01b857c78b7fcc8df1edc7a25daf6 Mon Sep 17 00:00:00 2001 From: Joshua Boverhof Date: Fri, 5 Nov 2004 06:35:59 +0000 Subject: [PATCH] ---------------------------------------------------------------------- Modified Files: ** removed all "imports" of ZSI or ZSI.wstools, so wstools can be used independently by SOAPpy. Namespaces.py -- added a namespace Utility.py -- moved ZSI.utility here, and the "Base" class for logging. WSDLTools.py -- added a "toDom" and "GetWSDL" methods to several classes, so now you can construct a WSDL instance and then call WSDL.toDom() --> DOM --> and create a WSDL file. __init__.py -- removed "Base" class for logging. Added Files: c14n.py -- moved the c14n stuff from ZSI.compat here. ---------------------------------------------------------------------- --- Namespaces.py | 3 + Utility.py | 498 ++++++++++++++++++++++++++++++++++++++++++++++++- WSDLTools.py | 260 +++++++++++++++++++++++++- __init__.py | 5 +- c14n.py | 505 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1255 insertions(+), 16 deletions(-) create mode 100755 c14n.py diff --git a/Namespaces.py b/Namespaces.py index 1300120..1574853 100755 --- a/Namespaces.py +++ b/Namespaces.py @@ -75,6 +75,7 @@ class OASIS: WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" UTILITY = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" LIFETIME = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd" + PROPERTIES = "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" class WSSE: BASE = "http://schemas.xmlsoap.org/ws/2002/04/secext" @@ -109,3 +110,5 @@ class GLOBUS: SECCONV = "http://wsrf.globus.org/core/2004/07/security/secconv" CORE = "http://www.globus.org/namespaces/2004/06/core" SIG = "http://www.globus.org/2002/04/xmlenc#gssapi-sign" + +ZSI_SCHEMA_URI = 'http://www.zolera.com/schemas/ZSI/' diff --git a/Utility.py b/Utility.py index 84e42f6..96f7231 100755 --- a/Utility.py +++ b/Utility.py @@ -11,15 +11,22 @@ ident = "$Id$" import types import string, httplib, smtplib, urllib, socket, weakref -import xml.dom.minidom from string import join, strip, split from UserDict import UserDict -from StringIO import StringIO +from cStringIO import StringIO from TimeoutSocket import TimeoutSocket, TimeoutError from urlparse import urlparse from httplib import HTTPConnection, HTTPSConnection from exceptions import Exception +import xml.dom.minidom +from xml.dom import Node + +import logging +from c14n import Canonicalize +from Namespaces import SCHEMA, SOAP, XMLNS, ZSI_SCHEMA_URI + + try: from xml.dom.ext import SplitQName except: @@ -46,13 +53,29 @@ except: return return tuple(l) + +class NamespaceError(Exception): + """Used to indicate a Namespace Error.""" + + class RecursionError(Exception): """Used to indicate a HTTP redirect recursion.""" - pass + class ParseError(Exception): """Used to indicate a XML parsing error.""" + +class DOMException(Exception): + """Used to indicate a problem processing DOM.""" + + +class Base: + """Base class for instance level Logging""" + def __init__(self, module=__name__): + self.logger = logging.getLogger('%s-%s(%x)' %(module, self.__class__, id(self))) + + class HTTPResponse: """Captures the information in an HTTP response message.""" @@ -586,11 +609,473 @@ class DOM: file.close() return result +DOM = DOM() -class DOMException(Exception): - pass -DOM = DOM() +class MessageInterface: + '''Higher Level Interface, delegates to DOM singleton, must + be subclassed and implement all methods that throw NotImplementedError. + ''' + def __init__(self, sw): + '''Constructor, May be extended, do not override. + sw -- soapWriter instance + ''' + self.sw = sw + + def getSoapWriter(self, sw): + return self._sw() + def setSoapWriter(self, sw): + self._sw = weakref.ref(sw) + sw = property(getSoapWriter, setSoapWriter, None, "soap writer instance.") + + def AddCallback(self, func, *arglist): + self.sw.AddCallback(func, *arglist) + + def Known(self, obj): + return self.sw.Known(obj) + + def Forget(self, obj): + return self.sw.Forget(obj) + + def canonicalize(self): + '''canonicalize the underlying DOM, and return as string. + ''' + raise NotImplementedError, '' + + def createDocument(self, namespaceURI=SOAP.ENV, localName='Envelope'): + '''create Document + ''' + raise NotImplementedError, '' + + def createAppendElement(self, namespaceURI, localName): + '''create and append element(namespaceURI,localName), and return + the node. + ''' + raise NotImplementedError, '' + + def findNamespaceURI(self, qualifiedName): + raise NotImplementedError, '' + + def resolvePrefix(self, prefix): + raise NotImplementedError, '' + + def setAttributeNS(self, namespaceURI, localName, value): + '''set attribute (namespaceURI, localName)=value + ''' + raise NotImplementedError, '' + + def setAttributeType(self, namespaceURI, localName): + '''set attribute xsi:type=(namespaceURI, localName) + ''' + raise NotImplementedError, '' + + def setNamespaceAttribute(self, namespaceURI, prefix): + '''set namespace attribute xmlns:prefix=namespaceURI + ''' + raise NotImplementedError, '' + + +class ElementProxy(Base, MessageInterface): + ''' + ''' + _soap_env_prefix = 'SOAP-ENV' + _soap_enc_prefix = 'SOAP-ENC' + _zsi_prefix = 'ZSI' + _xsd_prefix = 'xsd' + _xsi_prefix = 'xsi' + _xml_prefix = 'xml' + _xmlns_prefix = 'xmlns' + + _soap_env_nsuri = SOAP.ENV + _soap_enc_nsuri = SOAP.ENC + _zsi_nsuri = ZSI_SCHEMA_URI + _xsd_nsuri = SCHEMA.XSD3 + _xsi_nsuri = SCHEMA.XSI3 + _xml_nsuri = XMLNS.XML + _xmlns_nsuri = XMLNS.BASE + + standard_ns = {\ + _xml_prefix:_xml_nsuri, + _xmlns_prefix:_xmlns_nsuri + } + reserved_ns = {\ + _soap_env_prefix:_soap_env_nsuri, + _soap_enc_prefix:_soap_enc_nsuri, + _zsi_prefix:_zsi_nsuri, + _xsd_prefix:_xsd_nsuri, + _xsi_prefix:_xsi_nsuri, + } + name = None + namespaceURI = None + + def __init__(self, sw, message=None): + '''Initialize. + sw -- SoapWriter + ''' + self._indx = 0 + MessageInterface.__init__(self, sw) + Base.__init__(self) + self._dom = DOM + self.node = None + if type(message) in (types.StringType,types.UnicodeType): + self.loadFromString(message) + elif isinstance(message, ElementProxy): + self.node = message._getNode() + else: + self.node = message + self.processorNss = self.standard_ns.copy() + self.processorNss.update(self.reserved_ns) + + def __str__(self): + return self.toString() + + def evaluate(self, expression, processorNss=None): + '''expression -- XPath compiled expression + ''' + from Ft.Xml import XPath + if not processorNss: + context = XPath.Context.Context(self.node, processorNss=self.processorNss) + else: + context = XPath.Context.Context(self.node, processorNss=processorNss) + nodes = expression.evaluate(context) + return map(lambda node: ElementProxy(self.sw,node), nodes) + + ############################################# + # Methods for checking/setting the + # classes (namespaceURI,name) node. + ############################################# + def checkNode(self, namespaceURI=None, localName=None): + ''' + namespaceURI -- namespace of element + localName -- local name of element + ''' + namespaceURI = namespaceURI or self.namespaceURI + localName = localName or self.name + check = False + if localName and self.node: + check = self._dom.isElement(self.node, localName, namespaceURI) + if not check: + raise NamespaceError, 'unexpected node type %s, expecting %s' %(self.node, localName) + + def setNode(self, node=None): + if node: + if isinstance(node, ElementProxy): + self.node = node._getNode() + else: + self.node = node + elif self.node: + node = self._dom.getElement(self.node, self.name, self.namespaceURI, default=None) + if not node: + raise NamespaceError, 'cant find element (%s,%s)' %(self.namespaceURI,self.name) + self.node = node + else: + #self.node = self._dom.create(self.node, self.name, self.namespaceURI, default=None) + self.createDocument(self.namespaceURI, localName=self.name, doctype=None) + + self.checkNode() + + ############################################# + # Wrapper Methods for direct DOM Element Node access + ############################################# + def _getNode(self): + return self.node + + def _getElements(self): + return self._dom.getElements(self.node, name=None) + + def _getOwnerDocument(self): + return self.node.ownerDocument or self.node + + def _getUniquePrefix(self): + '''I guess we need to resolve all potential prefixes + because when the current node is attached it copies the + namespaces into the parent node. + ''' + while 1: + self._indx += 1 + prefix = 'ns%d' %self._indx + try: + self._dom.findNamespaceURI(prefix, self._getNode()) + except DOMException, ex: + break + return prefix + + def _getPrefix(self, node, nsuri): + ''' + Keyword arguments: + node -- DOM Element Node + nsuri -- namespace of attribute value + ''' + try: + if node and (node.nodeType == node.ELEMENT_NODE) and \ + (nsuri == self._dom.findDefaultNS(node)): + return None + except DOMException, ex: + pass + if nsuri == XMLNS.XML: + return self._xml_prefix + if node.nodeType == Node.ELEMENT_NODE: + for attr in node.attributes.values(): + if attr.namespaceURI == XMLNS.BASE \ + and nsuri == attr.value: + return attr.localName + else: + if node.parentNode: + return self._getPrefix(node.parentNode, nsuri) + raise NamespaceError, 'namespaceURI "%s" is not defined' %nsuri + + def _appendChild(self, node): + ''' + Keyword arguments: + node -- DOM Element Node + ''' + if node is None: + raise TypeError, 'node is None' + self.node.appendChild(node) + + def _insertBefore(self, newChild, refChild): + ''' + Keyword arguments: + child -- DOM Element Node to insert + refChild -- DOM Element Node + ''' + self.node.insertBefore(newChild, refChild) + + def _setAttributeNS(self, namespaceURI, qualifiedName, value): + ''' + Keyword arguments: + namespaceURI -- namespace of attribute + qualifiedName -- qualified name of new attribute value + value -- value of attribute + ''' + self.node.setAttributeNS(namespaceURI, qualifiedName, value) + + ############################################# + #General Methods + ############################################# + def isFault(self): + '''check to see if this is a soap:fault message. + ''' + return False + + def getPrefix(self, namespaceURI): + try: + prefix = self._getPrefix(node=self.node, nsuri=namespaceURI) + except NamespaceError, ex: + prefix = self._getUniquePrefix() + self.setNamespaceAttribute(prefix, namespaceURI) + return prefix + + def getDocument(self): + return self._getOwnerDocument() + + def setDocument(self, document): + self.node = document + + def importFromString(self, xmlString): + doc = self._dom.loadDocument(StringIO(xmlString)) + node = self._dom.getElement(doc, name=None) + clone = self.importNode(node) + self._appendChild(clone) + + def importNode(self, node): + if isinstance(node, ElementProxy): + node = node._getNode() + return self._dom.importNode(self._getOwnerDocument(), node, deep=1) + + def loadFromString(self, data): + self.node = self._dom.loadDocument(StringIO(data)) + + def canonicalize(self): + return Canonicalize(self.node) + + def toString(self): + return self.canonicalize() + + def createDocument(self, namespaceURI, localName, doctype=None): + prefix = self._soap_env_prefix + if namespaceURI != self.reserved_ns[prefix]: + raise KeyError, 'only support creation of document in %s' %self.reserved_ns[prefix] + qualifiedName = '%s:%s' %(prefix,localName) + + document = self._dom.createDocument(nsuri=namespaceURI, qname=qualifiedName, doctype=doctype) + self.node = document.childNodes[0] + + #set up reserved namespace attributes + for prefix,nsuri in self.reserved_ns.items(): + self._setAttributeNS(namespaceURI=self._xmlns_nsuri, + qualifiedName='%s:%s' %(self._xmlns_prefix,prefix), + value=nsuri) + + ############################################# + #Methods for attributes + ############################################# + def hasAttribute(self, namespaceURI, localName): + return self._dom.hasAttr(self._getNode(), name=localName, nsuri=namespaceURI) + + def setAttributeType(self, namespaceURI, localName): + '''set xsi:type + Keyword arguments: + namespaceURI -- namespace of attribute value + localName -- name of new attribute value + + ''' + self.logger.debug('setAttributeType: (%s,%s)', namespaceURI, localName) + value = localName + if namespaceURI: + value = '%s:%s' %(self.getPrefix(namespaceURI),localName) + self._setAttributeNS(self._xsi_nsuri, '%s:type' %self._xsi_prefix, value) + + def createAttributeNS(self, namespace, name, value): + document = self._getOwnerDocument() + attrNode = document.createAttributeNS(namespace, name, value) + + def setAttributeNS(self, namespaceURI, localName, value): + ''' + Keyword arguments: + namespaceURI -- namespace of attribute to create, None is for + attributes in no namespace. + localName -- local name of new attribute + value -- value of new attribute + ''' + prefix = None + if namespaceURI: + try: + prefix = self.getPrefix(namespaceURI) + except KeyError, ex: + prefix = 'ns2' + self.setNamespaceAttribute(prefix, namespaceURI) + qualifiedName = localName + if prefix: + qualifiedName = '%s:%s' %(prefix, localName) + self._setAttributeNS(namespaceURI, qualifiedName, value) + + def setNamespaceAttribute(self, prefix, namespaceURI): + ''' + Keyword arguments: + prefix -- xmlns prefix + namespaceURI -- value of prefix + ''' + self._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI) + + ############################################# + #Methods for elements + ############################################# + def createElementNS(self, namespace, qname): + ''' + Keyword arguments: + namespace -- namespace of element to create + qname -- qualified name of new element + ''' + document = self._getOwnerDocument() + node = document.createElementNS(namespace, qname) + return ElementProxy(self.sw, node) + + def createAppendSetElement(self, namespaceURI, localName, prefix=None): + '''Create a new element (namespaceURI,name), append it + to current node, then set it to be the current node. + Keyword arguments: + namespaceURI -- namespace of element to create + localName -- local name of new element + prefix -- if namespaceURI is not defined, declare prefix. defaults + to 'ns1' if left unspecified. + ''' + node = self.createAppendElement(namespaceURI, localName, prefix=None) + node=node._getNode() + self._setNode(node._getNode()) + + def createAppendElement(self, namespaceURI, localName, prefix=None): + '''Create a new element (namespaceURI,name), append it + to current node, and return the newly created node. + Keyword arguments: + namespaceURI -- namespace of element to create + localName -- local name of new element + prefix -- if namespaceURI is not defined, declare prefix. defaults + to 'ns1' if left unspecified. + ''' + declare = False + qualifiedName = localName + if namespaceURI: + try: + prefix = self.getPrefix(namespaceURI) + except: + declare = True + prefix = prefix or self._getUniquePrefix() + if prefix: + qualifiedName = '%s:%s' %(prefix, localName) + node = self.createElementNS(namespaceURI, qualifiedName) + if declare: + node._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI) + self._appendChild(node=node._getNode()) + return node + + def createInsertBefore(self, namespaceURI, localName, refChild): + qualifiedName = localName + prefix = self.getPrefix(namespaceURI) + if prefix: + qualifiedName = '%s:%s' %(prefix, localName) + node = self.createElementNS(namespaceURI, qualifiedName) + self._insertBefore(newChild=node._getNode(), refChild=refChild._getNode()) + return node + + def getElement(self, namespaceURI, localName): + ''' + Keyword arguments: + namespaceURI -- namespace of element + localName -- local name of element + ''' + node = self._dom.getElement(self.node, localName, namespaceURI, default=None) + if node: + return ElementProxy(self.sw, node) + return None + + def getAttributeValue(self, namespaceURI, localName): + ''' + Keyword arguments: + namespaceURI -- namespace of attribute + localName -- local name of attribute + ''' + if self.hasAttribute(namespaceURI, localName): + attr = self.node.getAttributeNodeNS(namespaceURI,localName) + return attr.value + return None + + def getValue(self): + return self._dom.getElementText(self.node, preserve_ws=True) + + ############################################# + #Methods for text nodes + ############################################# + def createAppendTextNode(self, pyobj): + node = self.createTextNode(pyobj) + self._appendChild(node=node._getNode()) + return node + + def createTextNode(self, pyobj): + document = self._getOwnerDocument() + node = document.createTextNode(pyobj) + return ElementProxy(self.sw, node) + + ############################################# + #Methods for retrieving namespaceURI's + ############################################# + def findNamespaceURI(self, qualifiedName): + parts = SplitQName(qualifiedName) + element = self._getNode() + if len(parts) == 1: + return (self._dom.findTargetNS(element), value) + return self._dom.findNamespaceURI(parts[0], element) + + def resolvePrefix(self, prefix): + element = self._getNode() + return self._dom.findNamespaceURI(prefix, element) + + def getSOAPEnvURI(self): + return self._soap_env_nsuri + + def isEmpty(self): + return not self.node + class Collection(UserDict): @@ -845,3 +1330,4 @@ if 1: return clone xml.dom.minidom._clone_node = _clone_node + diff --git a/WSDLTools.py b/WSDLTools.py index 2b5d506..65e4f28 100755 --- a/WSDLTools.py +++ b/WSDLTools.py @@ -9,11 +9,11 @@ ident = "$Id$" -from Utility import DOM, Collection, CollectionNS +import urllib, weakref +from cStringIO import StringIO +from Namespaces import OASIS, WSA, XMLNS +from Utility import Collection, CollectionNS, DOM, ElementProxy from XMLSchema import XMLSchema, SchemaReader, WSDLToolsAdapter -from Namespaces import WSR, WSA -from StringIO import StringIO -import urllib class WSDLReader: @@ -130,6 +130,39 @@ class WSDL: self.imports[namespace] = item return item + def toDom(self): + """ Generate a DOM representation of the WSDL instance. + Not dealing with generating XML Schema, thus the targetNamespace + of all XML Schema elements or types used by WSDL message parts + needs to be specified via import information items. + """ + namespaceURI = DOM.GetWSDLUri(self.version) + self.document = DOM.createDocument(namespaceURI ,'wsdl:definitions') + + # Set up a couple prefixes for easy reading. + child = DOM.getElement(self.document, None) + child.setAttributeNS(None, 'targetNamespace', self.targetNamespace) + child.setAttributeNS(XMLNS.BASE, 'xmlns:wsdl', namespaceURI) + child.setAttributeNS(XMLNS.BASE, 'xmlns:xsd', 'http://www.w3.org/1999/XMLSchema') + child.setAttributeNS(XMLNS.BASE, 'xmlns:soap', 'http://schemas.xmlsoap.org/wsdl/soap/') + child.setAttributeNS(XMLNS.BASE, 'xmlns:tns', self.targetNamespace) + + # wsdl:import + for item in self.imports: + item.toDom() + # wsdl:message + for item in self.messages: + item.toDom() + # wsdl:portType + for item in self.portTypes: + item.toDom() + # wsdl:binding + for item in self.bindings: + item.toDom() + # wsdl:service + for item in self.services: + item.toDom() + def load(self, document): # We save a reference to the DOM document to ensure that elements # saved as "extensions" will continue to have a meaningful context @@ -330,6 +363,7 @@ class Element: self.extensions = [] def addExtension(self, item): + item.parent = weakref.ref(self) self.extensions.append(item) @@ -338,6 +372,17 @@ class ImportElement(Element): self.namespace = namespace self.location = location + def getWSDL(self): + """Return the WSDL object that contains this Message Part.""" + return self.parent().parent() + + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'import') + epc.setAttributeNS(None, 'namespace', self.namespace) + epc.setAttributeNS(None, 'location', self.location) + _loaded = None @@ -393,6 +438,19 @@ class Message(Element): if elemref is not None: part.element = ParseTypeRef(elemref, element) + def getWSDL(self): + """Return the WSDL object that contains this Message Part.""" + return self.parent().parent() + + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'message') + epc.setAttributeNS(None, 'name', self.name) + + for part in self.parts: + part.toDom(epc._getNode()) + class MessagePart(Element): def __init__(self, name): @@ -416,6 +474,22 @@ class MessagePart(Element): schema = wsdl.types.get(nsuri, {}) return schema.get(name) + def toDom(self, node): + """node -- node representing message""" + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'part') + epc.setAttributeNS(None, 'name', self.name) + + if self.element is not None: + ns,name = self.element + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'element', '%s:%s'%(prefix,name)) + elif self.type is not None: + ns,name = self.type + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'type', '%s:%s'%(prefix,name)) + class PortType(Element): '''PortType has a anyAttribute, thus must provide for an extensible @@ -455,8 +529,8 @@ class PortType(Element): self.documentation = GetDocumentation(element) self.targetNamespace = DOM.getAttr(element, 'targetNamespace') - if DOM.hasAttr(element, 'ResourceProperties', WSR.PROPERTIES): - rpref = DOM.getAttr(element, 'ResourceProperties', WSR.PROPERTIES) + if DOM.hasAttr(element, 'ResourceProperties', OASIS.PROPERTIES): + rpref = DOM.getAttr(element, 'ResourceProperties', OASIS.PROPERTIES) self.resourceProperties = ParseQName(rpref, element) NS_WSDL = DOM.GetWSDLUri(self.getWSDL().version) @@ -501,6 +575,20 @@ class PortType(Element): action = DOM.getAttr(item, 'Action', WSA.ADDRESS, None) operation.addFault(message, name, docs, action) + def toDom(self): + wsdl = self.getWSDL() + + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'portType') + epc.setAttributeNS(None, 'name', self.name) + if self.resourceProperties: + ns,name = self.resourceProperties + prefix = epc.getPrefix(ns) + epc.setAttributeNS(OASIS.PROPERTIES, 'ResourceProperties', '%s:%s'%(prefix,name)) + + for op in self.operations: + op.toDom(epc._getNode()) + class Operation(Element): @@ -511,6 +599,10 @@ class Operation(Element): self.input = None self.output = None + def getWSDL(self): + """Return the WSDL object that contains this Operation.""" + return self.parent().parent().parent().parent() + def getPortType(self): return self.parent().parent() @@ -553,12 +645,28 @@ class Operation(Element): def setInput(self, message, name='', documentation='', action=None): self.input = MessageRole('input', message, name, documentation, action) + self.input.parent = weakref.ref(self) return self.input def setOutput(self, message, name='', documentation='', action=None): self.output = MessageRole('output', message, name, documentation, action) + self.output.parent = weakref.ref(self) return self.output + def toDom(self, node): + wsdl = self.getWSDL() + + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'operation') + epc.setAttributeNS(None, 'name', self.name) + node = epc._getNode() + if self.input: + self.input.toDom(node) + if self.output: + self.output.toDom(node) + for fault in self.faults: + fault.toDom(node) + class MessageRole(Element): def __init__(self, type, message, name='', documentation='', action=None): @@ -567,6 +675,22 @@ class MessageRole(Element): self.type = type self.action = action + def getWSDL(self): + """Return the WSDL object that contains this MessageRole.""" + if self.parent().getWSDL() == 'fault': + return self.parent().parent().getWSDL() + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), self.type) + epc.setAttributeNS(None, 'message', self.message) + + if self.action: + epc.setAttributeNS(WSA.ADDRESS2004, 'Action', self.action) + class Binding(Element): def __init__(self, name, type, documentation=''): @@ -641,6 +765,22 @@ class Binding(Element): else: self.addExtension(e) + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'binding') + epc.setAttributeNS(None, 'name', self.name) + + ns,name = self.type + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, 'type', '%s:%s' %(prefix,name)) + + node = epc._getNode() + for ext in self.extensions: + ext.toDom(node) + for op_binding in self.operations: + op_binding.toDom(node) + class OperationBinding(Element): def __init__(self, name, documentation=''): @@ -649,6 +789,11 @@ class OperationBinding(Element): self.output = None self.faults = Collection(self) + def getWSDL(self): + """Return the WSDL object that contains this binding.""" + return self.parent().parent().parent().parent() + + def getBinding(self): """Return the parent Binding object of the operation binding.""" return self.parent().parent() @@ -669,12 +814,14 @@ class OperationBinding(Element): def addInputBinding(self, binding): if self.input is None: self.input = MessageRoleBinding('input') + self.input.parent = weakref.ref(self) self.input.addExtension(binding) return binding def addOutputBinding(self, binding): if self.output is None: self.output = MessageRoleBinding('output') + self.output.parent = weakref.ref(self) self.output.addExtension(binding) return binding @@ -702,12 +849,34 @@ class OperationBinding(Element): else: self.addExtension(e) + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), 'operation') + epc.setAttributeNS(None, 'name', self.name) + + node = epc._getNode() + for ext in self.extensions: + ext.toDom(node) + if self.input: + self.input.toDom(node) + if self.output: + self.output.toDom(node) + for fault in self.faults: + fault.toDom(node) + class MessageRoleBinding(Element): def __init__(self, type, name='', documentation=''): Element.__init__(self, name, documentation) self.type = type + def getWSDL(self): + """Return the WSDL object that contains this MessageRole.""" + if self.type == 'fault': + return self.parent().parent().getWSDL() + return self.parent().getWSDL() + def findBinding(self, kind): for item in self.extensions: if isinstance(item, kind): @@ -795,6 +964,15 @@ class MessageRoleBinding(Element): else: self.addExtension(e) + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), self.type) + + node = epc._getNode() + for item in self.extensions: + if item: item.toDom(node) + class Service(Element): def __init__(self, name, documentation=''): @@ -826,12 +1004,25 @@ class Service(Element): for e in elements: self.addExtension(e) + def toDom(self): + wsdl = self.getWSDL() + ep = ElementProxy(None, DOM.getElement(wsdl.document, None)) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), "service") + epc.setAttributeNS(None, "name", self.name) + + node = epc._getNode() + for port in self.ports: + port.toDom(node) + class Port(Element): def __init__(self, name, binding, documentation=''): Element.__init__(self, name, documentation) self.binding = binding + def getWSDL(self): + return self.parent().parent().getWSDL() + def getService(self): """Return the Service object associated with this port.""" return self.parent().parent() @@ -874,23 +1065,69 @@ class Port(Element): else: self.addExtension(e) + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLUri(wsdl.version), "port") + epc.setAttributeNS(None, "name", self.name) + + ns,name = self.binding + prefix = epc.getPrefix(ns) + epc.setAttributeNS(None, "binding", "%s:%s" %(prefix,name)) + + node = epc._getNode() + for ext in self.extensions: + ext.toDom(node) + class SoapBinding: def __init__(self, transport, style='rpc'): self.transport = transport self.style = style + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'binding') + if self.transport: + epc.setAttributeNS(None, "transport", self.transport) + if self.style: + epc.setAttributeNS(None, "style", self.style) class SoapAddressBinding: def __init__(self, location): self.location = location + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'address') + epc.setAttributeNS(None, "location", self.location) + class SoapOperationBinding: def __init__(self, soapAction=None, style=None): self.soapAction = soapAction self.style = style + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'operation') + if self.soapAction: + epc.setAttributeNS(None, 'soapAction', self.soapAction) + if self.style: + epc.setAttributeNS(None, 'style', self.style) + class SoapBodyBinding: def __init__(self, use, namespace=None, encodingStyle=None, parts=None): @@ -905,6 +1142,17 @@ class SoapBodyBinding: self.parts = parts self.use = use + def getWSDL(self): + return self.parent().getWSDL() + + def toDom(self, node): + wsdl = self.getWSDL() + ep = ElementProxy(None, node) + epc = ep.createAppendElement(DOM.GetWSDLSoapBindingUri(wsdl.version), 'body') + epc.setAttributeNS(None, "use", self.use) + epc.setAttributeNS(None, "namespace", self.namespace) + + class SoapFaultBinding: def __init__(self, name, use, namespace=None, encodingStyle=None): if not use in ('literal', 'encoded'): diff --git a/__init__.py b/__init__.py index 21be72e..e239407 100644 --- a/__init__.py +++ b/__init__.py @@ -3,10 +3,7 @@ ident = "$Id$" -import WSDLTools +#import WSDLTools import XMLname import logging -class Base: - def __init__(self, module=__name__): - self.logger = logging.getLogger('%s-%s(%x)' %(module, self.__class__, id(self))) diff --git a/c14n.py b/c14n.py new file mode 100755 index 0000000..e56a354 --- /dev/null +++ b/c14n.py @@ -0,0 +1,505 @@ +#! /usr/bin/env python +"""Compatibility module, imported by ZSI if you don't have PyXML 0.7. + +No copyright violations -- we're only using parts of PyXML that we +wrote. +""" + +from ZSI import _attrs, _children, _copyright + +_copyright += "\n\nPortions are also: " +_copyright += '''Copyright 2001, Zolera Systems Inc. All Rights Reserved. +Copyright 2001, MIT. All Rights Reserved. + +Distributed under the terms of: + Python 2.0 License or later. + http://www.python.org/2.0.1/license.html +or + W3C Software License + http://www.w3.org/Consortium/Legal/copyright-software-19980720 +''' + +from xml.dom import Node +from Namespaces import XMLNS +import cStringIO as StringIO +try: + from xml.dom.ext import c14n +except ImportError, ex: + _implementation2 = None +else: + class _implementation2(c14n._implementation): + """Patch for exclusive c14n + """ + def __init__(self, node, write, **kw): + self.unsuppressedPrefixes = kw.get('unsuppressedPrefixes') + self._exclusive = None + if node.nodeType == Node.ELEMENT_NODE: + if not c14n._inclusive(self): + self._exclusive = self._inherit_context(node) + c14n._implementation.__init__(self, node, write, **kw) + + def _do_element(self, node, initial_other_attrs = []): + """Patch for the xml.dom.ext.c14n implemenation _do_element method. + This fixes a problem with sorting of namespaces. + """ + # Get state (from the stack) make local copies. + # ns_parent -- NS declarations in parent + # ns_rendered -- NS nodes rendered by ancestors + # ns_local -- NS declarations relevant to this element + # xml_attrs -- Attributes in XML namespace from parent + # xml_attrs_local -- Local attributes in XML namespace. + ns_parent, ns_rendered, xml_attrs = \ + self.state[0], self.state[1].copy(), self.state[2].copy() #0422 + ns_local = ns_parent.copy() + xml_attrs_local = {} + + # Divide attributes into NS, XML, and others. + #other_attrs = initial_other_attrs[:] + other_attrs = [] + sort_these_attrs = initial_other_attrs[:] + + in_subset = c14n._in_subset(self.subset, node) + #for a in _attrs(node): + sort_these_attrs +=c14n._attrs(node) + + for a in sort_these_attrs: + if a.namespaceURI == c14n.XMLNS.BASE: + n = a.nodeName + if n == "xmlns:": n = "xmlns" # DOM bug workaround + ns_local[n] = a.nodeValue + elif a.namespaceURI == c14n.XMLNS.XML: + if c14n._inclusive(self) or (in_subset and c14n._in_subset(self.subset, a)): #020925 Test to see if attribute node in subset + xml_attrs_local[a.nodeName] = a #0426 + else: + if c14n._in_subset(self.subset, a): #020925 Test to see if attribute node in subset + other_attrs.append(a) + #add local xml:foo attributes to ancestor's xml:foo attributes + xml_attrs.update(xml_attrs_local) + + # Render the node + W, name = self.write, None + if in_subset: + name = node.nodeName + W('<') + W(name) + + # Create list of NS attributes to render. + ns_to_render = [] + for n,v in ns_local.items(): + + # If default namespace is XMLNS.BASE or empty, + # and if an ancestor was the same + if n == "xmlns" and v in [ c14n.XMLNS.BASE, '' ] \ + and ns_rendered.get('xmlns') in [ c14n.XMLNS.BASE, '', None ]: + continue + + # "omit namespace node with local name xml, which defines + # the xml prefix, if its string value is + # http://www.w3.org/XML/1998/namespace." + if n in ["xmlns:xml", "xml"] \ + and v in [ 'http://www.w3.org/XML/1998/namespace' ]: + continue + + + # If not previously rendered + # and it's inclusive or utilized + if (n,v) not in ns_rendered.items() \ + and (c14n._inclusive(self) or \ + c14n._utilized(n, node, other_attrs, self.unsuppressedPrefixes)): + ns_to_render.append((n, v)) + + ##################################### + # JRB + ##################################### + if not c14n._inclusive(self): + if node.prefix is None: + look_for = [('xmlns', node.namespaceURI),] + else: + look_for = [('xmlns:%s' %node.prefix, node.namespaceURI),] + for a in c14n._attrs(node): + if a.namespaceURI != XMLNS.BASE: + #print "ATTRIBUTE: ", (a.namespaceURI, a.prefix) + if a.prefix: + #print "APREFIX: ", a.prefix + look_for.append(('xmlns:%s' %a.prefix, a.namespaceURI)) + + for key,namespaceURI in look_for: + if ns_rendered.has_key(key): + if ns_rendered[key] == namespaceURI: + # Dont write out + pass + else: + #ns_to_render += [(key, namespaceURI)] + pass + elif (key,namespaceURI) in ns_to_render: + # Dont write out + pass + else: + # Unique write out, rewrite to render + ns_local[key] = namespaceURI + for a in self._exclusive: + if a.nodeName == key: + #self._do_attr(a.nodeName, a.value) + #ns_rendered[key] = namespaceURI + #break + ns_to_render += [(a.nodeName, a.value)] + break + elif key is None and a.nodeName == 'xmlns': + #print "DEFAULT: ", (a.nodeName, a.value) + ns_to_render += [(a.nodeName, a.value)] + break + #print "KEY: ", key + else: + #print "Look for: ", look_for + #print "NS_TO_RENDER: ", ns_to_render + #print "EXCLUSIVE NS: ", map(lambda f: (f.nodeName,f.value),self._exclusive) + raise RuntimeError, \ + 'can not find namespace (%s="%s") for exclusive canonicalization'\ + %(key, namespaceURI) + ##################################### + + + + # Sort and render the ns, marking what was rendered. + ns_to_render.sort(c14n._sorter_ns) + for n,v in ns_to_render: + #XXX JRB, getting 'xmlns,None' here when xmlns='' + if v: self._do_attr(n, v) + else: + v = '' + self._do_attr(n, v) + ns_rendered[n]=v #0417 + + # If exclusive or the parent is in the subset, add the local xml attributes + # Else, add all local and ancestor xml attributes + # Sort and render the attributes. + if not c14n._inclusive(self) or c14n._in_subset(self.subset,node.parentNode): #0426 + other_attrs.extend(xml_attrs_local.values()) + else: + other_attrs.extend(xml_attrs.values()) + #print "OTHER: ", other_attrs + other_attrs.sort(c14n._sorter) + for a in other_attrs: + self._do_attr(a.nodeName, a.value) + W('>') + + + # Push state, recurse, pop state. + state, self.state = self.state, (ns_local, ns_rendered, xml_attrs) + for c in c14n._children(node): + c14n._implementation.handlers[c.nodeType](self, c) + self.state = state + + if name: W('' % name) + c14n._implementation.handlers[c14n.Node.ELEMENT_NODE] = _do_element + + +_IN_XML_NS = lambda n: n.namespaceURI == XMLNS.XML + +# Does a document/PI has lesser/greater document order than the +# first element? +_LesserElement, _Element, _GreaterElement = range(3) + +def _sorter(n1,n2): + '''_sorter(n1,n2) -> int + Sorting predicate for non-NS attributes.''' + + i = cmp(n1.namespaceURI, n2.namespaceURI) + if i: return i + return cmp(n1.localName, n2.localName) + + +def _sorter_ns(n1,n2): + '''_sorter_ns((n,v),(n,v)) -> int + "(an empty namespace URI is lexicographically least)."''' + + if n1[0] == 'xmlns': return -1 + if n2[0] == 'xmlns': return 1 + return cmp(n1[0], n2[0]) + +def _utilized(n, node, other_attrs, unsuppressedPrefixes): + '''_utilized(n, node, other_attrs, unsuppressedPrefixes) -> boolean + Return true if that nodespace is utilized within the node''' + + if n.startswith('xmlns:'): + n = n[6:] + elif n.startswith('xmlns'): + n = n[5:] + if n == node.prefix or n in unsuppressedPrefixes: return 1 + for attr in other_attrs: + if n == attr.prefix: return 1 + return 0 + +_in_subset = lambda subset, node: not subset or node in subset + +# +# JRB. Currently there is a bug in do_element, but since the underlying +# Data Structures in c14n have changed I can't just apply the +# _implementation2 patch above. But this will work OK for most uses, +# just not XML Signatures. +# +class _implementation: + '''Implementation class for C14N. This accompanies a node during it's + processing and includes the parameters and processing state.''' + + # Handler for each node type; populated during module instantiation. + handlers = {} + + def __init__(self, node, write, **kw): + '''Create and run the implementation.''' + + self.write = write + self.subset = kw.get('subset') + if self.subset: + self.comments = kw.get('comments', 1) + else: + self.comments = kw.get('comments', 0) + self.unsuppressedPrefixes = kw.get('unsuppressedPrefixes') + nsdict = kw.get('nsdict', { 'xml': XMLNS.XML, 'xmlns': XMLNS.BASE }) + + # Processing state. + self.state = (nsdict, ['xml'], []) + + if node.nodeType == Node.DOCUMENT_NODE: + self._do_document(node) + elif node.nodeType == Node.ELEMENT_NODE: + self.documentOrder = _Element # At document element + if self.unsuppressedPrefixes is not None: + self._do_element(node) + else: + inherited = self._inherit_context(node) + self._do_element(node, inherited) + elif node.nodeType == Node.DOCUMENT_TYPE_NODE: + pass + else: + raise TypeError, str(node) + + + def _inherit_context(self, node): + '''_inherit_context(self, node) -> list + Scan ancestors of attribute and namespace context. Used only + for single element node canonicalization, not for subset + canonicalization.''' + + # Collect the initial list of xml:foo attributes. + xmlattrs = filter(_IN_XML_NS, _attrs(node)) + + # Walk up and get all xml:XXX attributes we inherit. + inherited, parent = [], node.parentNode + while parent and parent.nodeType == Node.ELEMENT_NODE: + for a in filter(_IN_XML_NS, _attrs(parent)): + n = a.localName + if n not in xmlattrs: + xmlattrs.append(n) + inherited.append(a) + parent = parent.parentNode + return inherited + + + def _do_document(self, node): + '''_do_document(self, node) -> None + Process a document node. documentOrder holds whether the document + element has been encountered such that PIs/comments can be written + as specified.''' + + self.documentOrder = _LesserElement + for child in node.childNodes: + if child.nodeType == Node.ELEMENT_NODE: + self.documentOrder = _Element # At document element + self._do_element(child) + self.documentOrder = _GreaterElement # After document element + elif child.nodeType == Node.PROCESSING_INSTRUCTION_NODE: + self._do_pi(child) + elif child.nodeType == Node.COMMENT_NODE: + self._do_comment(child) + elif child.nodeType == Node.DOCUMENT_TYPE_NODE: + pass + else: + raise TypeError, str(child) + handlers[Node.DOCUMENT_NODE] = _do_document + + + def _do_text(self, node): + '''_do_text(self, node) -> None + Process a text or CDATA node. Render various special characters + as their C14N entity representations.''' + if not _in_subset(self.subset, node): return + s = node.data \ + .replace("&", "&") \ + .replace("<", "<") \ + .replace(">", ">") \ + .replace("\015", " ") + if s: self.write(s) + handlers[Node.TEXT_NODE] = _do_text + handlers[Node.CDATA_SECTION_NODE] = _do_text + + + def _do_pi(self, node): + '''_do_pi(self, node) -> None + Process a PI node. Render a leading or trailing #xA if the + document order of the PI is greater or lesser (respectively) + than the document element. + ''' + if not _in_subset(self.subset, node): return + W = self.write + if self.documentOrder == _GreaterElement: W('\n') + W('') + if self.documentOrder == _LesserElement: W('\n') + handlers[Node.PROCESSING_INSTRUCTION_NODE] = _do_pi + + + def _do_comment(self, node): + '''_do_comment(self, node) -> None + Process a comment node. Render a leading or trailing #xA if the + document order of the comment is greater or lesser (respectively) + than the document element. + ''' + if not _in_subset(self.subset, node): return + if self.comments: + W = self.write + if self.documentOrder == _GreaterElement: W('\n') + W('') + if self.documentOrder == _LesserElement: W('\n') + handlers[Node.COMMENT_NODE] = _do_comment + + + def _do_attr(self, n, value): + ''''_do_attr(self, node) -> None + Process an attribute.''' + + W = self.write + W(' ') + W(n) + W('="') + s = value \ + .replace("&", "&") \ + .replace("<", "<") \ + .replace('"', '"') \ + .replace('\011', ' ') \ + .replace('\012', ' ') \ + .replace('\015', ' ') + W(s) + W('"') + + def _do_element(self, node, initial_other_attrs = []): + '''_do_element(self, node, initial_other_attrs = []) -> None + Process an element (and its children).''' + + # Get state (from the stack) make local copies. + # ns_parent -- NS declarations in parent + # ns_rendered -- NS nodes rendered by ancestors + # xml_attrs -- Attributes in XML namespace from parent + # ns_local -- NS declarations relevant to this element + ns_parent, ns_rendered, xml_attrs = \ + self.state[0], self.state[1][:], self.state[2][:] + ns_local = ns_parent.copy() + + # Divide attributes into NS, XML, and others. + other_attrs = initial_other_attrs[:] + in_subset = _in_subset(self.subset, node) + for a in _attrs(node): + if a.namespaceURI == XMLNS.BASE: + n = a.nodeName + if n == "xmlns:": n = "xmlns" # DOM bug workaround + ns_local[n] = a.nodeValue + elif a.namespaceURI == XMLNS.XML: + if self.unsuppressedPrefixes is None or in_subset: + xml_attrs.append(a) + else: + other_attrs.append(a) + + # Render the node + W, name = self.write, None + if in_subset: + name = node.nodeName + W('<') + W(name) + + # Create list of NS attributes to render. + ns_to_render = [] + for n,v in ns_local.items(): + pval = ns_parent.get(n) + + # If default namespace is XMLNS.BASE or empty, skip + if n == "xmlns" \ + and v in [ XMLNS.BASE, '' ] and pval in [ XMLNS.BASE, '' ]: + continue + + # "omit namespace node with local name xml, which defines + # the xml prefix, if its string value is + # http://www.w3.org/XML/1998/namespace." + if n == "xmlns:xml" \ + and v in [ 'http://www.w3.org/XML/1998/namespace' ]: + continue + + # If different from parent, or parent didn't render + # and if not exclusive, or this prefix is needed or + # not suppressed + if (v != pval or n not in ns_rendered) \ + and (self.unsuppressedPrefixes is None or \ + _utilized(n, node, other_attrs, self.unsuppressedPrefixes)): + ns_to_render.append((n, v)) + + # Sort and render the ns, marking what was rendered. + ns_to_render.sort(_sorter_ns) + for n,v in ns_to_render: + self._do_attr(n, v) + ns_rendered.append(n) + + # Add in the XML attributes (don't pass to children, since + # we're rendering them), sort, and render. + other_attrs.extend(xml_attrs) + xml_attrs = [] + other_attrs.sort(_sorter) + for a in other_attrs: + self._do_attr(a.nodeName, a.value) + W('>') + + # Push state, recurse, pop state. + state, self.state = self.state, (ns_local, ns_rendered, xml_attrs) + for c in _children(node): + _implementation.handlers[c.nodeType](self, c) + self.state = state + + if name: W('' % name) + handlers[Node.ELEMENT_NODE] = _do_element + + +def Canonicalize(node, output=None, **kw): + '''Canonicalize(node, output=None, **kw) -> UTF-8 + + Canonicalize a DOM document/element node and all descendents. + Return the text; if output is specified then output.write will + be called to output the text and None will be returned + Keyword parameters: + nsdict: a dictionary of prefix:uri namespace entries + assumed to exist in the surrounding context + comments: keep comments if non-zero (default is 0) + subset: Canonical XML subsetting resulting from XPath + (default is []) + unsuppressedPrefixes: do exclusive C14N, and this specifies the + prefixes that should be inherited. + ''' + if output: + if _implementation2 is None: + _implementation(node, output.write, **kw) + else: + apply(_implementation2, (node, output.write), kw) + else: + s = c14n.StringIO.StringIO() + if _implementation2 is None: + _implementation(node, s.write, **kw) + else: + apply(_implementation2, (node, s.write), kw) + return s.getvalue() + + +if __name__ == '__main__': print _copyright