OwlCyberSecurity - MANAGER
Edit File: vcard.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. ## """ vCard Utilities """ __all__ = [ "InvalidVCardDataError", "Property", "Component", ] import cStringIO as StringIO import codecs from twext.python.log import Logger from txweb2.stream import IStream from txweb2.dav.util import allDataFromStream from twistedcaldav.config import config from pycalendar.parameter import Parameter from pycalendar.componentbase import ComponentBase from pycalendar.exceptions import ErrorBase from pycalendar.vcard.card import Card from pycalendar.vcard.property import Property as pyProperty log = Logger() vCardProductID = "-//CALENDARSERVER.ORG//NONSGML Version 1//EN" class InvalidVCardDataError(ValueError): pass class Property (object): """ vCard Property """ def __init__(self, name, value, params={}, group=None, **kwargs): """ @param name: the property's name @param value: the property's value @param params: a dictionary of parameters, where keys are parameter names and values are (possibly empty) lists of parameter values. """ if name is None: assert value is None assert params is None pyobj = kwargs["pycard"] if not isinstance(pyobj, pyProperty): raise TypeError("Not a pyProperty: %r" % (property,)) self._pycard = pyobj else: # Convert params dictionary to list of lists format used by pycalendar if isinstance(value, unicode): value = value.encode("utf-8") self._pycard = pyProperty(group=group, name=name, value=value) for attrname, attrvalue in params.items(): if isinstance(attrvalue, unicode): attrvalue = attrvalue.encode("utf-8") self._pycard.addParameter(Parameter(attrname, attrvalue)) def __str__(self): return str(self._pycard) def __repr__(self): return "<%s: %r: %r>" % (self.__class__.__name__, self.name(), self.value()) def __hash__(self): return hash(str(self)) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, Property): return False return self._pycard == other._pycard def __gt__(self, other): return not (self.__eq__(other) or self.__lt__(other)) def __lt__(self, other): my_name = self.name() other_name = other.name() if my_name < other_name: return True if my_name > other_name: return False return self.value() < other.value() def __ge__(self, other): return self.__eq__(other) or self.__gt__(other) def __le__(self, other): return self.__eq__(other) or self.__lt__(other) def duplicate(self): """ Duplicate this object and all its contents. @return: the duplicated vcard. """ return Property(None, None, params=None, pycard=self._pycard.duplicate()) def name(self): return self._pycard.getName() def value(self): return self._pycard.getValue().getValue() def strvalue(self): return str(self._pycard.getValue()) def setValue(self, value): self._pycard.setValue(value) def parameterNames(self): """ Returns a set containing parameter names for this property. """ result = set() for pyattrlist in self._pycard.getParameters().values(): for pyattr in pyattrlist: result.add(pyattr.getName()) return result def parameterValue(self, name, default=None): """ Returns a single value for the given parameter. Raises InvalidICalendarDataError if the parameter has more than one value. """ try: return self._pycard.getParameterValue(name) except KeyError: return default def parameterValues(self, name): """ Returns a single value for the given parameter. Raises InvalidICalendarDataError if the parameter has more than one value. """ results = [] try: attrs = self._pycard.getParameters()[name.upper()] except KeyError: return [] for attr in attrs: results.extend(attr.getValues()) return results def hasParameter(self, paramname): return self._pycard.hasParameter(paramname) def setParameter(self, paramname, paramvalue): self._pycard.replaceParameter(Parameter(paramname, paramvalue)) def removeParameter(self, paramname): self._pycard.removeParameters(paramname) def removeAllParameters(self): self._pycard.setParameters({}) def removeParameterValue(self, paramname, paramvalue): paramname = paramname.upper() for attrName in self.parameterNames(): if attrName.upper() == paramname: for attr in tuple(self._pycard.getParameters()[attrName]): for value in attr.getValues(): if value == paramvalue: if not attr.removeValue(value): self._pycard.removeParameters(paramname) class Component (object): """ X{vCard} component. """ allowedTypesList = None @classmethod def allowedTypes(cls): if cls.allowedTypesList is None: cls.allowedTypesList = ["text/vcard"] if config.EnableJSONData: cls.allowedTypesList.append("application/vcard+json") return cls.allowedTypesList @classmethod def allFromString(clazz, string, format=None): """ FIXME: Just default to reading a single VCARD - actually need more """ if type(string) is unicode: string = string.encode("utf-8") else: # Valid utf-8 please string.decode("utf-8") # No BOMs please if string[:3] == codecs.BOM_UTF8: string = string[3:] return clazz.allFromStream(StringIO.StringIO(string), format) @classmethod def allFromStream(clazz, stream, format=None): """ FIXME: Just default to reading a single VCARD - actually need more """ try: results = Card.parseMultipleData(stream, format) except ErrorBase: results = None if not results: stream.seek(0) raise InvalidVCardDataError("%s" % (stream.read(),)) return [clazz(None, pycard=result) for result in results] @classmethod def fromString(clazz, string, format=None): """ Construct a L{Component} from a string. @param string: a string containing vCard data. @return: a L{Component} representing the first component described by C{string}. """ return clazz._fromData(string, False, format) @classmethod def fromStream(clazz, stream, format=None): """ Construct a L{Component} from a stream. @param stream: a C{read()}able stream containing vCard data. @return: a L{Component} representing the first component described by C{stream}. """ return clazz._fromData(stream, True, format) @classmethod def _fromData(clazz, data, isstream, format=None): """ Construct a L{Component} from a stream. @param stream: a C{read()}able stream containing vCard data. @param format: a C{str} indicating whether the data is vCard or jCard @return: a L{Component} representing the first component described by C{stream}. """ if isstream: pass else: if type(data) is unicode: data = data.encode("utf-8") else: # Valid utf-8 please data.decode("utf-8") # No BOMs please if data[:3] == codecs.BOM_UTF8: data = data[3:] errmsg = "Unknown" try: result = Card.parseData(data, format) except ErrorBase, e: errmsg = "%s: %s" % (e.mReason, e.mData,) result = None if not result: if isstream: data.seek(0) data = data.read() raise InvalidVCardDataError("%s\n%s" % (errmsg, data,)) return clazz(None, pycard=result) @classmethod def fromIStream(clazz, stream, format=None): """ Construct a L{Component} from a stream. @param stream: an L{IStream} containing vCard data. @return: a deferred returning a L{Component} representing the first component described by C{stream}. """ # # FIXME: # This reads the request body into a string and then parses it. # A better solution would parse directly and incrementally from the # request stream. # def parse(data): return clazz.fromString(data, format) return allDataFromStream(IStream(stream), parse) def __init__(self, name, **kwargs): """ Use this constructor to initialize an empty L{Component}. To create a new L{Component} from X{vCard} data, don't use this constructor directly. Use one of the factory methods instead. @param name: the name (L{str}) of the X{iCalendar} component type for the component. """ if name is None: if "pycard" in kwargs: pyobj = kwargs["pycard"] if pyobj is not None: if not isinstance(pyobj, ComponentBase): raise TypeError("Not a ComponentBase: %r" % (pyobj,)) self._pycard = pyobj else: raise AssertionError("name may not be None") # FIXME: _parent is not use internally, and appears to be used elsewhere, # even though it's names as a private variable. if "parent" in kwargs: parent = kwargs["parent"] if parent is not None: if not isinstance(parent, Component): raise TypeError("Not a Component: %r" % (parent,)) self._parent = parent else: self._parent = None elif name == "VCARD": self._pycard = Card(add_defaults=False) self._parent = None else: raise ValueError("VCards have no child components") def __str__(self): return str(self._pycard) def __repr__(self): return "<%s: %r>" % (self.__class__.__name__, str(self._pycard)) def __hash__(self): return hash(str(self)) def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): if not isinstance(other, Component): return False return self._pycard == other._pycard def getText(self, format=None): """ Return text representation """ assert self.name() == "VCARD", "Must be a VCARD: %r" % (self,) result = self._pycard.getText(format) if result is None: raise ValueError("Unknown format requested for address data.") return result # FIXME: Should this not be in __eq__? def same(self, other): return self._pycard == other._pycard def name(self): """ @return: the name of the vCard type of this component. """ return self._pycard.getType() def duplicate(self): """ Duplicate this object and all its contents. @return: the duplicated vcard. """ return Component(None, pycard=self._pycard.duplicate()) def hasProperty(self, name): """ @param name: the name of the property whose existence is being tested. @return: True if the named property exists, False otherwise. """ return self._pycard.hasProperty(name) def getProperty(self, name): """ Get one property from the property list. @param name: the name of the property to get. @return: the L{Property} found or None. @raise: L{ValueError} if there is more than one property of the given name. """ properties = tuple(self.properties(name)) if len(properties) == 1: return properties[0] if len(properties) > 1: raise InvalidVCardDataError("More than one %s property in component %r" % (name, self)) return None def properties(self, name=None): """ @param name: if given and not C{None}, restricts the returned properties to those with the given C{name}. @return: an iterable of L{Property} objects, one for each property of this component. """ properties = [] if name is None: [properties.extend(i) for i in self._pycard.getProperties().values()] elif self._pycard.countProperty(name) > 0: properties = self._pycard.getProperties(name) return ( Property(None, None, None, pycard=p) for p in properties ) def propertyValue(self, name): properties = tuple(self.properties(name)) if len(properties) == 1: return properties[0].value() if len(properties) > 1: raise InvalidVCardDataError("More than one %s property in component %r" % (name, self)) return None def addProperty(self, property): """ Adds a property to this component. @param property: the L{Property} to add to this component. """ self._pycard.addProperty(property._pycard) self._pycard.finalise() def removeProperty(self, property): """ Remove a property from this component. @param property: the L{Property} to remove from this component. """ self._pycard.removeProperty(property._pycard) self._pycard.finalise() def removeProperties(self, name): """ remove all properties with name @param name: the name of the properties to remove. """ self._pycard.removeProperties(name) def replaceProperty(self, property): """ Add or replace a property in this component. @param property: the L{Property} to add or replace in this component. """ # Remove all existing ones first self._pycard.removeProperties(property.name()) self.addProperty(property) def resourceUID(self): """ @return: the UID of the subcomponents in this component. """ assert self.name() == "VCARD", "Not a vcard: %r" % (self,) if not hasattr(self, "_resource_uid"): self._resource_uid = self.propertyValue("UID") return self._resource_uid def resourceKind(self): """ @return: the kind of the subcomponents in this component. """ assert self.name() == "VCARD", "Not a vcard: %r" % (self,) if not hasattr(self, "_resource_kind"): self._resource_kind = self.propertyValue("X-ADDRESSBOOKSERVER-KIND") return self._resource_kind def resourceMemberAddresses(self): """ @return: an iterable of X-ADDRESSBOOKSERVER-MEMBER property values """ assert self.name() == "VCARD", "Not a vcard: %r" % (self,) return [prop.value() for prop in list(self.properties("X-ADDRESSBOOKSERVER-MEMBER"))] def validVCardData(self, doFix=True, doRaise=True): """ @return: tuple of fixed, unfixed issues @raise InvalidVCardDataError: if the given vcard data is not valid. """ if self.name() != "VCARD": log.debug("Not a vcard: {s!r}", s=self) raise InvalidVCardDataError("Not a vcard") # Do underlying vCard library validation with data fix fixed, unfixed = self._pycard.validate(doFix=doFix) if unfixed: log.debug("vCard data had unfixable problems:\n {problems}", problems="\n ".join(unfixed)) if doRaise: raise InvalidVCardDataError("Address data had unfixable problems:\n %s" % ("\n ".join(unfixed),)) if fixed: log.debug("vCard data had fixable problems:\n {problems}", problems="\n ".join(fixed)) return fixed, unfixed def validForCardDAV(self): """ @raise ValueError: if the given vcard data is not valid. """ if self.name() != "VCARD": raise InvalidVCardDataError("Not a vcard") version = self.propertyValue("VERSION") if version != "3.0": raise InvalidVCardDataError("Not a version 2.0 vCard (version=%s)" % (version,)) uid = self.propertyValue("UID") if uid is None: raise InvalidVCardDataError("All vCards must have UIDs") # Control character check - only HTAB, CR, LF allowed for characters in the range 0x00-0x1F s = str(self) if len(s.translate(None, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) != len(s): raise InvalidVCardDataError("vCard contains illegal control character")