OwlCyberSecurity - MANAGER
Edit File: carddavxml.py
## # Copyright (c) 2005-2017 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## """ CardDAV XML Support. This module provides XML utilities for use with CardDAV. This API is considered private to static.py and is therefore subject to change. See draft spec: """ from txdav.xml.element import registerElement, dav_namespace from txdav.xml.element import WebDAVElement, PCDATAElement from txdav.xml.element import WebDAVEmptyElement, WebDAVTextElement from txdav.xml.element import ResourceType, Collection from twistedcaldav.config import config from twistedcaldav.vcard import Component ## # CardDAV objects ## carddav_namespace = "urn:ietf:params:xml:ns:carddav" carddav_compliance = ( "addressbook", ) class CardDAVElement (WebDAVElement): """ CardDAV XML element. """ namespace = carddav_namespace class CardDAVEmptyElement (WebDAVEmptyElement): """ CardDAV element with no contents. """ namespace = carddav_namespace class CardDAVTextElement (WebDAVTextElement): """ CardDAV element containing PCDATA. """ namespace = carddav_namespace class CardDAVDataMixin(object): """ A mixin to support accept/returning data in various formats. """ def __init__(self, *children, **attributes): if "content-type" in attributes: self.content_type = attributes["content-type"] else: self.content_type = "text/vcard" if "version" in attributes: self.version = attributes["version"] else: self.version = "3.0" super(CardDAVDataMixin, self).__init__(*children, **attributes) def verifyTypeVersion(self): """ Make sure any content-type and version matches at least one supported set. @return: C{True} if there is at least one match, C{False} otherwise. """ allowedTypes = set() allowedTypes.add(("text/vcard", "3.0",)) if config.EnableJSONData: allowedTypes.add(("application/vcard+json", "3.0",)) for format, version in allowedTypes: if (format == self.content_type) and (version == self.version): return True return False @classmethod def fromAddress(clazz, address, format=None): attrs = {} if format is not None and format != "text/vcard": attrs["content-type"] = format if isinstance(address, str): if not address: raise ValueError("Missing address data") return clazz(PCDATAElement(address), **attrs) elif isinstance(address, Component): assert address.name() == "VCARD", "Not a vCard: %r" % (address,) return clazz(PCDATAElement(address.getText(format)), **attrs) else: raise ValueError("Not an address: %s" % (address,)) fromTextData = fromAddress fromComponent = fromAddress def address(self): """ Returns an address component derived from this element. """ data = self.addressData() if data: return Component.fromString(data, format=self.content_type) else: return None generateComponent = address def addressData(self): """ Returns the address data derived from this element. """ for data in self.children: if not isinstance(data, PCDATAElement): return None else: # We guaranteed in __init__() that there is only one child... break return str(data) textData = addressData @registerElement class AddressBookHomeSet (CardDAVElement): """ The address book collections URLs for this principal. (CardDAV, RFC 6352 section 7.1.1) """ name = "addressbook-home-set" hidden = True allowed_children = {(dav_namespace, "href"): (0, None)} @registerElement class AddressBookDescription (CardDAVTextElement): """ Provides a human-readable description of what this address book collection represents. (CardDAV, RFC 6352 section 6.2.1) """ name = "addressbook-description" hidden = True # May be protected; but we'll let the client set this if they like. @registerElement class SupportedAddressData (CardDAVElement): """ Specifies restrictions on an address book collection. (CardDAV, RFC 6352 section 6.2.2) """ name = "supported-address-data" hidden = True protected = True allowed_children = {(carddav_namespace, "address-data-type"): (0, None)} @registerElement class MaxResourceSize (CardDAVTextElement): """ Specifies restrictions on an address book collection. (CardDAV, RFC 6352 section 6.2.3) """ name = "max-resource-size" hidden = True protected = True @registerElement class AddressBook (CardDAVEmptyElement): """ Denotes an address book collection. (CardDAV, RFC 6352 sections 5.2, 10.1) """ name = "addressbook" @registerElement class AddressBookQuery (CardDAVElement): """ Defines a report for querying address book data. (CardDAV, RFC 6352 section 10.3) """ name = "addressbook-query" allowed_children = { (dav_namespace, "allprop"): (0, None), (dav_namespace, "propname"): (0, None), (dav_namespace, "prop"): (0, None), (carddav_namespace, "filter"): (0, 1), # Actually (1, 1) unless element is empty (carddav_namespace, "limit"): (0, None), } def __init__(self, *children, **attributes): super(AddressBookQuery, self).__init__(*children, **attributes) props = None filter = None limit = None for child in self.children: qname = child.qname() if qname in ( (dav_namespace, "allprop"), (dav_namespace, "propname"), (dav_namespace, "prop"), ): if props is not None: raise ValueError("Only one of CardDAV:allprop, CardDAV:propname, CardDAV:prop allowed") props = child elif qname == (carddav_namespace, "filter"): filter = child elif qname == (carddav_namespace, "limit"): # type check child.childOfType(NResults) limit = child else: raise AssertionError("We shouldn't be here") if len(self.children) > 0: if filter is None: raise ValueError("CARDDAV:filter required") self.props = props self.filter = filter self.limit = limit @registerElement class AddressDataType (CardDAVEmptyElement): """ Defines which parts of a address component object should be returned by a report. (CardDAV, RFC 6352 section 6.2.2) """ name = "address-data-type" allowed_attributes = { "content-type": False, "version": False, } @registerElement class AddressData (CardDAVDataMixin, CardDAVElement): """ Defines which parts of a address component object should be returned by a report. (CardDAV, RFC 6352 section 10.4) """ name = "address-data" allowed_children = { (carddav_namespace, "allprop"): (0, 1), (carddav_namespace, "prop"): (0, None), PCDATAElement: (0, None), } allowed_attributes = { "content-type": False, "version": False, } def __init__(self, *children, **attributes): super(AddressData, self).__init__(*children, **attributes) properties = None data = None for child in self.children: qname = child.qname() if qname == (carddav_namespace, "allprop"): if properties is not None: raise ValueError( "CardDAV:allprop and CardDAV:prop may not be combined" ) properties = child elif qname == (carddav_namespace, "prop"): try: properties.append(child) except AttributeError: if properties is None: properties = [child] else: raise ValueError("CardDAV:allprop and CardDAV:prop may not be combined") elif isinstance(child, PCDATAElement): if data is None: data = child else: data += child else: raise AssertionError("We shouldn't be here") self.properties = properties if data is not None: try: if properties is not None: raise ValueError("Only one of allprop, prop (%r) or PCDATA (%r) allowed" % (properties, str(data))) except ValueError: if not data.isWhitespace(): raise else: # Since we've already combined PCDATA elements, we'd may as well # optimize them originals away self.children = (data,) @registerElement class AllProperties (CardDAVEmptyElement): """ Specifies that all properties shall be returned. (CardDAV, RFC 6352 section 10.4.1) """ name = "allprop" @registerElement class Property (CardDAVEmptyElement): """ Defines a property to return in a response. (CardDAV, RFC 6352 section 10.4.2) """ name = "prop" allowed_attributes = { "name": True, "novalue": False, } def __init__(self, *children, **attributes): super(Property, self).__init__(*children, **attributes) self.property_name = attributes["name"] if "novalue" in attributes: novalue = attributes["novalue"] if novalue == "yes": self.novalue = True elif novalue == "no": self.novalue = False else: raise ValueError("Invalid novalue: %r" % (novalue,)) else: self.novalue = False @registerElement class Filter (CardDAVElement): """ Determines which matching components are returned. (CardDAV, RFC 6352 section 10.5) """ name = "filter" allowed_children = {(carddav_namespace, "prop-filter"): (0, None)} allowed_attributes = {"test": False} @registerElement class PropertyFilter (CardDAVElement): """ Limits a search to specific properties. (CardDAV-access-09, RFC 6352 section 10.5.1) """ name = "prop-filter" allowed_children = { (carddav_namespace, "is-not-defined"): (0, 1), (carddav_namespace, "text-match"): (0, None), (carddav_namespace, "param-filter"): (0, None), } allowed_attributes = { "name": True, "test": False, } @registerElement class ParameterFilter (CardDAVElement): """ Limits a search to specific parameters. (CardDAV, RFC 6352 section 10.5.2) """ name = "param-filter" allowed_children = { (carddav_namespace, "is-not-defined"): (0, 1), (carddav_namespace, "text-match"): (0, 1), } allowed_attributes = {"name": True} @registerElement class Limit (WebDAVElement): """ Client supplied limit for reports. """ namespace = carddav_namespace name = "limit" allowed_children = { (carddav_namespace, "nresults"): (1, 1), } @registerElement class NResults (WebDAVTextElement): """ Number of results limit. """ namespace = carddav_namespace name = "nresults" @registerElement class IsNotDefined (CardDAVEmptyElement): """ Specifies that the named vCard item does not exist. (CardDAV, RFC 6352 section 10.5.3) """ name = "is-not-defined" @registerElement class TextMatch (CardDAVTextElement): """ Specifies a substring match on a property or parameter value. (CardDAV, RFC 6352 section 10.5.4) """ name = "text-match" def fromString(clazz, string): # @NoSelf if type(string) is str: return clazz(PCDATAElement(string)) elif type(string) is unicode: return clazz(PCDATAElement(string.encode("utf-8"))) else: return clazz(PCDATAElement(str(string))) fromString = classmethod(fromString) allowed_attributes = { "collation": False, "negate-condition": False, "match-type": False } @registerElement class AddressBookMultiGet (CardDAVElement): """ CardDAV report used to retrieve specific vCard items via their URIs. (CardDAV, RFC 6352 section 10.7) """ name = "addressbook-multiget" # To allow for an empty element in a supported-report-set property we need # to relax the child restrictions allowed_children = { (dav_namespace, "allprop"): (0, 1), (dav_namespace, "propname"): (0, 1), (dav_namespace, "prop"): (0, 1), (dav_namespace, "href"): (0, None), # Actually ought to be (1, None) } def __init__(self, *children, **attributes): super(AddressBookMultiGet, self).__init__(*children, **attributes) property = None resources = [] for child in self.children: qname = child.qname() if qname in ( (dav_namespace, "allprop"), (dav_namespace, "propname"), (dav_namespace, "prop"), ): if property is not None: raise ValueError("Only one of DAV:allprop, DAV:propname, DAV:prop allowed") property = child elif qname == (dav_namespace, "href"): resources.append(child) self.property = property self.resources = resources @registerElement class NoUIDConflict(CardDAVElement): """ CardDAV precondition used to indicate a UID conflict during PUT/COPY/MOVE. The conflicting resource href must be returned as a child. """ name = "no-uid-conflict" allowed_children = {(dav_namespace, "href"): (1, 1)} @registerElement class SupportedFilter(CardDAVElement): """ CardDAV precondition used to indicate an unsupported component type in a query filter. The conflicting filter elements are returned. """ name = "supported-filter" allowed_children = { (carddav_namespace, "prop-filter"): (0, None), (carddav_namespace, "param-filter"): (0, None) } @registerElement class DirectoryGateway(CardDAVElement): """ CardDAV property on a principal to indicate where the directory gateway resource is. """ name = "directory-gateway" hidden = True protected = True allowed_children = {(dav_namespace, "href"): (0, None)} @registerElement class Directory(CardDAVEmptyElement): """ CardDAV property on a principal to indicate where the directory resource is. """ name = "directory" @registerElement class DefaultAddressBookURL (CardDAVElement): """ A single href indicating which addressbook is the default. """ name = "default-addressbook-URL" allowed_children = {(dav_namespace, "href"): (0, 1)} ## # Extensions to ResourceType ## def _isAddressBook(self): return bool(self.childrenOfType(AddressBook)) ResourceType.isAddressBook = _isAddressBook ResourceType.addressbook = ResourceType(Collection(), AddressBook()) ResourceType.directory = ResourceType(Collection(), AddressBook(), Directory())