12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Copyright (c) 2006-2007 Open Source Applications Foundation 22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License"); 42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# you may not use this file except in compliance with the License. 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# You may obtain a copy of the License at 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# http://www.apache.org/licenses/LICENSE-2.0 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# 92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Unless required by applicable law or agreed to in writing, software 102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# distributed under the License is distributed on an "AS IS" BASIS, 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# See the License for the specific language governing permissions and 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# limitations under the License. 142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import urlparse, httplib, copy, base64, StringIO 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import urllib 172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)try: 192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) from xml.etree import ElementTree 202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)except: 212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) from elementtree import ElementTree 222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)__all__ = ['DAVClient'] 242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def object_to_etree(parent, obj, namespace=''): 262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """This function takes in a python object, traverses it, and adds it to an existing etree object""" 272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if type(obj) is int or type(obj) is float or type(obj) is str: 292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If object is a string, int, or float just add it 302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) obj = str(obj) 312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if obj.startswith('{') is False: 322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ElementTree.SubElement(parent, '{%s}%s' % (namespace, obj)) 332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ElementTree.SubElement(parent, obj) 352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) elif type(obj) is dict: 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If the object is a dictionary we'll need to parse it and send it back recusively 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for key, value in obj.items(): 392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if key.startswith('{') is False: 402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) key_etree = ElementTree.SubElement(parent, '{%s}%s' % (namespace, key)) 412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(key_etree, value, namespace=namespace) 422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) key_etree = ElementTree.SubElement(parent, key) 442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(key_etree, value, namespace=namespace) 452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) elif type(obj) is list: 472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If the object is a list parse it and send it back recursively 482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for item in obj: 492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(parent, item, namespace=namespace) 502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # If it's none of previous types then raise 532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) raise TypeError, '%s is an unsupported type' % type(obj) 542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class DAVClient(object): 572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def __init__(self, url='http://localhost:8080'): 592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Initialization""" 602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._url = urlparse.urlparse(url) 622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.headers = {'Host':self._url[1], 642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 'User-Agent': 'python.davclient.DAVClient/0.1'} 652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def _request(self, method, path='', body=None, headers=None): 682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Internal request method""" 692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.response = None 702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = copy.copy(self.headers) 732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) new_headers = copy.copy(self.headers) 752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) new_headers.update(headers) 762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = new_headers 772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self._url.scheme == 'http': 792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._connection = httplib.HTTPConnection(self._url[1]) 802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) elif self._url.scheme == 'https': 812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._connection = httplib.HTTPSConnection(self._url[1]) 822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) raise Exception, 'Unsupported scheme' 842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._connection.request(method, path, body, headers) 862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.response = self._connection.getresponse() 882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.response.body = self.response.read() 902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Try to parse and get an etree 922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) try: 932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._get_response_tree() 942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) except: 952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) pass 962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def _get_response_tree(self): 992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Parse the response body into an elementree object""" 1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.response.tree = ElementTree.fromstring(self.response.body) 1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return self.response.tree 1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def set_basic_auth(self, username, password): 1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Set basic authentication""" 1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) auth = 'Basic %s' % base64.encodestring('%s:%s' % (username, password)).strip() 1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._username = username 1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._password = password 1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.headers['Authorization'] = auth 1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ## HTTP DAV methods ## 1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def get(self, path, headers=None): 1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Simple get request""" 1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('GET', path, headers=headers) 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return self.response.body 1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def head(self, path, headers=None): 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Basic HEAD request""" 1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('HEAD', path, headers=headers) 1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def put(self, path, body=None, f=None, headers=None): 1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Put resource with body""" 1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if f is not None: 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) body = f.read() 1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('PUT', path, body=body, headers=headers) 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def post(self, path, body=None, headers=None): 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """POST resource with body""" 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('POST', path, body=body, headers=headers) 1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def mkcol(self, path, headers=None): 1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Make DAV collection""" 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('MKCOL', path=path, headers=headers) 1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) make_collection = mkcol 1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def delete(self, path, headers=None): 1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Delete DAV resource""" 1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('DELETE', path=path, headers=headers) 1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def copy(self, source, destination, body=None, depth='infinity', overwrite=True, headers=None): 1442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Copy DAV resource""" 1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Set all proper headers 1462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 1472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {'Destination':destination} 1482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Destination'] = self._url.geturl() + destination 1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if overwrite is False: 1512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Overwrite'] = 'F' 1522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Depth'] = depth 1532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('COPY', source, body=body, headers=headers) 1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def copy_collection(self, source, destination, depth='infinity', overwrite=True, headers=None): 1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Copy DAV collection""" 1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) body = '<?xml version="1.0" encoding="utf-8" ?><d:propertybehavior xmlns:d="DAV:"><d:keepalive>*</d:keepalive></d:propertybehavior>' 1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Add proper headers 1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Content-Type'] = 'text/xml; charset="utf-8"' 1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.copy(source, destination, body=unicode(body, 'utf-8'), depth=depth, overwrite=overwrite, headers=headers) 1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def move(self, source, destination, body=None, depth='infinity', overwrite=True, headers=None): 1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Move DAV resource""" 1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Set all proper headers 1722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 1732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {'Destination':destination} 1742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 1752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Destination'] = self._url.geturl() + destination 1762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if overwrite is False: 1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Overwrite'] = 'F' 1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Depth'] = depth 1792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('MOVE', source, body=body, headers=headers) 1812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def move_collection(self, source, destination, depth='infinity', overwrite=True, headers=None): 1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Move DAV collection and copy all properties""" 1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) body = '<?xml version="1.0" encoding="utf-8" ?><d:propertybehavior xmlns:d="DAV:"><d:keepalive>*</d:keepalive></d:propertybehavior>' 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Add proper headers 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Content-Type'] = 'text/xml; charset="utf-8"' 1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self.move(source, destination, unicode(body, 'utf-8'), depth=depth, overwrite=overwrite, headers=headers) 1932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def propfind(self, path, properties='allprop', namespace='DAV:', depth=None, headers=None): 1962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Property find. If properties arg is unspecified it defaults to 'allprop'""" 1972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Build propfind xml 1982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) root = ElementTree.Element('{DAV:}propfind') 1992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if type(properties) is str: 2002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) ElementTree.SubElement(root, '{DAV:}%s' % properties) 2012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 2022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) props = ElementTree.SubElement(root, '{DAV:}prop') 2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(props, properties, namespace=namespace) 2042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) tree = ElementTree.ElementTree(root) 2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Etree won't just return a normal string, so we have to do this 2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) body = StringIO.StringIO() 2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) tree.write(body) 2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) body = body.getvalue() 2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Add proper headers 2122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 2142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if depth is not None: 2152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Depth'] = depth 2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Content-Type'] = 'text/xml; charset="utf-8"' 2172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Body encoding must be utf-8, 207 is proper response 2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('PROPFIND', path, body=unicode('<?xml version="1.0" encoding="utf-8" ?>\n'+body, 'utf-8'), headers=headers) 2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if self.response is not None and hasattr(self.response, 'tree') is True: 2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_responses = {} 2232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for response in self.response.tree._children: 2242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_href = response.find('{DAV:}href') 2252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_stat = response.find('{DAV:}propstat') 2262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def parse_props(props): 2282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_dict = {} 2292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for prop in props: 2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if prop.tag.find('{DAV:}') is not -1: 2312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) name = prop.tag.split('}')[-1] 2322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 2332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) name = prop.tag 2342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if len(prop._children) is not 0: 2352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_dict[name] = parse_props(prop._children) 2362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else: 2372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_dict[name] = prop.text 2382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return property_dict 2392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if property_href is not None and property_stat is not None: 2412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_dict = parse_props(property_stat.find('{DAV:}prop')._children) 2422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) property_responses[property_href.text] = property_dict 2432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return property_responses 2442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def proppatch(self, path, set_props=None, remove_props=None, namespace='DAV:', headers=None): 2462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Patch properties on a DAV resource. If namespace is not specified the DAV namespace is used for all properties""" 2472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) root = ElementTree.Element('{DAV:}propertyupdate') 2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if set_props is not None: 2502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) prop_set = ElementTree.SubElement(root, '{DAV:}set') 2512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(prop_set, set_props, namespace=namespace) 2522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if remove_props is not None: 2532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) prop_remove = ElementTree.SubElement(root, '{DAV:}remove') 2542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(prop_remove, remove_props, namespace=namespace) 2552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) tree = ElementTree.ElementTree(root) 2572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Add proper headers 2592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 2602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 2612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Content-Type'] = 'text/xml; charset="utf-8"' 2622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('PROPPATCH', path, body=unicode('<?xml version="1.0" encoding="utf-8" ?>\n'+body, 'utf-8'), headers=headers) 2642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def set_lock(self, path, owner, locktype='exclusive', lockscope='write', depth=None, headers=None): 2672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Set a lock on a dav resource""" 2682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) root = ElementTree.Element('{DAV:}lockinfo') 2692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) object_to_etree(root, {'locktype':locktype, 'lockscope':lockscope, 'owner':{'href':owner}}, namespace='DAV:') 2702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) tree = ElementTree.ElementTree(root) 2712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) # Add proper headers 2732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 2752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if depth is not None: 2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Depth'] = depth 2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Content-Type'] = 'text/xml; charset="utf-8"' 2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Timeout'] = 'Infinite, Second-4100000000' 2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('LOCK', path, body=unicode('<?xml version="1.0" encoding="utf-8" ?>\n'+body, 'utf-8'), headers=headers) 2812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) locks = self.response.etree.finall('.//{DAV:}locktoken') 2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) lock_list = [] 2842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) for lock in locks: 2852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) lock_list.append(lock.getchildren()[0].text.strip().strip('\n')) 2862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return lock_list 2872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def refresh_lock(self, path, token, headers=None): 2902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Refresh lock with token""" 2912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 2932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 2942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['If'] = '(<%s>)' % token 2952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Timeout'] = 'Infinite, Second-4100000000' 2962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('LOCK', path, body=None, headers=headers) 2982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 2992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) def unlock(self, path, token, headers=None): 3012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) """Unlock DAV resource with token""" 3022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if headers is None: 3032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers = {} 3042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) headers['Lock-Tocken'] = '<%s>' % token 3052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) self._request('UNLOCK', path, body=None, headers=headers) 3072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 313