Package caldavclientlibrary :: Package client :: Module clientsession
[hide private]
[frames] | no frames]

Source Code for Module caldavclientlibrary.client.clientsession

  1  ## 
  2  # Copyright (c) 2006-2016 Apple Inc. All rights reserved. 
  3  # 
  4  # Licensed under the Apache License, Version 2.0 (the "License"); 
  5  # you may not use this file except in compliance with the License. 
  6  # You may obtain a copy of the License at 
  7  # 
  8  # http://www.apache.org/licenses/LICENSE-2.0 
  9  # 
 10  # Unless required by applicable law or agreed to in writing, software 
 11  # distributed under the License is distributed on an "AS IS" BASIS, 
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 13  # See the License for the specific language governing permissions and 
 14  # limitations under the License. 
 15  ## 
 16   
 17  from caldavclientlibrary.client.httpshandler import SmartHTTPConnection 
 18  from caldavclientlibrary.protocol.caldav.definitions import headers 
 19  from caldavclientlibrary.protocol.caldav.makecalendar import MakeCalendar 
 20  from caldavclientlibrary.protocol.carddav.makeaddressbook import MakeAddressBook 
 21  from caldavclientlibrary.protocol.http.authentication.basic import Basic 
 22  from caldavclientlibrary.protocol.http.authentication.digest import Digest 
 23  try: 
 24      from caldavclientlibrary.protocol.http.authentication.gssapi import Kerberos 
 25  except ImportError: 
 26      Kerberos = None 
 27  from caldavclientlibrary.protocol.http.data.string import ResponseDataString, RequestDataString 
 28  from caldavclientlibrary.protocol.url import URL 
 29  from caldavclientlibrary.protocol.webdav.acl import ACL 
 30  from caldavclientlibrary.protocol.webdav.definitions import davxml 
 31  from caldavclientlibrary.protocol.webdav.definitions import statuscodes 
 32  from caldavclientlibrary.protocol.webdav.delete import Delete 
 33  from caldavclientlibrary.protocol.webdav.get import Get 
 34  from caldavclientlibrary.protocol.webdav.makecollection import MakeCollection 
 35  from caldavclientlibrary.protocol.webdav.move import Move 
 36  from caldavclientlibrary.protocol.webdav.post import Post 
 37  from caldavclientlibrary.protocol.webdav.principalmatch import PrincipalMatch 
 38  from caldavclientlibrary.protocol.webdav.propall import PropAll 
 39  from caldavclientlibrary.protocol.webdav.propfind import PropFind 
 40  from caldavclientlibrary.protocol.webdav.propfindparser import PropFindParser 
 41  from caldavclientlibrary.protocol.webdav.propnames import PropNames 
 42  from caldavclientlibrary.protocol.webdav.proppatch import PropPatch 
 43  from caldavclientlibrary.protocol.webdav.put import Put 
 44  from caldavclientlibrary.protocol.webdav.session import Session 
 45  from xml.etree.ElementTree import Element, tostring 
 46  import types 
 47   
