25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1225 lines
40 KiB

  1. """vobject module for reading vCard and vCalendar files."""
  2. from __future__ import print_function
  3. import copy
  4. import codecs
  5. import logging
  6. import re
  7. import six
  8. import sys
  9. # Package version
  10. VERSION = "0.9.9"
  11. # ------------------------------------ Python 2/3 compatibility challenges ----
  12. # Python 3 no longer has a basestring type, so....
  13. try:
  14. basestring = basestring
  15. except NameError:
  16. basestring = (str, bytes)
  17. # One more problem ... in python2 the str operator breaks on unicode
  18. # objects containing non-ascii characters
  19. try:
  20. unicode
  21. def str_(s):
  22. """
  23. Return byte string with correct encoding
  24. """
  25. if type(s) == unicode:
  26. return s.encode('utf-8')
  27. else:
  28. return str(s)
  29. except NameError:
  30. def str_(s):
  31. """
  32. Return string
  33. """
  34. return s
  35. if not isinstance(b'', type('')):
  36. unicode_type = str
  37. else:
  38. unicode_type = unicode # noqa
  39. def to_unicode(value):
  40. """Converts a string argument to a unicode string.
  41. If the argument is already a unicode string, it is returned
  42. unchanged. Otherwise it must be a byte string and is decoded as utf8.
  43. """
  44. if isinstance(value, unicode_type):
  45. return value
  46. return value.decode('utf-8')
  47. def to_basestring(s):
  48. """Converts a string argument to a byte string.
  49. If the argument is already a byte string, it is returned unchanged.
  50. Otherwise it must be a unicode string and is encoded as utf8.
  51. """
  52. if isinstance(s, bytes):
  53. return s
  54. return s.encode('utf-8')
  55. # ------------------------------------ Logging ---------------------------------
  56. logger = logging.getLogger(__name__)
  57. if not logging.getLogger().handlers:
  58. handler = logging.StreamHandler()
  59. formatter = logging.Formatter('%(name)s %(levelname)s %(message)s')
  60. handler.setFormatter(formatter)
  61. logger.addHandler(handler)
  62. logger.setLevel(logging.ERROR) # Log errors
  63. DEBUG = False # Don't waste time on debug calls
  64. # ----------------------------------- Constants --------------------------------
  65. CR = '\r'
  66. LF = '\n'
  67. CRLF = CR + LF
  68. SPACE = ' '
  69. TAB = '\t'
  70. SPACEORTAB = SPACE + TAB
  71. # --------------------------------- Main classes -------------------------------
  72. class VBase(object):
  73. """
  74. Base class for ContentLine and Component.
  75. @ivar behavior:
  76. The Behavior class associated with this object, which controls
  77. validation, transformations, and encoding.
  78. @ivar parentBehavior:
  79. The object's parent's behavior, or None if no behaviored parent exists.
  80. @ivar isNative:
  81. Boolean describing whether this component is a Native instance.
  82. @ivar group:
  83. An optional group prefix, should be used only to indicate sort order in
  84. vCards, according to spec.
  85. Current spec: 4.0 (http://tools.ietf.org/html/rfc6350)
  86. """
  87. def __init__(self, group=None, *args, **kwds):
  88. super(VBase, self).__init__(*args, **kwds)
  89. self.group = group
  90. self.behavior = None
  91. self.parentBehavior = None
  92. self.isNative = False
  93. def copy(self, copyit):
  94. self.group = copyit.group
  95. self.behavior = copyit.behavior
  96. self.parentBehavior = copyit.parentBehavior
  97. self.isNative = copyit.isNative
  98. def validate(self, *args, **kwds):
  99. """
  100. Call the behavior's validate method, or return True.
  101. """
  102. if self.behavior:
  103. return self.behavior.validate(self, *args, **kwds)
  104. return True
  105. def getChildren(self):
  106. """
  107. Return an iterable containing the contents of the object.
  108. """
  109. return []
  110. def clearBehavior(self, cascade=True):
  111. """
  112. Set behavior to None. Do for all descendants if cascading.
  113. """
  114. self.behavior = None
  115. if cascade:
  116. self.transformChildrenFromNative()
  117. def autoBehavior(self, cascade=False):
  118. """
  119. Set behavior if name is in self.parentBehavior.knownChildren.
  120. If cascade is True, unset behavior and parentBehavior for all
  121. descendants, then recalculate behavior and parentBehavior.
  122. """
  123. parentBehavior = self.parentBehavior
  124. if parentBehavior is not None:
  125. knownChildTup = parentBehavior.knownChildren.get(self.name)
  126. if knownChildTup is not None:
  127. behavior = getBehavior(self.name, knownChildTup[2])
  128. if behavior is not None:
  129. self.setBehavior(behavior, cascade)
  130. if isinstance(self, ContentLine) and self.encoded:
  131. self.behavior.decode(self)
  132. elif isinstance(self, ContentLine):
  133. self.behavior = parentBehavior.defaultBehavior
  134. if self.encoded and self.behavior:
  135. self.behavior.decode(self)
  136. def setBehavior(self, behavior, cascade=True):
  137. """
  138. Set behavior. If cascade is True, autoBehavior all descendants.
  139. """
  140. self.behavior = behavior
  141. if cascade:
  142. for obj in self.getChildren():
  143. obj.parentBehavior = behavior
  144. obj.autoBehavior(True)
  145. def transformToNative(self):
  146. """
  147. Transform this object into a custom VBase subclass.
  148. transformToNative should always return a representation of this object.
  149. It may do so by modifying self in place then returning self, or by
  150. creating a new object.
  151. """
  152. if self.isNative or not self.behavior or not self.behavior.hasNative:
  153. return self
  154. else:
  155. self_orig = copy.copy(self)
  156. try:
  157. return self.behavior.transformToNative(self)
  158. except Exception as e:
  159. # wrap errors in transformation in a ParseError
  160. lineNumber = getattr(self, 'lineNumber', None)
  161. if isinstance(e, ParseError):
  162. if lineNumber is not None:
  163. e.lineNumber = lineNumber
  164. raise
  165. else:
  166. msg = "In transformToNative, unhandled exception on line {0}: {1}: {2}"
  167. msg = msg.format(lineNumber, sys.exc_info()[0], sys.exc_info()[1])
  168. msg = msg + " (" + str(self_orig) + ")"
  169. raise ParseError(msg, lineNumber)
  170. def transformFromNative(self):
  171. """
  172. Return self transformed into a ContentLine or Component if needed.
  173. May have side effects. If it does, transformFromNative and
  174. transformToNative MUST have perfectly inverse side effects. Allowing
  175. such side effects is convenient for objects whose transformations only
  176. change a few attributes.
  177. Note that it isn't always possible for transformFromNative to be a
  178. perfect inverse of transformToNative, in such cases transformFromNative
  179. should return a new object, not self after modifications.
  180. """
  181. if self.isNative and self.behavior and self.behavior.hasNative:
  182. try:
  183. return self.behavior.transformFromNative(self)
  184. except Exception as e:
  185. # wrap errors in transformation in a NativeError
  186. lineNumber = getattr(self, 'lineNumber', None)
  187. if isinstance(e, NativeError):
  188. if lineNumber is not None:
  189. e.lineNumber = lineNumber
  190. raise
  191. else:
  192. msg = "In transformFromNative, unhandled exception on line {0} {1}: {2}"
  193. msg = msg.format(lineNumber, sys.exc_info()[0], sys.exc_info()[1])
  194. raise NativeError(msg, lineNumber)
  195. else:
  196. return self
  197. def transformChildrenToNative(self):
  198. """
  199. Recursively replace children with their native representation.
  200. """
  201. pass
  202. def transformChildrenFromNative(self, clearBehavior=True):
  203. """
  204. Recursively transform native children to vanilla representations.
  205. """
  206. pass
  207. def serialize(self, buf=None, lineLength=75, validate=True, behavior=None, *args, **kwargs):
  208. """
  209. Serialize to buf if it exists, otherwise return a string.
  210. Use self.behavior.serialize if behavior exists.
  211. """
  212. if not behavior:
  213. behavior = self.behavior
  214. if behavior:
  215. if DEBUG:
  216. logger.debug("serializing {0!s} with behavior {1!s}".format(self.name, behavior))
  217. return behavior.serialize(self, buf, lineLength, validate, *args, **kwargs)
  218. else:
  219. if DEBUG:
  220. logger.debug("serializing {0!s} without behavior".format(self.name))
  221. return defaultSerialize(self, buf, lineLength)
  222. def toVName(name, stripNum=0, upper=False):
  223. """
  224. Turn a Python name into an iCalendar style name,
  225. optionally uppercase and with characters stripped off.
  226. """
  227. if upper:
  228. name = name.upper()
  229. if stripNum != 0:
  230. name = name[:-stripNum]
  231. return name.replace('_', '-')
  232. class ContentLine(VBase):
  233. """
  234. Holds one content line for formats like vCard and vCalendar.
  235. For example::
  236. <SUMMARY{u'param1' : [u'val1'], u'param2' : [u'val2']}Bastille Day Party>
  237. @ivar name:
  238. The uppercased name of the contentline.
  239. @ivar params:
  240. A dictionary of parameters and associated lists of values (the list may
  241. be empty for empty parameters).
  242. @ivar value:
  243. The value of the contentline.
  244. @ivar singletonparams:
  245. A list of parameters for which it's unclear if the string represents the
  246. parameter name or the parameter value. In vCard 2.1, "The value string
  247. can be specified alone in those cases where the value is unambiguous".
  248. This is crazy, but we have to deal with it.
  249. @ivar encoded:
  250. A boolean describing whether the data in the content line is encoded.
  251. Generally, text read from a serialized vCard or vCalendar should be
  252. considered encoded. Data added programmatically should not be encoded.
  253. @ivar lineNumber:
  254. An optional line number associated with the contentline.
  255. """
  256. def __init__(self, name, params, value, group=None, encoded=False,
  257. isNative=False, lineNumber=None, *args, **kwds):
  258. """
  259. Take output from parseLine, convert params list to dictionary.
  260. Group is used as a positional argument to match parseLine's return
  261. """
  262. super(ContentLine, self).__init__(group, *args, **kwds)
  263. self.name = name.upper()
  264. self.encoded = encoded
  265. self.params = {}
  266. self.singletonparams = []
  267. self.isNative = isNative
  268. self.lineNumber = lineNumber
  269. self.value = value
  270. def updateTable(x):
  271. if len(x) == 1:
  272. self.singletonparams += x
  273. else:
  274. paramlist = self.params.setdefault(x[0].upper(), [])
  275. paramlist.extend(x[1:])
  276. list(map(updateTable, params))
  277. qp = False
  278. if 'ENCODING' in self.params and \
  279. 'QUOTED-PRINTABLE' in self.params['ENCODING']:
  280. qp = True
  281. self.params['ENCODING'].remove('QUOTED-PRINTABLE')
  282. if len(self.params['ENCODING']) == 0:
  283. del self.params['ENCODING']
  284. if 'QUOTED-PRINTABLE' in self.singletonparams:
  285. qp = True
  286. self.singletonparams.remove('QUOTED-PRINTABLE')
  287. if qp:
  288. if 'ENCODING' in self.params:
  289. self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode(self.params['ENCODING'])
  290. else:
  291. if 'CHARSET' in self.params:
  292. self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode(self.params['CHARSET'][0])
  293. else:
  294. self.value = codecs.decode(self.value.encode("utf-8"), "quoted-printable").decode('utf-8')
  295. @classmethod
  296. def duplicate(cls, copyit):
  297. newcopy = cls('', {}, '')
  298. newcopy.copy(copyit)
  299. return newcopy
  300. def copy(self, copyit):
  301. super(ContentLine, self).copy(copyit)
  302. self.name = copyit.name
  303. self.value = copy.copy(copyit.value)
  304. self.encoded = self.encoded
  305. self.params = copy.copy(copyit.params)
  306. for k, v in self.params.items():
  307. self.params[k] = copy.copy(v)
  308. self.singletonparams = copy.copy(copyit.singletonparams)
  309. self.lineNumber = copyit.lineNumber
  310. def __eq__(self, other):
  311. try:
  312. return (self.name == other.name) and (self.params == other.params) and (self.value == other.value)
  313. except Exception:
  314. return False
  315. def __getattr__(self, name):
  316. """
  317. Make params accessible via self.foo_param or self.foo_paramlist.
  318. Underscores, legal in python variable names, are converted to dashes,
  319. which are legal in IANA tokens.
  320. """
  321. try:
  322. if name.endswith('_param'):
  323. return self.params[toVName(name, 6, True)][0]
  324. elif name.endswith('_paramlist'):
  325. return self.params[toVName(name, 10, True)]
  326. else:
  327. raise AttributeError(name)
  328. except KeyError:
  329. raise AttributeError(name)
  330. def __setattr__(self, name, value):
  331. """
  332. Make params accessible via self.foo_param or self.foo_paramlist.
  333. Underscores, legal in python variable names, are converted to dashes,
  334. which are legal in IANA tokens.
  335. """
  336. if name.endswith('_param'):
  337. if type(value) == list:
  338. self.params[toVName(name, 6, True)] = value
  339. else:
  340. self.params[toVName(name, 6, True)] = [value]
  341. elif name.endswith('_paramlist'):
  342. if type(value) == list:
  343. self.params[toVName(name, 10, True)] = value
  344. else:
  345. raise VObjectError("Parameter list set to a non-list")
  346. else:
  347. prop = getattr(self.__class__, name, None)
  348. if isinstance(prop, property):
  349. prop.fset(self, value)
  350. else:
  351. object.__setattr__(self, name, value)
  352. def __delattr__(self, name):
  353. try:
  354. if name.endswith('_param'):
  355. del self.params[toVName(name, 6, True)]
  356. elif name.endswith('_paramlist'):
  357. del self.params[toVName(name, 10, True)]
  358. else:
  359. object.__delattr__(self, name)
  360. except KeyError:
  361. raise AttributeError(name)
  362. def valueRepr(self):
  363. """
  364. Transform the representation of the value
  365. according to the behavior, if any.
  366. """
  367. v = self.value
  368. if self.behavior:
  369. v = self.behavior.valueRepr(self)
  370. return v
  371. def __str__(self):
  372. try:
  373. return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr())
  374. except UnicodeEncodeError as e:
  375. return "<{0}{1}{2}>".format(self.name, self.params, self.valueRepr().encode('utf-8'))
  376. def __repr__(self):
  377. return self.__str__()
  378. def __unicode__(self):
  379. return u"<{0}{1}{2}>".format(self.name, self.params, self.valueRepr())
  380. def prettyPrint(self, level=0, tabwidth=3):
  381. pre = ' ' * level * tabwidth
  382. print(pre, self.name + ":", self.valueRepr())
  383. if self.params:
  384. print(pre, "params for ", self.name + ':')
  385. for k in self.params.keys():
  386. print(pre + ' ' * tabwidth, k, self.params[k])
  387. class Component(VBase):
  388. """
  389. A complex property that can contain multiple ContentLines.
  390. For our purposes, a component must start with a BEGIN:xxxx line and end with
  391. END:xxxx, or have a PROFILE:xxx line if a top-level component.
  392. @ivar contents:
  393. A dictionary of lists of Component or ContentLine instances. The keys
  394. are the lowercased names of child ContentLines or Components.
  395. Note that BEGIN and END ContentLines are not included in contents.
  396. @ivar name:
  397. Uppercase string used to represent this Component, i.e VCARD if the
  398. serialized object starts with BEGIN:VCARD.
  399. @ivar useBegin:
  400. A boolean flag determining whether BEGIN: and END: lines should
  401. be serialized.
  402. """
  403. def __init__(self, name=None, *args, **kwds):
  404. super(Component, self).__init__(*args, **kwds)
  405. self.contents = {}
  406. if name:
  407. self.name = name.upper()
  408. self.useBegin = True
  409. else:
  410. self.name = ''
  411. self.useBegin = False
  412. self.autoBehavior()
  413. @classmethod
  414. def duplicate(cls, copyit):
  415. newcopy = cls()
  416. newcopy.copy(copyit)
  417. return newcopy
  418. def copy(self, copyit):
  419. super(Component, self).copy(copyit)
  420. # deep copy of contents
  421. self.contents = {}
  422. for key, lvalue in copyit.contents.items():
  423. newvalue = []
  424. for value in lvalue:
  425. newitem = value.duplicate(value)
  426. newvalue.append(newitem)
  427. self.contents[key] = newvalue
  428. self.name = copyit.name
  429. self.useBegin = copyit.useBegin
  430. def setProfile(self, name):
  431. """
  432. Assign a PROFILE to this unnamed component.
  433. Used by vCard, not by vCalendar.
  434. """
  435. if self.name or self.useBegin:
  436. if self.name == name:
  437. return
  438. raise VObjectError("This component already has a PROFILE or "
  439. "uses BEGIN.")
  440. self.name = name.upper()
  441. def __getattr__(self, name):
  442. """
  443. For convenience, make self.contents directly accessible.
  444. Underscores, legal in python variable names, are converted to dashes,
  445. which are legal in IANA tokens.
  446. """
  447. # if the object is being re-created by pickle, self.contents may not
  448. # be set, don't get into an infinite loop over the issue
  449. if name == 'contents':
  450. return object.__getattribute__(self, name)
  451. try:
  452. if name.endswith('_list'):
  453. return self.contents[toVName(name, 5)]
  454. else:
  455. return self.contents[toVName(name)][0]
  456. except KeyError:
  457. raise AttributeError(name)
  458. normal_attributes = ['contents', 'name', 'behavior', 'parentBehavior', 'group']
  459. def __setattr__(self, name, value):
  460. """
  461. For convenience, make self.contents directly accessible.
  462. Underscores, legal in python variable names, are converted to dashes,
  463. which are legal in IANA tokens.
  464. """
  465. if name not in self.normal_attributes and name.lower() == name:
  466. if type(value) == list:
  467. if name.endswith('_list'):
  468. name = name[:-5]
  469. self.contents[toVName(name)] = value
  470. elif name.endswith('_list'):
  471. raise VObjectError("Component list set to a non-list")
  472. else:
  473. self.contents[toVName(name)] = [value]
  474. else:
  475. prop = getattr(self.__class__, name, None)
  476. if isinstance(prop, property):
  477. prop.fset(self, value)
  478. else:
  479. object.__setattr__(self, name, value)
  480. def __delattr__(self, name):
  481. try:
  482. if name not in self.normal_attributes and name.lower() == name:
  483. if name.endswith('_list'):
  484. del self.contents[toVName(name, 5)]
  485. else:
  486. del self.contents[toVName(name)]
  487. else:
  488. object.__delattr__(self, name)
  489. except KeyError:
  490. raise AttributeError(name)
  491. def getChildValue(self, childName, default=None, childNumber=0):
  492. """
  493. Return a child's value (the first, by default), or None.
  494. """
  495. child = self.contents.get(toVName(childName))
  496. if child is None:
  497. return default
  498. else:
  499. return child[childNumber].value
  500. def add(self, objOrName, group=None):
  501. """
  502. Add objOrName to contents, set behavior if it can be inferred.
  503. If objOrName is a string, create an empty component or line based on
  504. behavior. If no behavior is found for the object, add a ContentLine.
  505. group is an optional prefix to the name of the object (see RFC 2425).
  506. """
  507. if isinstance(objOrName, VBase):
  508. obj = objOrName
  509. if self.behavior:
  510. obj.parentBehavior = self.behavior
  511. obj.autoBehavior(True)
  512. else:
  513. name = objOrName.upper()
  514. try:
  515. id = self.behavior.knownChildren[name][2]
  516. behavior = getBehavior(name, id)
  517. if behavior.isComponent:
  518. obj = Component(name)
  519. else:
  520. obj = ContentLine(name, [], '', group)
  521. obj.parentBehavior = self.behavior
  522. obj.behavior = behavior
  523. obj = obj.transformToNative()
  524. except (KeyError, AttributeError):
  525. obj = ContentLine(objOrName, [], '', group)
  526. if obj.behavior is None and self.behavior is not None and \
  527. isinstance(obj, ContentLine):
  528. obj.behavior = self.behavior.defaultBehavior
  529. self.contents.setdefault(obj.name.lower(), []).append(obj)
  530. return obj
  531. def remove(self, obj):
  532. """
  533. Remove obj from contents.
  534. """
  535. named = self.contents.get(obj.name.lower())
  536. if named:
  537. try:
  538. named.remove(obj)
  539. if len(named) == 0:
  540. del self.contents[obj.name.lower()]
  541. except ValueError:
  542. pass
  543. def getChildren(self):
  544. """
  545. Return an iterable of all children.
  546. """
  547. for objList in self.contents.values():
  548. for obj in objList:
  549. yield obj
  550. def components(self):
  551. """
  552. Return an iterable of all Component children.
  553. """
  554. return (i for i in self.getChildren() if isinstance(i, Component))
  555. def lines(self):
  556. """
  557. Return an iterable of all ContentLine children.
  558. """
  559. return (i for i in self.getChildren() if isinstance(i, ContentLine))
  560. def sortChildKeys(self):
  561. try:
  562. first = [s for s in self.behavior.sortFirst if s in self.contents]
  563. except Exception:
  564. first = []
  565. return first + sorted(k for k in self.contents.keys() if k not in first)
  566. def getSortedChildren(self):
  567. return [obj for k in self.sortChildKeys() for obj in self.contents[k]]
  568. def setBehaviorFromVersionLine(self, versionLine):
  569. """
  570. Set behavior if one matches name, versionLine.value.
  571. """
  572. v = getBehavior(self.name, versionLine.value)
  573. if v:
  574. self.setBehavior(v)
  575. def transformChildrenToNative(self):
  576. """
  577. Recursively replace children with their native representation.
  578. Sort to get dependency order right, like vtimezone before vevent.
  579. """
  580. for childArray in (self.contents[k] for k in self.sortChildKeys()):
  581. for child in childArray:
  582. child = child.transformToNative()
  583. child.transformChildrenToNative()
  584. def transformChildrenFromNative(self, clearBehavior=True):
  585. """
  586. Recursively transform native children to vanilla representations.
  587. """
  588. for childArray in self.contents.values():
  589. for child in childArray:
  590. child = child.transformFromNative()
  591. child.transformChildrenFromNative(clearBehavior)
  592. if clearBehavior:
  593. child.behavior = None
  594. child.parentBehavior = None
  595. def __str__(self):
  596. if self.name:
  597. return "<{0}| {1}>".format(self.name, self.getSortedChildren())
  598. else:
  599. return u'<*unnamed*| {0}>'.format(self.getSortedChildren())
  600. def __repr__(self):
  601. return self.__str__()
  602. def prettyPrint(self, level=0, tabwidth=3):
  603. pre = ' ' * level * tabwidth
  604. print(pre, self.name)
  605. if isinstance(self, Component):
  606. for line in self.getChildren():
  607. line.prettyPrint(level + 1, tabwidth)
  608. class VObjectError(Exception):
  609. def __init__(self, msg, lineNumber=None):
  610. self.msg = msg
  611. if lineNumber is not None:
  612. self.lineNumber = lineNumber
  613. def __str__(self):
  614. if hasattr(self, 'lineNumber'):
  615. return "At line {0!s}: {1!s}".format(self.lineNumber, self.msg)
  616. else:
  617. return repr(self.msg)
  618. class ParseError(VObjectError):
  619. pass
  620. class ValidateError(VObjectError):
  621. pass
  622. class NativeError(VObjectError):
  623. pass
  624. # --------- Parsing functions and parseLine regular expressions ----------------
  625. patterns = {}
  626. # Note that underscore is not legal for names, it's included because
  627. # Lotus Notes uses it
  628. patterns['name'] = '[a-zA-Z0-9_-]+' # 1*(ALPHA / DIGIT / "-")
  629. patterns['safe_char'] = '[^";:,]'
  630. patterns['qsafe_char'] = '[^"]'
  631. # the combined Python string replacement and regex syntax is a little confusing;
  632. # remember that {foobar} is replaced with patterns['foobar'], so for instance
  633. # param_value is any number of safe_chars or any number of qsaf_chars surrounded
  634. # by double quotes.
  635. patterns['param_value'] = ' "{qsafe_char!s} * " | {safe_char!s} * '.format(**patterns)
  636. # get a tuple of two elements, one will be empty, the other will have the value
  637. patterns['param_value_grouped'] = """
  638. " ( {qsafe_char!s} * )" | ( {safe_char!s} + )
  639. """.format(**patterns)
  640. # get a parameter and its values, without any saved groups
  641. patterns['param'] = r"""
  642. ; (?: {name!s} ) # parameter name
  643. (?:
  644. (?: = (?: {param_value!s} ) )? # 0 or more parameter values, multiple
  645. (?: , (?: {param_value!s} ) )* # parameters are comma separated
  646. )*
  647. """.format(**patterns)
  648. # get a parameter, saving groups for name and value (value still needs parsing)
  649. patterns['params_grouped'] = r"""
  650. ; ( {name!s} )
  651. (?: =
  652. (
  653. (?: (?: {param_value!s} ) )? # 0 or more parameter values, multiple
  654. (?: , (?: {param_value!s} ) )* # parameters are comma separated
  655. )
  656. )?
  657. """.format(**patterns)
  658. # get a full content line, break it up into group, name, parameters, and value
  659. patterns['line'] = r"""
  660. ^ ((?P<group> {name!s})\.)?(?P<name> {name!s}) # name group
  661. (?P<params> ;?(?: {param!s} )* ) # params group (may be empty)
  662. : (?P<value> .* )$ # value group
  663. """.format(**patterns)
  664. ' "%(qsafe_char)s*" | %(safe_char)s* ' # what is this line?? - never assigned?
  665. param_values_re = re.compile(patterns['param_value_grouped'], re.VERBOSE)
  666. params_re = re.compile(patterns['params_grouped'], re.VERBOSE)
  667. line_re = re.compile(patterns['line'], re.DOTALL | re.VERBOSE)
  668. begin_re = re.compile('BEGIN', re.IGNORECASE)
  669. def parseParams(string):
  670. """
  671. Parse parameters
  672. """
  673. all = params_re.findall(string)
  674. allParameters = []
  675. for tup in all:
  676. paramList = [tup[0]] # tup looks like (name, valuesString)
  677. for pair in param_values_re.findall(tup[1]):
  678. # pair looks like ('', value) or (value, '')
  679. if pair[0] != '':
  680. paramList.append(pair[0])
  681. else:
  682. paramList.append(pair[1])
  683. allParameters.append(paramList)
  684. return allParameters
  685. def parseLine(line, lineNumber=None):
  686. """
  687. Parse line
  688. """
  689. match = line_re.match(line)
  690. if match is None:
  691. raise ParseError("Failed to parse line: {0!s}".format(line), lineNumber)
  692. # Underscores are replaced with dash to work around Lotus Notes
  693. return (match.group('name').replace('_', '-'),
  694. parseParams(match.group('params')),
  695. match.group('value'), match.group('group'))
  696. # logical line regular expressions
  697. patterns['lineend'] = r'(?:\r\n|\r|\n|$)'
  698. patterns['wrap'] = r'{lineend!s} [\t ]'.format(**patterns)
  699. patterns['logicallines'] = r"""
  700. (
  701. (?: [^\r\n] | {wrap!s} )*
  702. {lineend!s}
  703. )
  704. """.format(**patterns)
  705. patterns['wraporend'] = r'({wrap!s} | {lineend!s} )'.format(**patterns)
  706. wrap_re = re.compile(patterns['wraporend'], re.VERBOSE)
  707. logical_lines_re = re.compile(patterns['logicallines'], re.VERBOSE)
  708. testLines = """
  709. Line 0 text
  710. , Line 0 continued.
  711. Line 1;encoding=quoted-printable:this is an evil=
  712. evil=
  713. format.
  714. Line 2 is a new line, it does not start with whitespace.
  715. """
  716. def getLogicalLines(fp, allowQP=True):
  717. """
  718. Iterate through a stream, yielding one logical line at a time.
  719. Because many applications still use vCard 2.1, we have to deal with the
  720. quoted-printable encoding for long lines, as well as the vCard 3.0 and
  721. vCalendar line folding technique, a whitespace character at the start
  722. of the line.
  723. Quoted-printable data will be decoded in the Behavior decoding phase.
  724. # We're leaving this test in for awhile, because the unittest was ugly and dumb.
  725. >>> from six import StringIO
  726. >>> f=StringIO(testLines)
  727. >>> for n, l in enumerate(getLogicalLines(f)):
  728. ... print("Line %s: %s" % (n, l[0]))
  729. ...
  730. Line 0: Line 0 text, Line 0 continued.
  731. Line 1: Line 1;encoding=quoted-printable:this is an evil=
  732. evil=
  733. format.
  734. Line 2: Line 2 is a new line, it does not start with whitespace.
  735. """
  736. if not allowQP:
  737. val = fp.read(-1)
  738. lineNumber = 1
  739. for match in logical_lines_re.finditer(val):
  740. line, n = wrap_re.subn('', match.group())
  741. if line != '':
  742. yield line, lineNumber
  743. lineNumber += n
  744. else:
  745. quotedPrintable = False
  746. newbuffer = six.StringIO
  747. logicalLine = newbuffer()
  748. lineNumber = 0
  749. lineStartNumber = 0
  750. while True:
  751. line = fp.readline()
  752. if line == '':
  753. break
  754. else:
  755. line = line.rstrip(CRLF)
  756. lineNumber += 1
  757. if line.rstrip() == '':
  758. if logicalLine.tell() > 0:
  759. yield logicalLine.getvalue(), lineStartNumber
  760. lineStartNumber = lineNumber
  761. logicalLine = newbuffer()
  762. quotedPrintable = False
  763. continue
  764. if quotedPrintable and allowQP:
  765. logicalLine.write('\n')
  766. logicalLine.write(line)
  767. quotedPrintable = False
  768. elif line[0] in SPACEORTAB:
  769. logicalLine.write(line[1:])
  770. elif logicalLine.tell() > 0:
  771. yield logicalLine.getvalue(), lineStartNumber
  772. lineStartNumber = lineNumber
  773. logicalLine = newbuffer()
  774. logicalLine.write(line)
  775. else:
  776. logicalLine = newbuffer()
  777. logicalLine.write(line)
  778. # vCard 2.1 allows parameters to be encoded without a parameter name
  779. # False positives are unlikely, but possible.
  780. val = logicalLine.getvalue()
  781. if val[-1] == '=' and val.lower().find('quoted-printable') >= 0:
  782. quotedPrintable = True
  783. if logicalLine.tell() > 0:
  784. yield logicalLine.getvalue(), lineStartNumber
  785. def textLineToContentLine(text, n=None):
  786. return ContentLine(*parseLine(text, n), **{'encoded': True,
  787. 'lineNumber': n})
  788. def dquoteEscape(param):
  789. """
  790. Return param, or "param" if ',' or ';' or ':' is in param.
  791. """
  792. if param.find('"') >= 0:
  793. raise VObjectError("Double quotes aren't allowed in parameter values.")
  794. for char in ',;:':
  795. if param.find(char) >= 0:
  796. return '"' + param + '"'
  797. return param
  798. def foldOneLine(outbuf, input, lineLength=75):
  799. """
  800. Folding line procedure that ensures multi-byte utf-8 sequences are not
  801. broken across lines
  802. TO-DO: This all seems odd. Is it still needed, especially in python3?
  803. """
  804. if len(input) < lineLength:
  805. # Optimize for unfolded line case
  806. try:
  807. outbuf.write(bytes(input, 'UTF-8'))
  808. except Exception:
  809. # fall back on py2 syntax
  810. outbuf.write(input)
  811. else:
  812. # Look for valid utf8 range and write that out
  813. start = 0
  814. written = 0
  815. counter = 0 # counts line size in bytes
  816. decoded = to_unicode(input)
  817. length = len(to_basestring(input))
  818. while written < length:
  819. s = decoded[start] # take one char
  820. size = len(to_basestring(s)) # calculate it's size in bytes
  821. if counter + size > lineLength:
  822. try:
  823. outbuf.write(bytes("\r\n ", 'UTF-8'))
  824. except Exception:
  825. # fall back on py2 syntax
  826. outbuf.write("\r\n ")
  827. counter = 1 # one for space
  828. if str is unicode_type:
  829. outbuf.write(to_unicode(s))
  830. else:
  831. # fall back on py2 syntax
  832. outbuf.write(s.encode('utf-8'))
  833. written += size
  834. counter += size
  835. start += 1
  836. try:
  837. outbuf.write(bytes("\r\n", 'UTF-8'))
  838. except Exception:
  839. # fall back on py2 syntax
  840. outbuf.write("\r\n")
  841. def defaultSerialize(obj, buf, lineLength):
  842. """
  843. Encode and fold obj and its children, write to buf or return a string.
  844. """
  845. outbuf = buf or six.StringIO()
  846. if isinstance(obj, Component):
  847. if obj.group is None:
  848. groupString = ''
  849. else:
  850. groupString = obj.group + '.'
  851. if obj.useBegin:
  852. foldOneLine(outbuf, "{0}BEGIN:{1}".format(groupString, obj.name),
  853. lineLength)
  854. for child in obj.getSortedChildren():
  855. # validate is recursive, we only need to validate once
  856. child.serialize(outbuf, lineLength, validate=False)
  857. if obj.useBegin:
  858. foldOneLine(outbuf, "{0}END:{1}".format(groupString, obj.name),
  859. lineLength)
  860. elif isinstance(obj, ContentLine):
  861. startedEncoded = obj.encoded
  862. if obj.behavior and not startedEncoded:
  863. obj.behavior.encode(obj)
  864. s = six.StringIO()
  865. if obj.group is not None:
  866. s.write(obj.group + '.')
  867. s.write(str_(obj.name.upper()))
  868. keys = sorted(obj.params.keys())
  869. for key in keys:
  870. paramstr = ','.join(dquoteEscape(p) for p in obj.params[key])
  871. try:
  872. s.write(";{0}={1}".format(key, paramstr))
  873. except (UnicodeDecodeError, UnicodeEncodeError):
  874. s.write(";{0}={1}".format(key, paramstr.encode('utf-8')))
  875. try:
  876. s.write(":{0}".format(obj.value))
  877. except (UnicodeDecodeError, UnicodeEncodeError):
  878. s.write(":{0}".format(obj.value.encode('utf-8')))
  879. if obj.behavior and not startedEncoded:
  880. obj.behavior.decode(obj)
  881. foldOneLine(outbuf, s.getvalue(), lineLength)
  882. return buf or outbuf.getvalue()
  883. class Stack:
  884. def __init__(self):
  885. self.stack = []
  886. def __len__(self):
  887. return len(self.stack)
  888. def top(self):
  889. if len(self) == 0:
  890. return None
  891. else:
  892. return self.stack[-1]
  893. def topName(self):
  894. if len(self) == 0:
  895. return None
  896. else:
  897. return self.stack[-1].name
  898. def modifyTop(self, item):
  899. top = self.top()
  900. if top:
  901. top.add(item)
  902. else:
  903. new = Component()
  904. self.push(new)
  905. new.add(item) # add sets behavior for item and children
  906. def push(self, obj):
  907. self.stack.append(obj)
  908. def pop(self):
  909. return self.stack.pop()
  910. def readComponents(streamOrString, validate=False, transform=True,
  911. ignoreUnreadable=False, allowQP=False):
  912. """
  913. Generate one Component at a time from a stream.
  914. """
  915. if isinstance(streamOrString, basestring):
  916. stream = six.StringIO(streamOrString)
  917. else:
  918. stream = streamOrString
  919. try:
  920. stack = Stack()
  921. versionLine = None
  922. n = 0
  923. for line, n in getLogicalLines(stream, allowQP):
  924. if ignoreUnreadable:
  925. try:
  926. vline = textLineToContentLine(line, n)
  927. except VObjectError as e:
  928. if e.lineNumber is not None:
  929. msg = "Skipped line {lineNumber}, message: {msg}"
  930. else:
  931. msg = "Skipped a line, message: {msg}"
  932. logger.error(msg.format(**{'lineNumber': e.lineNumber, 'msg': str(e)}))
  933. continue
  934. else:
  935. vline = textLineToContentLine(line, n)
  936. if vline.name == "VERSION":
  937. versionLine = vline
  938. stack.modifyTop(vline)
  939. elif vline.name == "BEGIN":
  940. stack.push(Component(vline.value, group=vline.group))
  941. elif vline.name == "PROFILE":
  942. if not stack.top():
  943. stack.push(Component())
  944. stack.top().setProfile(vline.value)
  945. elif vline.name == "END":
  946. if len(stack) == 0:
  947. err = "Attempted to end the {0} component but it was never opened"
  948. raise ParseError(err.format(vline.value), n)
  949. if vline.value.upper() == stack.topName(): # START matches END
  950. if len(stack) == 1:
  951. component = stack.pop()
  952. if versionLine is not None:
  953. component.setBehaviorFromVersionLine(versionLine)
  954. else:
  955. behavior = getBehavior(component.name)
  956. if behavior:
  957. component.setBehavior(behavior)
  958. if validate:
  959. component.validate(raiseException=True)
  960. if transform:
  961. component.transformChildrenToNative()
  962. yield component # EXIT POINT
  963. else:
  964. stack.modifyTop(stack.pop())
  965. else:
  966. err = "{0} component wasn't closed"
  967. raise ParseError(err.format(stack.topName()), n)
  968. else:
  969. stack.modifyTop(vline) # not a START or END line
  970. if stack.top():
  971. if stack.topName() is None:
  972. logger.warning("Top level component was never named")
  973. elif stack.top().useBegin:
  974. raise ParseError("Component {0!s} was never closed".format(
  975. (stack.topName())), n)
  976. yield stack.pop()
  977. except ParseError as e:
  978. e.input = streamOrString
  979. raise
  980. def readOne(stream, validate=False, transform=True, ignoreUnreadable=False,
  981. allowQP=False):
  982. """
  983. Return the first component from stream.
  984. """
  985. return next(readComponents(stream, validate, transform, ignoreUnreadable,
  986. allowQP))
  987. # --------------------------- version registry ---------------------------------
  988. __behaviorRegistry = {}
  989. def registerBehavior(behavior, name=None, default=False, id=None):
  990. """
  991. Register the given behavior.
  992. If default is True (or if this is the first version registered with this
  993. name), the version will be the default if no id is given.
  994. """
  995. if not name:
  996. name = behavior.name.upper()
  997. if id is None:
  998. id = behavior.versionString
  999. if name in __behaviorRegistry:
  1000. if default:
  1001. __behaviorRegistry[name].insert(0, (id, behavior))
  1002. else:
  1003. __behaviorRegistry[name].append((id, behavior))
  1004. else:
  1005. __behaviorRegistry[name] = [(id, behavior)]
  1006. def getBehavior(name, id=None):
  1007. """
  1008. Return a matching behavior if it exists, or None.
  1009. If id is None, return the default for name.
  1010. """
  1011. name = name.upper()
  1012. if name in __behaviorRegistry:
  1013. if id:
  1014. for n, behavior in __behaviorRegistry[name]:
  1015. if n == id:
  1016. return behavior
  1017. return __behaviorRegistry[name][0][1]
  1018. return None
  1019. def newFromBehavior(name, id=None):
  1020. """
  1021. Given a name, return a behaviored ContentLine or Component.
  1022. """
  1023. name = name.upper()
  1024. behavior = getBehavior(name, id)
  1025. if behavior is None:
  1026. raise VObjectError("No behavior found named {0!s}".format(name))
  1027. if behavior.isComponent:
  1028. obj = Component(name)
  1029. else:
  1030. obj = ContentLine(name, [], '')
  1031. obj.behavior = behavior
  1032. obj.isNative = False
  1033. return obj
  1034. # --------------------------- Helper function ----------------------------------
  1035. def backslashEscape(s):
  1036. s = s.replace("\\", "\\\\").replace(";", "\\;").replace(",", "\\,")
  1037. return s.replace("\r\n", "\\n").replace("\n", "\\n").replace("\r", "\\n")