Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / telemetry / third_party / davclient / davclient.py
blob2d9107aa9fb2f6a40a50c983bbcdebb1576c3957
1 # Copyright (c) 2006-2007 Open Source Applications Foundation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 import urlparse, httplib, copy, base64, StringIO
16 import urllib
18 try:
19 from xml.etree import ElementTree
20 except:
21 from elementtree import ElementTree
23 __all__ = ['DAVClient']
25 def object_to_etree(parent, obj, namespace=''):
26 """This function takes in a python object, traverses it, and adds it to an existing etree object"""
28 if type(obj) is int or type(obj) is float or type(obj) is str:
29 # If object is a string, int, or float just add it
30 obj = str(obj)
31 if obj.startswith('{') is False:
32 ElementTree.SubElement(parent, '{%s}%s' % (namespace, obj))
33 else:
34 ElementTree.SubElement(parent, obj)
36 elif type(obj) is dict:
37 # If the object is a dictionary we'll need to parse it and send it back recusively
38 for key, value in obj.items():
39 if key.startswith('{') is False:
40 key_etree = ElementTree.SubElement(parent, '{%s}%s' % (namespace, key))
41 object_to_etree(key_etree, value, namespace=namespace)
42 else:
43 key_etree = ElementTree.SubElement(parent, key)
44 object_to_etree(key_etree, value, namespace=namespace)
46 elif type(obj) is list:
47 # If the object is a list parse it and send it back recursively
48 for item in obj:
49 object_to_etree(parent, item, namespace=namespace)
51 else:
52 # If it's none of previous types then raise
53 raise TypeError, '%s is an unsupported type' % type(obj)
56 class DAVClient(object):
58 def __init__(self, url='http://localhost:8080'):
59 """Initialization"""
61 self._url = urlparse.urlparse(url)
63 self.headers = {'Host':self._url[1],
64 'User-Agent': 'python.davclient.DAVClient/0.1'}
67 def _request(self, method, path='', body=None, headers=None):
68 """Internal request method"""
69 self.response = None
71 if headers is None:
72 headers = copy.copy(self.headers)
73 else:
74 new_headers = copy.copy(self.headers)
75 new_headers.update(headers)
76 headers = new_headers
78 if self._url.scheme == 'http':
79 self._connection = httplib.HTTPConnection(self._url[1])
80 elif self._url.scheme == 'https':
81 self._connection = httplib.HTTPSConnection(self._url[1])
82 else:
83 raise Exception, 'Unsupported scheme'
85 self._connection.request(method, path, body, headers)
87 self.response = self._connection.getresponse()
89 self.response.body = self.response.read()
91 # Try to parse and get an etree
92 try:
93 self._get_response_tree()
94 except:
95 pass
98 def _get_response_tree(self):
99 """Parse the response body into an elementree object"""
100 self.response.tree = ElementTree.fromstring(self.response.body)
101 return self.response.tree
103 def set_basic_auth(self, username, password):
104 """Set basic authentication"""
105 auth = 'Basic %s' % base64.encodestring('%s:%s' % (username, password)).strip()
106 self._username = username
107 self._password = password
108 self.headers['Authorization'] = auth
110 ## HTTP DAV methods ##
112 def get(self, path, headers=None):
113 """Simple get request"""
114 self._request('GET', path, headers=headers)
115 return self.response.body
117 def head(self, path, headers=None):
118 """Basic HEAD request"""
119 self._request('HEAD', path, headers=headers)
121 def put(self, path, body=None, f=None, headers=None):
122 """Put resource with body"""
123 if f is not None:
124 body = f.read()
126 self._request('PUT', path, body=body, headers=headers)
128 def post(self, path, body=None, headers=None):
129 """POST resource with body"""
131 self._request('POST', path, body=body, headers=headers)
133 def mkcol(self, path, headers=None):
134 """Make DAV collection"""
135 self._request('MKCOL', path=path, headers=headers)
137 make_collection = mkcol
139 def delete(self, path, headers=None):
140 """Delete DAV resource"""
141 self._request('DELETE', path=path, headers=headers)
143 def copy(self, source, destination, body=None, depth='infinity', overwrite=True, headers=None):
144 """Copy DAV resource"""
145 # Set all proper headers
146 if headers is None:
147 headers = {'Destination':destination}
148 else:
149 headers['Destination'] = self._url.geturl() + destination
150 if overwrite is False:
151 headers['Overwrite'] = 'F'
152 headers['Depth'] = depth
154 self._request('COPY', source, body=body, headers=headers)
157 def copy_collection(self, source, destination, depth='infinity', overwrite=True, headers=None):
158 """Copy DAV collection"""
159 body = '<?xml version="1.0" encoding="utf-8" ?><d:propertybehavior xmlns:d="DAV:"><d:keepalive>*</d:keepalive></d:propertybehavior>'
161 # Add proper headers
162 if headers is None:
163 headers = {}
164 headers['Content-Type'] = 'text/xml; charset="utf-8"'
166 self.copy(source, destination, body=unicode(body, 'utf-8'), depth=depth, overwrite=overwrite, headers=headers)
169 def move(self, source, destination, body=None, depth='infinity', overwrite=True, headers=None):
170 """Move DAV resource"""
171 # Set all proper headers
172 if headers is None:
173 headers = {'Destination':destination}
174 else:
175 headers['Destination'] = self._url.geturl() + destination
176 if overwrite is False:
177 headers['Overwrite'] = 'F'
178 headers['Depth'] = depth
180 self._request('MOVE', source, body=body, headers=headers)
183 def move_collection(self, source, destination, depth='infinity', overwrite=True, headers=None):
184 """Move DAV collection and copy all properties"""
185 body = '<?xml version="1.0" encoding="utf-8" ?><d:propertybehavior xmlns:d="DAV:"><d:keepalive>*</d:keepalive></d:propertybehavior>'
187 # Add proper headers
188 if headers is None:
189 headers = {}
190 headers['Content-Type'] = 'text/xml; charset="utf-8"'
192 self.move(source, destination, unicode(body, 'utf-8'), depth=depth, overwrite=overwrite, headers=headers)
195 def propfind(self, path, properties='allprop', namespace='DAV:', depth=None, headers=None):
196 """Property find. If properties arg is unspecified it defaults to 'allprop'"""
197 # Build propfind xml
198 root = ElementTree.Element('{DAV:}propfind')
199 if type(properties) is str:
200 ElementTree.SubElement(root, '{DAV:}%s' % properties)
201 else:
202 props = ElementTree.SubElement(root, '{DAV:}prop')
203 object_to_etree(props, properties, namespace=namespace)
204 tree = ElementTree.ElementTree(root)
206 # Etree won't just return a normal string, so we have to do this
207 body = StringIO.StringIO()
208 tree.write(body)
209 body = body.getvalue()
211 # Add proper headers
212 if headers is None:
213 headers = {}
214 if depth is not None:
215 headers['Depth'] = depth
216 headers['Content-Type'] = 'text/xml; charset="utf-8"'
218 # Body encoding must be utf-8, 207 is proper response
219 self._request('PROPFIND', path, body=unicode('<?xml version="1.0" encoding="utf-8" ?>\n'+body, 'utf-8'), headers=headers)
221 if self.response is not None and hasattr(self.response, 'tree') is True:
222 property_responses = {}
223 for response in self.response.tree._children:
224 property_href = response.find('{DAV:}href')
225 property_stat = response.find('{DAV:}propstat')
227 def parse_props(props):
228 property_dict = {}
229 for prop in props:
230 if prop.tag.find('{DAV:}') is not -1:
231 name = prop.tag.split('}')[-1]
232 else:
233 name = prop.tag
234 if len(prop._children) is not 0:
235 property_dict[name] = parse_props(prop._children)
236 else:
237 property_dict[name] = prop.text
238 return property_dict
240 if property_href is not None and property_stat is not None:
241 property_dict = parse_props(property_stat.find('{DAV:}prop')._children)
242 property_responses[property_href.text] = property_dict
243 return property_responses
245 def proppatch(self, path, set_props=None, remove_props=None, namespace='DAV:', headers=None):
246 """Patch properties on a DAV resource. If namespace is not specified the DAV namespace is used for all properties"""
247 root = ElementTree.Element('{DAV:}propertyupdate')
249 if set_props is not None:
250 prop_set = ElementTree.SubElement(root, '{DAV:}set')
251 object_to_etree(prop_set, set_props, namespace=namespace)
252 if remove_props is not None:
253 prop_remove = ElementTree.SubElement(root, '{DAV:}remove')
254 object_to_etree(prop_remove, remove_props, namespace=namespace)
256 tree = ElementTree.ElementTree(root)
258 # Add proper headers
259 if headers is None:
260 headers = {}
261 headers['Content-Type'] = 'text/xml; charset="utf-8"'
263 self._request('PROPPATCH', path, body=unicode('<?xml version="1.0" encoding="utf-8" ?>\n'+body, 'utf-8'), headers=headers)
266 def set_lock(self, path, owner, locktype='exclusive', lockscope='write', depth=None, headers=None):
267 """Set a lock on a dav resource"""
268 root = ElementTree.Element('{DAV:}lockinfo')
269 object_to_etree(root, {'locktype':locktype, 'lockscope':lockscope, 'owner':{'href':owner}}, namespace='DAV:')
270 tree = ElementTree.ElementTree(root)
272 # Add proper headers
273 if headers is None:
274 headers = {}
275 if depth is not None:
276 headers['Depth'] = depth
277 headers['Content-Type'] = 'text/xml; charset="utf-8"'
278 headers['Timeout'] = 'Infinite, Second-4100000000'
280 self._request('LOCK', path, body=unicode('<?xml version="1.0" encoding="utf-8" ?>\n'+body, 'utf-8'), headers=headers)
282 locks = self.response.etree.finall('.//{DAV:}locktoken')
283 lock_list = []
284 for lock in locks:
285 lock_list.append(lock.getchildren()[0].text.strip().strip('\n'))
286 return lock_list
289 def refresh_lock(self, path, token, headers=None):
290 """Refresh lock with token"""
292 if headers is None:
293 headers = {}
294 headers['If'] = '(<%s>)' % token
295 headers['Timeout'] = 'Infinite, Second-4100000000'
297 self._request('LOCK', path, body=None, headers=headers)
300 def unlock(self, path, token, headers=None):
301 """Unlock DAV resource with token"""
302 if headers is None:
303 headers = {}
304 headers['Lock-Tocken'] = '<%s>' % token
306 self._request('UNLOCK', path, body=None, headers=headers)