48 -class CalDAVSession(Session):
49
50 - class logger(object):
51
52 - def write(self, data):
53 print data.replace("\r\n", "\n"),
54
55 - def __init__(self, server, port=None, ssl=False, user="", pswd="", principal=None, root=None, logging=False):
56 super(CalDAVSession, self).__init__(server, port, ssl, log=CalDAVSession.logger()) 57 58 self.loghttp = logging 59 60 self.user = user 61 self.pswd = pswd 62 63 # Initialize state 64 self.connect = None 65 66 # Paths 67 self.rootPath = URL(url=root) 68 self.principalPath = URL(url=principal) 69 70 self._initCalDAVState()
71
72 - def _initCalDAVState(self):
73 74 # We need to cache the server capabilities and properties 75 if not self.principalPath: 76 self._discoverPrincipal()
77
78 - def _discoverPrincipal(self):
79 80 hrefs = self.getHrefListProperty(self.rootPath, davxml.principal_collection_set) 81 if not hrefs: 82 return 83 84 # For each principal collection find one that matches self 85 for href in hrefs: 86 87 results = self.getSelfHrefs(href) 88 if results: 89 self.principalPath = results[0] 90 if self.log: 91 self.log.write("Found principal path: %s" % (self.principalPath.absoluteURL(),)) 92 return
93
94 - def setUserPswd(self, user, pswd):
95 96 self.user = user 97 self.pswd = pswd 98 self.authorization = None
99
100 - def testResource(self, rurl):
101 102 assert(isinstance(rurl, URL)) 103 104 request = PropFind(self, rurl.relativeURL(), headers.Depth0, (davxml.resourcetype,)) 105 106 # Process it 107 self.runSession(request) 108 109 return request.getStatusCode() == statuscodes.MultiStatus
110
111 - def getPropertyNames(self, rurl):
112 113 assert(isinstance(rurl, URL)) 114 115 results = () 116 117 # Create WebDAV propfind 118 request = PropNames(self, rurl.relativeURL(), headers.Depth0) 119 result = ResponseDataString() 120 request.setOutput(result) 121 122 # Process it 123 self.runSession(request) 124 125 # If its a 207 we want to parse the XML 126 if request.getStatusCode() == statuscodes.MultiStatus: 127 128 parser = PropFindParser() 129 parser.parseData(result.getData()) 130 131 # Look at each propfind result 132 for item in parser.getResults().itervalues(): 133 134 # Get child element name (decode URL) 135 name = URL(url=item.getResource(), decode=True) 136 137 # Must match rurl 138 if name.equalRelative(rurl): 139 140 results = tuple([name for name in item.getNodeProperties().iterkeys()]) 141 142 else: 143 self.handleHTTPError(request) 144 145 return results
146
147 - def getProperties(self, rurl, props, xmldata=False):
148 149 assert(isinstance(rurl, URL)) 150 151 results = {} 152 bad = None 153 154 # Create WebDAV propfind 155 if props: 156 request = PropFind(self, rurl.relativeURL(), headers.Depth0, props) 157 else: 158 request = PropAll(self, rurl.relativeURL(), headers.Depth0) 159 result = ResponseDataString() 160 request.setOutput(result) 161 162 # Process it 163 self.runSession(request) 164 165 # If its a 207 we want to parse the XML 166 if request.getStatusCode() == statuscodes.MultiStatus: 167 168 parser = PropFindParser() 169 parser.parseData(result.getData()) 170 171 # Look at each propfind result 172 for item in parser.getResults().itervalues(): 173 174 # Get child element name (decode URL) 175 name = URL(url=item.getResource(), decode=True) 176 177 # Must match rurl 178 if name.equalRelative(rurl): 179 for name, value in item.getTextProperties().iteritems(): 180 results[name] = value 181 for name, value in item.getHrefProperties().iteritems(): 182 if name not in results: 183 results[name] = value 184 for name, value in item.getNodeProperties().iteritems(): 185 if name not in results: 186 results[name] = tostring(value) if xmldata else value 187 bad = item.getBadProperties() 188 else: 189 self.handleHTTPError(request) 190 191 return results, bad
192
193 - def getPropertiesOnHierarchy(self, rurl, props):
194 195 assert(isinstance(rurl, URL)) 196 197 results = {} 198 199 # Create WebDAV propfind 200 request = PropFind(self, rurl.relativeURL(), headers.Depth1, props) 201 result = ResponseDataString() 202 request.setOutput(result) 203 204 # Process it 205 self.runSession(request) 206 207 # If its a 207 we want to parse the XML 208 if request.getStatusCode() == statuscodes.MultiStatus: 209 210 parser = PropFindParser() 211 parser.parseData(result.getData()) 212 213 # Look at each propfind result 214 for item in parser.getResults().itervalues(): 215 216 # Get child element name (decode URL) 217 name = URL(url=item.getResource(), decode=True) 218 propresults = {} 219 results[name.relativeURL()] = propresults 220 221 for prop in props: 222 223 if item.getTextProperties().has_key(str(prop)): 224 propresults[prop] = item.getTextProperties().get(str(prop)) 225 226 elif item.getNodeProperties().has_key(str(prop)): 227 propresults[prop] = item.getNodeProperties()[str(prop)] 228 else: 229 self.handleHTTPError(request) 230 231 return results
232
233 - def getHrefListProperty(self, rurl, propname):
234 235 assert(isinstance(rurl, URL)) 236 237 results = () 238 239 # Create WebDAV propfind 240 request = PropFind(self, rurl.relativeURL(), headers.Depth0, (propname,)) 241 result = ResponseDataString() 242 request.setOutput(result) 243 244 # Process it 245 self.runSession(request) 246 247 # If its a 207 we want to parse the XML 248 if request.getStatusCode() == statuscodes.MultiStatus: 249 250 parser = PropFindParser() 251 parser.parseData(result.getData()) 252 253 # Look at each propfind result and extract any Hrefs 254 for item in parser.getResults().itervalues(): 255 256 # Get child element name (decode URL) 257 name = URL(url=item.getResource(), decode=True) 258 259 # Must match rurl 260 if name.equalRelative(rurl): 261 262 if item.getNodeProperties().has_key(str(propname)): 263 264 propnode = item.getNodeProperties()[str(propname)] 265 results += tuple([URL(url=href.text, decode=True) for href in propnode.findall(str(davxml.href)) if href.text]) 266 else: 267 self.handleHTTPError(request) 268 269 return results
270 271 # Do principal-match report with self on the passed in url
272 - def getSelfProperties(self, rurl, props):
273 274 assert(isinstance(rurl, URL)) 275 276 results = {} 277 278 # Create WebDAV principal-match 279 request = PrincipalMatch(self, rurl.relativeURL(), props) 280 result = ResponseDataString() 281 request.setOutput(result) 282 283 # Process it 284 self.runSession(request) 285 286 # If its a 207 we want to parse the XML 287 if request.getStatusCode() == statuscodes.MultiStatus: 288 289 parser = PropFindParser() 290 parser.parseData(result.getData()) 291 292 # Look at each principal-match result and return first one that is appropriate 293 for item in parser.getResults().itervalues(): 294 295 for prop in props: 296 297 if item.getNodeProperties().has_key(str(prop)): 298 299 href = item.getNodeProperties()[str(prop)].find(str(davxml.href)) 300 301 if href is not None: 302 results[prop] = URL(url=href.text, decode=True) 303 304 # We'll take the first one, whatever that is 305 break 306 307 else: 308 self.handleHTTPError(request) 309 return None 310 311 return results
312 313 # Do principal-match report with self on the passed in url
314 - def getSelfHrefs(self, rurl):
315 316 assert(isinstance(rurl, URL)) 317 318 results = () 319 320 # Create WebDAV principal-match 321 request = PrincipalMatch(self, rurl.realtiveURL(), (davxml.principal_URL,)) 322 result = ResponseDataString() 323 request.setOutput(result) 324 325 # Process it 326 self.runSession(request) 327 328 # If its a 207 we want to parse the XML 329 if request.getStatusCode() == statuscodes.MultiStatus: 330 331 parser = PropFindParser() 332 parser.parseData(result.getData()) 333 334 # Look at each propfind result and extract any Hrefs 335 for item in parser.getResults().itervalues(): 336 337 # Get child element name (decode URL) 338 name = URL(url=item.getResource(), decode=True) 339 results += (name.path,) 340 341 else: 342 self.handleHTTPError(request) 343 return None 344 345 return results
346 347 # Do principal-match report with self on the passed in url
348 - def getSelfPrincipalResource(self, rurl):
349 350 assert(isinstance(rurl, URL)) 351 352 hrefs = self.getHrefListProperty(rurl, davxml.principal_collection_set) 353 if not hrefs: 354 return None 355 356 # For each principal collection find one that matches self 357 for href in hrefs: 358 359 results = self.getSelfHrefs(href) 360 if results: 361 return results[0] 362 363 return None
364
365 - def setProperties(self, rurl, props):
366 367 assert(isinstance(rurl, URL)) 368 369 results = () 370 371 # Convert property data into something sensible 372 converted = [] 373 for name, value in props: 374 node = None 375 if isinstance(value, types.StringType): 376 node = Element(name) 377 node.text = value 378 elif isinstance(value, URL): 379 node = Element(davxml.href) 380 node.text = value.absoluteURL() 381 elif isinstance(value, types.ListType) or isinstance(value, types.TupleType): 382 hrefs = [] 383 for item in value: 384 if isinstance(item, URL): 385 href = Element(davxml.href) 386 href.text = item.relativeURL() 387 hrefs.append(href) 388 else: 389 break 390 else: 391 node = Element(name) 392 map(node.append, hrefs) 393 394 if node is not None: 395 converted.append(node) 396 397 # Create WebDAV propfind 398 request = PropPatch(self, rurl.relativeURL(), converted) 399 result = ResponseDataString() 400 request.setOutput(result) 401 402 # Process it 403 self.runSession(request) 404 405 # If its a 207 we want to parse the XML 406 if request.getStatusCode() == statuscodes.MultiStatus: 407 408 parser = PropFindParser() 409 parser.parseData(result.getData()) 410 411 # Look at each propfind result 412 for item in parser.getResults().itervalues(): 413 414 # Get child element name (decode URL) 415 name = URL(url=item.getResource(), decode=True) 416 417 # Must match rurl 418 if name.equalRelative(rurl): 419 420 for prop in item.getNodeProperties(): 421 results += (prop,) 422 423 else: 424 self.handleHTTPError(request) 425 426 return results
427
428 - def setACL(self, rurl, aces):
429 430 assert(isinstance(rurl, URL)) 431 432 # Create WebDAV ACL 433 request = ACL(self, rurl.relativeURL(), aces) 434 435 # Process it 436 self.runSession(request) 437 438 if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): 439 self.handleHTTPError(request)
440
441 - def makeCollection(self, rurl):
442 443 assert(isinstance(rurl, URL)) 444 445 # Create WebDAV MKCOL 446 request = MakeCollection(self, rurl.relativeURL()) 447 448 # Process it 449 self.runSession(request) 450 451 if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): 452 self.handleHTTPError(request)
453
454 - def makeCalendar(self, rurl, displayname=None, description=None):
455 456 assert(isinstance(rurl, URL)) 457 458 # Create WebDAV MKCALENDAR 459 request = MakeCalendar(self, rurl.relativeURL(), displayname, description) 460 461 # Process it 462 self.runSession(request) 463 464 if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): 465 self.handleHTTPError(request)
466
467 - def makeAddressBook(self, rurl, displayname=None, description=None):
468 469 assert(isinstance(rurl, URL)) 470 471 # Create WebDAV extended MKCOL 472 request = MakeAddressBook(self, rurl.relativeURL(), displayname, description) 473 474 # Process it 475 self.runSession(request) 476 477 if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): 478 self.handleHTTPError(request)
479
480 - def deleteResource(self, rurl):
481 482 assert(isinstance(rurl, URL)) 483 484 # Create WebDAV DELETE 485 request = Delete(self, rurl.relativeURL()) 486 487 # Process it 488 self.runSession(request) 489 490 if request.getStatusCode() not in (statuscodes.OK, statuscodes.NoContent): 491 self.handleHTTPError(request)
492
493 - def moveResource(self, rurlFrom, rurlTo):
494 495 assert(isinstance(rurlFrom, URL)) 496 assert(isinstance(rurlTo, URL)) 497 498 # Create WebDAV MOVE 499 request = Move(self, rurlFrom.relativeURL(), rurlTo.absoluteURL()) 500 501 # Process it 502 self.runSession(request) 503 504 if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent): 505 self.handleHTTPError(request)
506
507 - def readData(self, rurl):
508 509 assert(isinstance(rurl, URL)) 510 511 # Create WebDAV GET 512 request = Get(self, rurl.relativeURL()) 513 dout = ResponseDataString() 514 request.setData(dout) 515 516 # Process it 517 self.runSession(request) 518 519 # Check response status 520 if request.getStatusCode() != statuscodes.OK: 521 self.handleHTTPError(request) 522 return None 523 524 # Look for ETag 525 if request.getNewETag() is not None: 526 527 etag = request.getNewETag() 528 529 # Handle server bug: ETag value MUST be quoted per HTTP/1.1 S3.11 530 if not etag.startswith('"'): 531 etag = "\"%s\"" % (etag,) 532 else: 533 etag = None 534 535 # Return data as a string and etag 536 return dout.getData(), etag
537
538 - def writeData(self, rurl, data, contentType):
539 540 assert(isinstance(rurl, URL)) 541 542 # Create WebDAV PUT 543 request = Put(self, rurl.relativeURL()) 544 dout = RequestDataString(data, contentType) 545 request.setData(dout, None) 546 547 # Process it 548 self.runSession(request) 549 550 # Check response status 551 if request.getStatusCode() not in (statuscodes.OK, statuscodes.Created, statuscodes.NoContent,): 552 self.handleHTTPError(request)
553
554 - def importData(self, rurl, data, contentType):
555 556 assert(isinstance(rurl, URL)) 557 558 # Create WebDAV POST 559 request = Post(self, rurl.relativeURL()) 560 dout = RequestDataString(data, contentType) 561 request.setData(dout, None) 562 563 # Process it 564 self.runSession(request) 565 566 # Check response status 567 if request.getStatusCode() not in (statuscodes.OK, statuscodes.MultiStatus, statuscodes.NoContent,): 568 self.handleHTTPError(request)
569
570 - def displayHTTPError(self, request):
571 print request.status_code
572
573 - def openSession(self):
574 # Create connection 575 self.connect = SmartHTTPConnection(self.server, self.port, self.ssl) 576 577 # Write to log file 578 if self.loghttp and self.log: 579 self.log.write("\n <-------- BEGIN HTTP CONNECTION -------->\n") 580 self.log.write("Server: %s\n" % (self.server,))
581
582 - def closeSession(self):
583 if self.connect: 584 self.connect.close() 585 self.connect = None 586 587 # Write to log file 588 if self.loghttp and self.log: 589 self.log.write("\n <-------- END HTTP CONNECTION -------->\n")
590
591 - def runSession(self, request):
592 593 ctr = 5 594 while ctr: 595 ctr -= 1 596 597 self.doSession(request) 598 599 if request and request.isRedirect(): 600 location = request.getResponseHeader(headers.Location) 601 if location: 602 u = URL(location) 603 if not u.scheme or u.scheme in ("http", "https",): 604 # Get new server and base RURL 605 different_server = (self.server != u.server) if u.server else False 606 607 # Use new host in this session 608 if different_server: 609 self.setServer(u.server) 610 611 # Reset the request with new info 612 request.setURL(u.relativeURL()) 613 request.clearResponse() 614 615 # Write to log file 616 if self.loghttp and self.log: 617 self.log.write("\n <-------- HTTP REDIRECT -------->\n") 618 self.log.write("Location: %s\n" % (location,)) 619 620 # Recyle through loop 621 continue 622 623 # Exit when redirect does not occur 624 break
625
626 - def doSession(self, request):
627 # Do initialisation if not already done 628 if not self.initialised: 629 630 if not self.initialise(self.server, self.rootPath.relativeURL()): 631 632 # Break connection with server 633 self.closeConnection() 634 return 635 636 # Do the request if present 637 if request: 638 639 # Handle delayed authorization 640 first_time = True 641 while True: 642 643 # Run the request actions - this will make any connection that is needed 644 self.sendRequest(request) 645 646 # Check for auth failure if none before 647 if request.getStatusCode() == statuscodes.Unauthorized: 648 649 # If we had authorization before, then chances are auth details are wrong - so delete and try again with new auth 650 if self.hasAuthorization(): 651 652 self.authorization = None 653 654 # Display error so user knows why the prompt occurs again - but not the first time 655 # as we might have a digest re-auth. 656 if not first_time: 657 self.displayHTTPError(request) 658 659 660 # Get authorization object (prompt the user) and redo the request 661 self.authorization, cancelled = self.getAuthorizor(first_time, request.getResponseHeaders(headers.WWWAuthenticate)) 662 663 # Check for auth cancellation 664 if cancelled: 665 self.authorization = None 666 667 else: 668 first_time = False 669 670 request.clearResponse() 671 672 # Repeat the request loop with new authorization 673 continue 674 675 # If we get here we are complete with auth loop 676 break 677 678 # Now close it - eventually we will do keep-alive support 679 680 # Break connection with server 681 self.closeConnection()
682
683 - def doRequest(self, request):
684 685 # Write request headers 686 self.connect.putrequest(request.method, request.url, skip_host=True, skip_accept_encoding=True) 687 hdrs = request.getRequestHeaders() 688 for header, value in hdrs: 689 self.connect.putheader(header, value) 690 self.connect.endheaders() 691 692 # Write to log file 693 if self.loghttp and self.log: 694 self.log.write("\n <-------- BEGIN HTTP REQUEST -------->\n") 695 self.log.write("%s\n" % (request.getRequestStartLine(),)) 696 for header, value in hdrs: 697 self.log.write("%s: %s\n" % (header, value)) 698 self.log.write("\n") 699 700 # Write the data 701 self.writeRequestData(request) 702 703 # Blank line in log between 704 if self.loghttp and self.log: 705 self.log.write("\n <-------- BEGIN HTTP RESPONSE -------->\n") 706 707 # Get response 708 response = self.connect.getresponse() 709 710 # Get response headers 711 request.setResponseStatus(response.version, response.status, response.reason) 712 request.setResponseHeaders(response.msg.headers) 713 if self.loghttp and self.log: 714 self.log.write("HTTP/%s %s %s\r\n" % ( 715 {11:"1.1",10:"1.0",9:"0.9"}.get(response.version, "?"), 716 response.status, 717 response.reason 718 )) 719 for hdr in response.msg.headers: 720 self.log.write(hdr) 721 self.log.write("\n") 722 723 # Now get the data 724 self.readResponseData(request, response) 725 726 # Trailer in log 727 if self.loghttp and self.log: 728 self.log.write("\n <-------- END HTTP RESPONSE -------->\n")
729
730 - def handleHTTPError(self, request):
731 print "Ignoring error: %d" % (request.getStatusCode(),)
732
733 - def getAuthorizor(self, first_time, wwwhdrs):
734 735 for item in wwwhdrs: 736 if item.lower().startswith("basic"): 737 return Basic(self.user, self.pswd), False 738 elif item.lower().startswith("digest"): 739 return Digest(self.user, self.pswd, wwwhdrs), False 740 elif item.lower().startswith("negotiate") and Kerberos is not None: 741 return Kerberos(self.user), False 742 else: 743 return None, True
744
745 - def writeRequestData(self, request):
746 747 # Write the data if any present 748 if request.hasRequestData(): 749 750 stream = request.getRequestData() 751 if stream: 752 # Tell data we are using it 753 stream.start() 754 755 # Buffered write from stream 756 more = True 757 while more: 758 data, more = stream.read() 759 if data: 760 self.connect.send(data) 761 762 if self.loghttp and self.log: 763 self.log.write(data) 764 765 # Tell data we are done using it 766 stream.stop()
767
768 - def readResponseData(self, request, response):
769 770 # Check for data and write it 771 data = response.read() 772 773 if request.hasResponseData(): 774 stream = request.getResponseData() 775 stream.start() 776 stream.write(data) 777 stream.stop() 778 else: 779 response.read() 780 781 if self.loghttp and self.log: 782 self.log.write(data)
783
784 - def setServerType(self, type):
785 self.type = type
786
787 - def setServerDescriptor(self, txt):
788 self.descriptor = txt
789
790 - def setServerCapability(self, txt):
791 self.capability = txt
792