You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

371 rivejä
11 KiB

  1. """Definitions and behavior for vCard 3.0"""
  2. import codecs
  3. from . import behavior
  4. from .base import ContentLine, registerBehavior, backslashEscape, str_
  5. from .icalendar import stringToTextValues
  6. # Python 3 no longer has a basestring type, so....
  7. try:
  8. basestring = basestring
  9. except NameError:
  10. basestring = (str, bytes)
  11. # ------------------------ vCard structs ---------------------------------------
  12. class Name(object):
  13. def __init__(self, family='', given='', additional='', prefix='',
  14. suffix=''):
  15. """
  16. Each name attribute can be a string or a list of strings.
  17. """
  18. self.family = family
  19. self.given = given
  20. self.additional = additional
  21. self.prefix = prefix
  22. self.suffix = suffix
  23. @staticmethod
  24. def toString(val):
  25. """
  26. Turn a string or array value into a string.
  27. """
  28. if type(val) in (list, tuple):
  29. return ' '.join(val)
  30. return val
  31. def __str__(self):
  32. eng_order = ('prefix', 'given', 'additional', 'family', 'suffix')
  33. out = ' '.join(self.toString(getattr(self, val)) for val in eng_order)
  34. return str_(out)
  35. def __repr__(self):
  36. return "<Name: {0!s}>".format(self.__str__())
  37. def __eq__(self, other):
  38. try:
  39. return (self.family == other.family and
  40. self.given == other.given and
  41. self.additional == other.additional and
  42. self.prefix == other.prefix and
  43. self.suffix == other.suffix)
  44. except:
  45. return False
  46. class Address(object):
  47. def __init__(self, street='', city='', region='', code='',
  48. country='', box='', extended=''):
  49. """
  50. Each name attribute can be a string or a list of strings.
  51. """
  52. self.box = box
  53. self.extended = extended
  54. self.street = street
  55. self.city = city
  56. self.region = region
  57. self.code = code
  58. self.country = country
  59. @staticmethod
  60. def toString(val, join_char='\n'):
  61. """
  62. Turn a string or array value into a string.
  63. """
  64. if type(val) in (list, tuple):
  65. return join_char.join(val)
  66. return val
  67. lines = ('box', 'extended', 'street')
  68. one_line = ('city', 'region', 'code')
  69. def __str__(self):
  70. lines = '\n'.join(self.toString(getattr(self, val))
  71. for val in self.lines if getattr(self, val))
  72. one_line = tuple(self.toString(getattr(self, val), ' ')
  73. for val in self.one_line)
  74. lines += "\n{0!s}, {1!s} {2!s}".format(*one_line)
  75. if self.country:
  76. lines += '\n' + self.toString(self.country)
  77. return lines
  78. def __repr__(self):
  79. return "<Address: {0!s}>".format(self)
  80. def __eq__(self, other):
  81. try:
  82. return (self.box == other.box and
  83. self.extended == other.extended and
  84. self.street == other.street and
  85. self.city == other.city and
  86. self.region == other.region and
  87. self.code == other.code and
  88. self.country == other.country)
  89. except:
  90. return False
  91. # ------------------------ Registered Behavior subclasses ----------------------
  92. class VCardTextBehavior(behavior.Behavior):
  93. """
  94. Provide backslash escape encoding/decoding for single valued properties.
  95. TextBehavior also deals with base64 encoding if the ENCODING parameter is
  96. explicitly set to BASE64.
  97. """
  98. allowGroup = True
  99. base64string = 'B'
  100. @classmethod
  101. def decode(cls, line):
  102. """
  103. Remove backslash escaping from line.valueDecode line, either to remove
  104. backslash espacing, or to decode base64 encoding. The content line should
  105. contain a ENCODING=b for base64 encoding, but Apple Addressbook seems to
  106. export a singleton parameter of 'BASE64', which does not match the 3.0
  107. vCard spec. If we encouter that, then we transform the parameter to
  108. ENCODING=b
  109. """
  110. if line.encoded:
  111. if 'BASE64' in line.singletonparams:
  112. line.singletonparams.remove('BASE64')
  113. line.encoding_param = cls.base64string
  114. encoding = getattr(line, 'encoding_param', None)
  115. if encoding:
  116. if isinstance(line.value, bytes):
  117. line.value = codecs.decode(line.value, "base64")
  118. else:
  119. line.value = codecs.decode(line.value.encode("utf-8"), "base64")
  120. else:
  121. line.value = stringToTextValues(line.value)[0]
  122. line.encoded = False
  123. @classmethod
  124. def encode(cls, line):
  125. """
  126. Backslash escape line.value.
  127. """
  128. if not line.encoded:
  129. encoding = getattr(line, 'encoding_param', None)
  130. if encoding and encoding.upper() == cls.base64string:
  131. if isinstance(line.value, bytes):
  132. line.value = codecs.encode(line.value, "base64").decode("utf-8").replace('\n', '')
  133. else:
  134. line.value = codecs.encode(line.value.encode(encoding), "base64").decode("utf-8")
  135. else:
  136. line.value = backslashEscape(line.value)
  137. line.encoded = True
  138. class VCardBehavior(behavior.Behavior):
  139. allowGroup = True
  140. defaultBehavior = VCardTextBehavior
  141. class VCard3_0(VCardBehavior):
  142. """
  143. vCard 3.0 behavior.
  144. """
  145. name = 'VCARD'
  146. description = 'vCard 3.0, defined in rfc2426'
  147. versionString = '3.0'
  148. isComponent = True
  149. sortFirst = ('version', 'prodid', 'uid')
  150. knownChildren = {
  151. 'N': (0, 1, None), # min, max, behaviorRegistry id
  152. 'FN': (1, None, None),
  153. 'VERSION': (1, 1, None), # required, auto-generated
  154. 'PRODID': (0, 1, None),
  155. 'LABEL': (0, None, None),
  156. 'UID': (0, None, None),
  157. 'ADR': (0, None, None),
  158. 'ORG': (0, None, None),
  159. 'PHOTO': (0, None, None),
  160. 'CATEGORIES': (0, None, None)
  161. }
  162. @classmethod
  163. def generateImplicitParameters(cls, obj):
  164. """
  165. Create PRODID, VERSION, and VTIMEZONEs if needed.
  166. VTIMEZONEs will need to exist whenever TZID parameters exist or when
  167. datetimes with tzinfo exist.
  168. """
  169. if not hasattr(obj, 'version'):
  170. obj.add(ContentLine('VERSION', [], cls.versionString))
  171. registerBehavior(VCard3_0, default=True)
  172. class FN(VCardTextBehavior):
  173. name = "FN"
  174. description = 'Formatted name'
  175. registerBehavior(FN)
  176. class Label(VCardTextBehavior):
  177. name = "Label"
  178. description = 'Formatted address'
  179. registerBehavior(Label)
  180. wacky_apple_photo_serialize = True
  181. REALLY_LARGE = 1E50
  182. class Photo(VCardTextBehavior):
  183. name = "Photo"
  184. description = 'Photograph'
  185. @classmethod
  186. def valueRepr(cls, line):
  187. return " (BINARY PHOTO DATA at 0x{0!s}) ".format(id(line.value))
  188. @classmethod
  189. def serialize(cls, obj, buf, lineLength, validate):
  190. """
  191. Apple's Address Book is *really* weird with images, it expects
  192. base64 data to have very specific whitespace. It seems Address Book
  193. can handle PHOTO if it's not wrapped, so don't wrap it.
  194. """
  195. if wacky_apple_photo_serialize:
  196. lineLength = REALLY_LARGE
  197. VCardTextBehavior.serialize(obj, buf, lineLength, validate)
  198. registerBehavior(Photo)
  199. def toListOrString(string):
  200. stringList = stringToTextValues(string)
  201. if len(stringList) == 1:
  202. return stringList[0]
  203. else:
  204. return stringList
  205. def splitFields(string):
  206. """
  207. Return a list of strings or lists from a Name or Address.
  208. """
  209. return [toListOrString(i) for i in
  210. stringToTextValues(string, listSeparator=';', charList=';')]
  211. def toList(stringOrList):
  212. if isinstance(stringOrList, basestring):
  213. return [stringOrList]
  214. return stringOrList
  215. def serializeFields(obj, order=None):
  216. """
  217. Turn an object's fields into a ';' and ',' seperated string.
  218. If order is None, obj should be a list, backslash escape each field and
  219. return a ';' separated string.
  220. """
  221. fields = []
  222. if order is None:
  223. fields = [backslashEscape(val) for val in obj]
  224. else:
  225. for field in order:
  226. escapedValueList = [backslashEscape(val) for val in
  227. toList(getattr(obj, field))]
  228. fields.append(','.join(escapedValueList))
  229. return ';'.join(fields)
  230. NAME_ORDER = ('family', 'given', 'additional', 'prefix', 'suffix')
  231. ADDRESS_ORDER = ('box', 'extended', 'street', 'city', 'region', 'code',
  232. 'country')
  233. class NameBehavior(VCardBehavior):
  234. """
  235. A structured name.
  236. """
  237. hasNative = True
  238. @staticmethod
  239. def transformToNative(obj):
  240. """
  241. Turn obj.value into a Name.
  242. """
  243. if obj.isNative:
  244. return obj
  245. obj.isNative = True
  246. obj.value = Name(**dict(zip(NAME_ORDER, splitFields(obj.value))))
  247. return obj
  248. @staticmethod
  249. def transformFromNative(obj):
  250. """
  251. Replace the Name in obj.value with a string.
  252. """
  253. obj.isNative = False
  254. obj.value = serializeFields(obj.value, NAME_ORDER)
  255. return obj
  256. registerBehavior(NameBehavior, 'N')
  257. class AddressBehavior(VCardBehavior):
  258. """
  259. A structured address.
  260. """
  261. hasNative = True
  262. @staticmethod
  263. def transformToNative(obj):
  264. """
  265. Turn obj.value into an Address.
  266. """
  267. if obj.isNative:
  268. return obj
  269. obj.isNative = True
  270. obj.value = Address(**dict(zip(ADDRESS_ORDER, splitFields(obj.value))))
  271. return obj
  272. @staticmethod
  273. def transformFromNative(obj):
  274. """
  275. Replace the Address in obj.value with a string.
  276. """
  277. obj.isNative = False
  278. obj.value = serializeFields(obj.value, ADDRESS_ORDER)
  279. return obj
  280. registerBehavior(AddressBehavior, 'ADR')
  281. class OrgBehavior(VCardBehavior):
  282. """
  283. A list of organization values and sub-organization values.
  284. """
  285. hasNative = True
  286. @staticmethod
  287. def transformToNative(obj):
  288. """
  289. Turn obj.value into a list.
  290. """
  291. if obj.isNative:
  292. return obj
  293. obj.isNative = True
  294. obj.value = splitFields(obj.value)
  295. return obj
  296. @staticmethod
  297. def transformFromNative(obj):
  298. """
  299. Replace the list in obj.value with a string.
  300. """
  301. if not obj.isNative:
  302. return obj
  303. obj.isNative = False
  304. obj.value = serializeFields(obj.value)
  305. return obj
  306. registerBehavior(OrgBehavior, 'ORG')