Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

744 řádky
25 KiB

  1. #!/usr/bin/env python
  2. #-------------------------------------------------------------------------------
  3. # Copyright (c) 2013 by Lukasz Janyst <ljanyst@buggybrain.net>
  4. #
  5. # Permission to use, copy, modify, and/or distribute this software for any
  6. # purpose with or without fee is hereby granted, provided that the above
  7. # copyright notice and this permission notice appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. #-------------------------------------------------------------------------------
  17. import sys
  18. import uuid
  19. import getopt
  20. import getpass
  21. #import carddav # Copied below due to import error
  22. import vobject
  23. import os
  24. import vcfpy
  25. import re
  26. import json
  27. from collections import OrderedDict
  28. DEBUG = True
  29. #-------------------------------------------------------------------------------
  30. # Fix FN
  31. #-------------------------------------------------------------------------------
  32. def fixFN( url, filename, user, passwd, auth, verify ):
  33. print ('[debug] Editing at', url, '...')
  34. print ('[debug] Listing the addressbook...')
  35. dav = PyCardDAV( url, user=user, passwd=passwd, auth=auth,
  36. write_support=True, verify=verify )
  37. abook = dav.get_abook()
  38. nCards = len( abook.keys() )
  39. print ('[debug] Found', nCards, 'cards.')
  40. curr = 1
  41. for href, etag in abook.items():
  42. print ("\r[debug] Processing", curr, "of", nCards,)
  43. sys.stdout.flush()
  44. curr += 1
  45. card = dav.get_vcard( href )
  46. card = card.split( '\r\n' )
  47. cardFixed = []
  48. for l in card:
  49. if not l.startswith( 'FN:' ):
  50. cardFixed.append( l )
  51. cardFixed = '\r\n'.join( cardFixed )
  52. c = vobject.readOne( cardFixed )
  53. n = [c.n.value.prefix, c.n.value.given, c.n.value.additional,
  54. c.n.value.family, c.n.value.suffix]
  55. name = ''
  56. for part in n:
  57. if part:
  58. name += part + ' '
  59. name = name.strip()
  60. if not hasattr( c, 'fn' ):
  61. c.add('fn')
  62. c.fn.value = name
  63. try:
  64. dav.update_vcard( c.serialize().decode( 'utf-8' ), href, etag )
  65. except Exception as e:
  66. print ('')
  67. raise
  68. print ('')
  69. print ('[debug] All updated')
  70. #-------------------------------------------------------------------------------
  71. # Download
  72. #-------------------------------------------------------------------------------
  73. def download( url, filename, user, passwd, auth, verify):
  74. if DEBUG: print ('[debug] Downloading from', url, 'to', filename, '...')
  75. if DEBUG: print ('[debug] Downloading the addressbook...')
  76. try:
  77. dav = PyCardDAV( url, user=user, passwd=passwd, auth=auth,
  78. verify=verify )
  79. abook = dav.get_abook()
  80. nCards = len( abook.keys() )
  81. if DEBUG: print ('[debug] Found', nCards, 'cards.')
  82. f = open( filename, 'wb' )
  83. curr = 1
  84. for href, etag in abook.items():
  85. if DEBUG: print ("\r[debug] Fetching", curr, "of", nCards,)
  86. sys.stdout.flush()
  87. curr += 1
  88. card = dav.get_vcard( href )
  89. #f.write( str(card.decode("utf-8")) )
  90. if DEBUG: print ("[debug] Writing on "+filename)
  91. #print ("[debug] CARD: "+card)
  92. f.write( card )
  93. #f.write( '\n' )
  94. f.close()
  95. if DEBUG: print ('[debug] All saved to:', filename)
  96. except Exception as e:
  97. if DEBUG: print(e)
  98. #-------------------------------------------------------------------------------
  99. # Read
  100. #-------------------------------------------------------------------------------
  101. def read( url, filename, user, passwd, auth, verify):
  102. """
  103. Example of READ use:
  104. python3.6 carddav-util.py --read --user=digiovine --passwd=Digiovine1! --file=test.vcf --url=https://mail.afasystems.it/drive/remote.php/dav/addressbooks/users/digiovine/rubrica-prova/
  105. """
  106. if DEBUG: print("[debug] ########## STARTING READ ##########")
  107. cards_response = {"contacts": []}
  108. if not filename:
  109. filename = _filename_from_url(url)
  110. download ( url, filename, user, passwd, auth, verify)
  111. if DEBUG: print("[debug] Download completed")
  112. f = open( filename, 'r' )
  113. cards = []
  114. for card in vobject.readComponents( f, validate=True ):
  115. cards.append( card )
  116. nCards = len(cards)
  117. if DEBUG: print ('[debug] Successfully read and validated', nCards, 'entries')
  118. for card in cards:
  119. if DEBUG: print("\n")
  120. if DEBUG: print("[debug] VCARD:")
  121. if DEBUG: print(card)
  122. if DEBUG: print("\n")
  123. cards_response['contacts'].append(_convertVcardToJson(card))
  124. f.close()
  125. if os.path.exists(filename):
  126. os.remove(filename)
  127. if DEBUG: print("File '"+filename+"' has been deleted")
  128. else:
  129. if DEBUG: print("The file does not exist")
  130. if DEBUG: print ('[debug] All done')
  131. #print (json.dumps(cards_response, indent=4, sort_keys=True))
  132. if DEBUG: print(cards_response)
  133. return cards_response
  134. def _filename_from_url(url):
  135. """
  136. This method takes in input a cardDav addressbook url,
  137. extract from the url the username and the addressbookName and then
  138. returns a string built with pattern: {username}_{addressbookName}
  139. """
  140. try:
  141. splitted_url = url.split("users/")[1]
  142. username = splitted_url.split("/")[0]
  143. addressbookName = splitted_url.split("/")[1]
  144. filename = username + "_" + addressbookName + ".vcf"
  145. except:
  146. filename = "addressbook_temp_generic_name.vcf"
  147. return filename
  148. def _convertVcardToJson(vcard):
  149. '''
  150. Takes in input a single vcard in vobject format and
  151. returns a json object of type:
  152. '''
  153. try:
  154. fn = _clean_vobject_attribute(vcard.fn, 'fn')
  155. except:
  156. fn = ""
  157. try:
  158. tel = {}
  159. for phoneNumber in vcard.contents['tel']:
  160. phoneNumber = str(phoneNumber)
  161. if "HOME" in phoneNumber and "VOICE" in phoneNumber:
  162. phoneKey = "homeVoice"
  163. elif "WORK" in phoneNumber and "VOICE" in phoneNumber:
  164. phoneKey = "workVoice"
  165. elif "HOME" in phoneNumber and "CELL" in phoneNumber:
  166. phoneKey = "personalMobile"
  167. elif "WORK" in phoneNumber and "CELL" in phoneNumber:
  168. phoneKey = "workMobile"
  169. else:
  170. phoneKey = "unknowkn"
  171. tel[phoneKey] = (_clean_vobject_attribute(phoneNumber, 'tel'))
  172. except:
  173. tel = []
  174. adr = _build_address_attribute(_clean_vobject_attribute(vcard.adr, 'adr'))
  175. try:
  176. email = _clean_vobject_attribute(vcard.email, 'email')
  177. except:
  178. email = ""
  179. try:
  180. categories = _clean_vobject_attribute(vcard.categories, 'categories')
  181. except:
  182. categories = []
  183. card_json = OrderedDict({
  184. "fn": fn,
  185. "tel": tel,
  186. "adr": adr,
  187. "email": email,
  188. "categories": categories,
  189. })
  190. if DEBUG: print("[DEBUG] Converted VCARD to JSON: ")
  191. if DEBUG: print(json.dumps(card_json, indent=4, sort_keys=True))
  192. return card_json
  193. def _clean_vobject_attribute(attribute, attr_type):
  194. attribute = str(attribute)
  195. # Common cleaning
  196. attribute = attribute.replace("{", "")
  197. attribute = attribute.replace("}", "")
  198. attribute = attribute.replace("<", "")
  199. attribute = attribute.replace(">", "")
  200. attribute = attribute.replace("TYPE", "")
  201. attribute = attribute.replace("VOICE", "")
  202. attribute = attribute.replace("[", "")
  203. attribute = attribute.replace("]", "")
  204. attribute = attribute.replace(":", "")
  205. attribute = attribute.replace("'", "")
  206. if attr_type == 'fn':
  207. # FN cleaning
  208. attribute = attribute.replace("FN", "")
  209. elif attr_type == 'tel':
  210. # TEL cleaning
  211. attribute = attribute.replace("TEL", "")
  212. attribute = attribute.replace("CELL", "")
  213. attribute = attribute.replace("WORK", "")
  214. attribute = attribute.replace("HOME", "")
  215. attribute = attribute.replace(",", "")
  216. attribute = attribute.replace("\"", "")
  217. attribute = attribute.replace(" ", "")
  218. elif attr_type == 'adr':
  219. # ADR cleaning
  220. attribute = attribute.replace("HOME", "")
  221. attribute = attribute.replace("ADR", "")
  222. attribute = attribute[1:]
  223. elif attr_type == 'email':
  224. # EMAIL cleaning
  225. attribute = attribute.replace("HOME", "")
  226. attribute = attribute.replace("EMAIL", "")
  227. attribute = attribute[1:]
  228. elif attr_type == 'categories':
  229. # CATEGORIES cleaning
  230. attribute = attribute.replace("CATEGORIES", "")
  231. attribute = attribute.split(", ")
  232. return attribute
  233. def _build_address_attribute(addr_string):
  234. casella_postale = ""
  235. indirizzo = ""
  236. indirizzo_esteso = ""
  237. citta_regione_cap = ""
  238. stato = ""
  239. addr_array = addr_string.split("\n")
  240. if len(addr_array) > 4:
  241. if(len(addr_array)>0):
  242. casella_postale = addr_array[0]
  243. if(len(addr_array)>1):
  244. indirizzo_esteso = addr_array[1]
  245. if indirizzo == ", ":
  246. indirizzo = ""
  247. if(len(addr_array)>2):
  248. indirizzo = addr_array[2]
  249. if indirizzo == ", ":
  250. indirizzo = ""
  251. if(len(addr_array)>3):
  252. citta_regione_cap = addr_array[3]
  253. if(len(addr_array)>4):
  254. stato = addr_array[4]
  255. addr_json = {
  256. "casellaPostale": casella_postale,
  257. "indirizzo": indirizzo,
  258. "indirizzoEsteso": indirizzo_esteso,
  259. "cittaRegioneCAP": citta_regione_cap,
  260. "stato": stato
  261. }
  262. return addr_json
  263. #-------------------------------------------------------------------------------
  264. # Upload
  265. #-------------------------------------------------------------------------------
  266. def upload( url, filename, user, passwd, auth, verify ):
  267. if not url.endswith( '/' ):
  268. url += '/'
  269. print ('[debug] Uploading from', filename, 'to', url, '...')
  270. print ('[debug] Processing cards in', filename, '...')
  271. f = open( filename, 'r' )
  272. cards = []
  273. for card in vobject.readComponents( f, validate=True ):
  274. cards.append( card )
  275. nCards = len(cards)
  276. print ('[debug] Successfuly read and validated', nCards, 'entries')
  277. print ('[debug] Connecting to', url, '...')
  278. dav = PyCardDAV( url, user=user, passwd=passwd, auth=auth,
  279. write_support=True, verify=verify )
  280. curr = 1
  281. for card in cards:
  282. print ("\r[debug] Uploading", curr, "of", nCards,)
  283. sys.stdout.flush()
  284. curr += 1
  285. if hasattr(card, 'prodid' ):
  286. del card.prodid
  287. if not hasattr( card, 'uid' ):
  288. card.add('uid')
  289. card.uid.value = str( uuid.uuid4() )
  290. try:
  291. dav.upload_new_card( card.serialize().decode('utf-8') )
  292. except Exception as e:
  293. print ('')
  294. raise
  295. print ('')
  296. f.close()
  297. print ('[debug] All done')
  298. def makeVcard(first_name, last_name, company, title, phone, address, email):
  299. address_formatted = ';'.join([p.strip() for p in address.split(',')])
  300. return [
  301. 'BEGIN:VCARD',
  302. 'VERSION:2.1',
  303. f'N:{last_name};{first_name}',
  304. f'FN:{first_name} {last_name}',
  305. f'ORG:{company}',
  306. f'TITLE:{title}',
  307. f'EMAIL;PREF;INTERNET:{email}',
  308. f'TEL;WORK;VOICE:{phone}',
  309. f'ADR;WORK;PREF:;;{address_formatted}',
  310. f'REV:1',
  311. 'END:VCARD'
  312. ]
  313. def writeVcard(filename, vcard):
  314. with open(filename, 'w') as f:
  315. f.writelines([l + '\n' for l in vcard])
  316. #-------------------------------------------------------------------------------
  317. # Print help
  318. #-------------------------------------------------------------------------------
  319. def printHelp():
  320. print( 'carddav-util.py [options]' )
  321. print( ' --url=http://your.addressbook.com CardDAV addressbook ' )
  322. print( ' --file=local.vcf local vCard file ' )
  323. print( ' --user=username username ' )
  324. print( ' --passwd=password password, if absent will ' )
  325. print( ' prompt for it in the console ' )
  326. print( ' --download copy server -> file ' )
  327. print( ' --upload copy file -> server ' )
  328. print( ' --fixfn regenerate the FN tag ' )
  329. print( ' --digest use digest authentication ' )
  330. print( ' --no-cert-verify skip certificate verification ' )
  331. print( ' --help this help message ' )
  332. #-------------------------------------------------------------------------------
  333. # Run the show
  334. #-------------------------------------------------------------------------------
  335. def main():
  336. try:
  337. params = ['url=', 'file=', 'download', 'upload', 'read', 'help',
  338. 'user=', 'passwd=', 'digest', 'no-cert-verify', 'fixfn']
  339. optlist, args = getopt.getopt( sys.argv[1:], '', params )
  340. except getopt.GetoptError as e:
  341. print ('[!]', e)
  342. return 1
  343. opts = dict(optlist)
  344. if '--help' in opts or not opts:
  345. printHelp()
  346. return 0
  347. if '--upload' in opts and '--download' in opts and '--fixfn' in opts:
  348. print ('[!] You can only choose one action at a time')
  349. return 2
  350. if '--url' not in opts:
  351. print ('[!] You must specify the url')
  352. return 3
  353. url = opts['--url']
  354. try:
  355. filename = opts['--file']
  356. except:
  357. filename = ""
  358. user = None
  359. passwd = None
  360. auth = 'basic'
  361. verify = True
  362. if '--digest' in opts:
  363. auth = 'digest'
  364. if '--no-cert-verify' in opts:
  365. verify = False
  366. if '--user' in opts:
  367. user = opts['--user']
  368. if '--passwd' in opts:
  369. passwd = opts['--passwd']
  370. else:
  371. passwd = getpass.getpass( user+'\'s password (won\'t be echoed): ')
  372. commandMap = {'--upload': upload, '--download': download, '--read': read, '--fixfn': fixFN}
  373. for command in commandMap:
  374. if command in opts:
  375. i = 0
  376. try:
  377. i = commandMap[command]( url, filename, user, passwd, auth, verify )
  378. except Exception as e:
  379. print ('[!]', e)
  380. if __name__ == '__main__':
  381. sys.exit(main())
  382. #!/usr/bin/env python
  383. # vim: set ts=4 sw=4 expandtab sts=4:
  384. # Copyright (c) 2011-2013 Christian Geier & contributors
  385. #
  386. # Permission is hereby granted, free of charge, to any person obtaining
  387. # a copy of this software and associated documentation files (the
  388. # "Software"), to deal in the Software without restriction, including
  389. # without limitation the rights to use, copy, modify, merge, publish,
  390. # distribute, sublicense, and/or sell copies of the Software, and to
  391. # permit persons to whom the Software is furnished to do so, subject to
  392. # the following conditions:
  393. #
  394. # The above copyright notice and this permission notice shall be
  395. # included in all copies or substantial portions of the Software.
  396. #
  397. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  398. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  399. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  400. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  401. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  402. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  403. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  404. #-------------------------------------------------------------------------------
  405. # Lukasz Janyst:
  406. #
  407. # lxml encoding issue:
  408. # * Remove '<?xml version="1.0" encoding="utf-8"?>' header from responses
  409. # to prevent etree errors
  410. #
  411. # requests-0.8.2:
  412. # * Remove the verify ssl flag - caused exception
  413. # * Add own raise_for_status for more meaningful error messages
  414. # * Fix digest auth
  415. #-------------------------------------------------------------------------------
  416. """
  417. contains the class PyCardDAV and some associated functions and definitions
  418. """
  419. from collections import namedtuple
  420. import requests
  421. import sys
  422. import urllib.parse as urlparse
  423. import logging
  424. import lxml.etree as ET
  425. import string
  426. def raise_for_status( resp ):
  427. if 400 <= resp.status_code < 500 or 500 <= resp.status_code < 600:
  428. msg = 'Error code: ' + str(resp.status_code) + '\n'
  429. msg += resp.content
  430. raise requests.exceptions.HTTPError( msg )
  431. def get_random_href():
  432. """returns a random href"""
  433. import random
  434. tmp_list = list()
  435. for _ in xrange(3):
  436. rand_number = random.randint(0, 0x100000000)
  437. tmp_list.append("{0:x}".format(rand_number))
  438. return "-".join(tmp_list).upper()
  439. DAVICAL = 'davical'
  440. SABREDAV = 'sabredav'
  441. UNKNOWN = 'unknown server'
  442. class UploadFailed(Exception):
  443. """uploading the card failed"""
  444. pass
  445. class PyCardDAV(object):
  446. """class for interacting with a CardDAV server
  447. Since PyCardDAV relies heavily on Requests [1] its SSL verification is also
  448. shared by PyCardDAV [2]. For now, only the *verify* keyword is exposed
  449. through PyCardDAV.
  450. [1] http://docs.python-requests.org/
  451. [2] http://docs.python-requests.org/en/latest/user/advanced/
  452. raises:
  453. requests.exceptions.SSLError
  454. requests.exceptions.ConnectionError
  455. more requests.exceptions depending on the actual error
  456. Exception (shame on me)
  457. """
  458. def __init__(self, resource, debug='', user='', passwd='',
  459. verify=True, write_support=False, auth='basic'):
  460. #shutup url3
  461. urllog = logging.getLogger('requests.packages.urllib3.connectionpool')
  462. urllog.setLevel(logging.CRITICAL)
  463. split_url = urlparse.urlparse(resource)
  464. url_tuple = namedtuple('url', 'resource base path')
  465. self.url = url_tuple(resource,
  466. split_url.scheme + '://' + split_url.netloc,
  467. split_url.path)
  468. self.debug = debug
  469. self.session = requests.session()
  470. self.write_support = write_support
  471. self._settings = {'verify': verify}
  472. if auth == 'basic':
  473. self._settings['auth'] = (user, passwd,)
  474. if auth == 'digest':
  475. from requests.auth import HTTPDigestAuth
  476. self._settings['auth'] = HTTPDigestAuth(user, passwd)
  477. self._default_headers = {"User-Agent": "pyCardDAV"}
  478. response = self.session.request('PROPFIND', resource,
  479. headers=self.headers,
  480. **self._settings)
  481. raise_for_status( response ) #raises error on not 2XX HTTP status code
  482. @property
  483. def verify(self):
  484. """gets verify from settings dict"""
  485. return self._settings['verify']
  486. @verify.setter
  487. def verify(self, verify):
  488. """set verify"""
  489. self._settings['verify'] = verify
  490. @property
  491. def headers(self):
  492. return dict(self._default_headers)
  493. def _check_write_support(self):
  494. """checks if user really wants his data destroyed"""
  495. if not self.write_support:
  496. sys.stderr.write("Sorry, no write support for you. Please check "
  497. "the documentation.\n")
  498. sys.exit(1)
  499. def _detect_server(self):
  500. """detects CardDAV server type
  501. currently supports davical and sabredav (same as owncloud)
  502. :rtype: string "davical" or "sabredav"
  503. """
  504. response = requests.request('OPTIONS',
  505. self.url.base,
  506. headers=self.header)
  507. if "X-Sabre-Version" in response.headers:
  508. server = SABREDAV
  509. elif "X-DAViCal-Version" in response.headers:
  510. server = DAVICAL
  511. else:
  512. server = UNKNOWN
  513. logging.info(server + " detected")
  514. return server
  515. def get_abook(self):
  516. """does the propfind and processes what it returns
  517. :rtype: list of hrefs to vcards
  518. """
  519. xml = self._get_xml_props()
  520. abook = self._process_xml_props(xml)
  521. return abook
  522. def get_vcard(self, href):
  523. """
  524. pulls vcard from server
  525. :returns: vcard
  526. :rtype: string
  527. """
  528. response = self.session.get(self.url.base + href,
  529. headers=self.headers,
  530. **self._settings)
  531. raise_for_status( response )
  532. return response.content
  533. def update_vcard(self, card, href, etag):
  534. """
  535. pushes changed vcard to the server
  536. card: vcard as unicode string
  537. etag: str or None, if this is set to a string, card is only updated if
  538. remote etag matches. If etag = None the update is forced anyway
  539. """
  540. # TODO what happens if etag does not match?
  541. self._check_write_support()
  542. remotepath = str(self.url.base + href)
  543. headers = self.headers
  544. headers['content-type'] = 'text/vcard'
  545. if etag is not None:
  546. headers['If-Match'] = etag
  547. self.session.put(remotepath, data=card.encode('utf-8'), headers=headers,
  548. **self._settings)
  549. def delete_vcard(self, href, etag):
  550. """deletes vcard from server
  551. deletes the resource at href if etag matches,
  552. if etag=None delete anyway
  553. :param href: href of card to be deleted
  554. :type href: str()
  555. :param etag: etag of that card, if None card is always deleted
  556. :type href: str()
  557. :returns: nothing
  558. """
  559. # TODO: what happens if etag does not match, url does not exist etc ?
  560. self._check_write_support()
  561. remotepath = str(self.url.base + href)
  562. headers = self.headers
  563. headers['content-type'] = 'text/vcard'
  564. if etag is not None:
  565. headers['If-Match'] = etag
  566. result = self.session.delete(remotepath,
  567. headers=headers,
  568. **self._settings)
  569. raise_for_status( response )
  570. def upload_new_card(self, card):
  571. """
  572. upload new card to the server
  573. :param card: vcard to be uploaded
  574. :type card: unicode
  575. :rtype: tuple of string (path of the vcard on the server) and etag of
  576. new card (string or None)
  577. """
  578. self._check_write_support()
  579. card = card.encode('utf-8')
  580. for _ in range(0, 5):
  581. rand_string = get_random_href()
  582. remotepath = str(self.url.resource + rand_string + ".vcf")
  583. headers = self.headers
  584. headers['content-type'] = 'text/vcard'
  585. headers['If-None-Match'] = '*'
  586. response = requests.put(remotepath, data=card, headers=headers,
  587. **self._settings)
  588. if response.ok:
  589. parsed_url = urlparse.urlparse(remotepath)
  590. if 'etag' not in response.headers:
  591. etag = ''
  592. else:
  593. etag = response.headers['etag']
  594. return (parsed_url.path, etag)
  595. raise_for_status( response )
  596. def _get_xml_props(self):
  597. """PROPFIND method
  598. gets the xml file with all vcard hrefs
  599. :rtype: str() (an xml file)
  600. """
  601. headers = self.headers
  602. headers['Depth'] = '1'
  603. response = self.session.request('PROPFIND',
  604. self.url.resource,
  605. headers=headers,
  606. **self._settings)
  607. raise_for_status( response )
  608. if response.headers['DAV'].count('addressbook') == 0:
  609. raise Exception("URL is not a CardDAV resource")
  610. return response.content
  611. @classmethod
  612. def _process_xml_props(cls, xml):
  613. """processes the xml from PROPFIND, listing all vcard hrefs
  614. :param xml: the xml file
  615. :type xml: str()
  616. :rtype: dict() key: href, value: etag
  617. """
  618. #xml.replace('<?xml version="1.0" encoding="utf-8"?>', '')
  619. namespace = "{DAV:}"
  620. element = ET.XML(xml)
  621. abook = dict()
  622. for response in element.iterchildren():
  623. if (response.tag == namespace + "response"):
  624. href = ""
  625. etag = ""
  626. insert = False
  627. for refprop in response.iterchildren():
  628. if (refprop.tag == namespace + "href"):
  629. href = refprop.text
  630. for prop in refprop.iterchildren():
  631. for props in prop.iterchildren():
  632. if (props.tag == namespace + "getcontenttype" and
  633. (props.text == "text/vcard" or
  634. props.text == "text/vcard; charset=utf-8" or
  635. props.text == "text/x-vcard" or
  636. props.text == "text/x-vcard; charset=utf-8")):
  637. insert = True
  638. if (props.tag == namespace + "getetag"):
  639. etag = props.text
  640. if insert:
  641. abook[href] = etag
  642. return abook