74
74
raise FatalBackendException("""For certificate verification a cacert database file is needed in one of these locations: %s
76
76
Consult the man page, chapter 'SSL Certificate Verification'.
77
Consider using the options --ssl-cacert-file, --ssl-no-check-certificate .""" % ", ".join(cacert_candidates) )
77
Consider using the options --ssl-cacert-file, --ssl-no-check-certificate .""" % ", ".join(cacert_candidates))
78
78
# check if file is accessible (libssl errors are not very detailed)
79
79
if not os.access(self.cacert_file, os.R_OK):
80
80
raise FatalBackendException("Cacert database file '%s' is not readable." % self.cacert_file)
98
98
return httplib.HTTPSConnection.request(self, *args, **kwargs)
99
99
except ssl.SSLError as e:
100
100
# encapsulate ssl errors
101
raise BackendException("SSL failed: %s" % util.uexc(e),log.ErrorCode.backend_error)
101
raise BackendException("SSL failed: %s" % util.uexc(e), log.ErrorCode.backend_error)
104
104
class WebDAVBackend(duplicity.backend.Backend):
137
def sanitize_path(self,path):
137
def sanitize_path(self, path):
139
139
foldpath = re.compile('/+')
140
return foldpath.sub('/', path + '/' )
140
return foldpath.sub('/', path + '/')
144
def getText(self,nodelist):
144
def getText(self, nodelist):
146
146
for node in nodelist:
147
147
if node.nodeType == node.TEXT_NODE:
163
163
log.Info("WebDAV create connection on '%s'" % (self.parsed_url.hostname))
165
165
# http schemes needed for redirect urls from servers
166
if self.parsed_url.scheme in ['webdav','http']:
166
if self.parsed_url.scheme in ['webdav', 'http']:
167
167
self.conn = httplib.HTTPConnection(self.parsed_url.hostname, self.parsed_url.port)
168
elif self.parsed_url.scheme in ['webdavs','https']:
168
elif self.parsed_url.scheme in ['webdavs', 'https']:
169
169
if globals.ssl_no_check_certificate:
170
170
self.conn = httplib.HTTPSConnection(self.parsed_url.hostname, self.parsed_url.port)
182
182
Wraps the connection.request method to retry once if authentication is
185
self._close() # or we get previous request's data or exception
185
self._close() # or we get previous request's data or exception
188
quoted_path = urllib.quote(path,"/:~")
188
quoted_path = urllib.quote(path, "/:~")
190
190
if self.digest_challenge is not None:
191
191
self.headers['Authorization'] = self.get_digest_authorization(path)
193
log.Info("WebDAV %s %s request with headers: %s " % (method,quoted_path,self.headers))
194
log.Info("WebDAV data length: %s " % len(str(data)) )
193
log.Info("WebDAV %s %s request with headers: %s " % (method, quoted_path, self.headers))
194
log.Info("WebDAV data length: %s " % len(str(data)))
195
195
self.conn.request(method, quoted_path, data, self.headers)
196
196
response = self.conn.getresponse()
197
log.Info("WebDAV response status %s with reason '%s'." % (response.status,response.reason))
197
log.Info("WebDAV response status %s with reason '%s'." % (response.status, response.reason))
198
198
# resolve redirects and reset url on listing requests (they usually come before everything else)
199
if response.status in [301,302] and method == 'PROPFIND':
200
redirect_url = response.getheader('location',None)
199
if response.status in [301, 302] and method == 'PROPFIND':
200
redirect_url = response.getheader('location', None)
203
log.Notice("WebDAV redirect to: %s " % urllib.unquote(redirect_url) )
203
log.Notice("WebDAV redirect to: %s " % urllib.unquote(redirect_url))
204
204
if redirected > 10:
205
205
raise FatalBackendException("WebDAV redirected 10 times. Giving up.")
206
206
self.parsed_url = duplicity.backend.ParsedUrl(redirect_url)
207
207
self.directory = self.sanitize_path(self.parsed_url.path)
208
return self.request(method,self.directory,data,redirected+1)
208
return self.request(method, self.directory, data, redirected + 1)
210
210
raise FatalBackendException("WebDAV missing location header in redirect response.")
211
211
elif response.status == 401:
214
214
self.headers['Authorization'] = self.get_authorization(response, quoted_path)
215
215
log.Info("WebDAV retry request with authentification headers.")
216
log.Info("WebDAV %s %s request2 with headers: %s " % (method,quoted_path,self.headers))
217
log.Info("WebDAV data length: %s " % len(str(data)) )
216
log.Info("WebDAV %s %s request2 with headers: %s " % (method, quoted_path, self.headers))
217
log.Info("WebDAV data length: %s " % len(str(data)))
218
218
self.conn.request(method, quoted_path, data, self.headers)
219
219
response = self.conn.getresponse()
220
log.Info("WebDAV response2 status %s with reason '%s'." % (response.status,response.reason))
220
log.Info("WebDAV response2 status %s with reason '%s'." % (response.status, response.reason))
275
275
del self.headers['Depth']
276
276
# if the target collection does not exist, create it.
277
277
if response.status == 404:
278
response.close() # otherwise next request fails with ResponseNotReady
278
response.close() # otherwise next request fails with ResponseNotReady
280
280
# just created an empty folder, so return empty
286
286
status = response.status
287
287
reason = response.reason
289
raise BackendException("Bad status code %s reason %s." % (status,reason))
289
raise BackendException("Bad status code %s reason %s." % (status, reason))
291
291
log.Debug("%s" % (document,))
292
292
dom = xml.dom.minidom.parseString(document)
307
307
# url causes directory to start with /, but it might be given
308
308
# with or without trailing / (which is required)
309
309
if dirs[-1] == '':
311
for i in range(1,len(dirs)):
312
d="/".join(dirs[0:i+1])+"/"
311
for i in range(1, len(dirs)):
312
d = "/".join(dirs[0:i + 1]) + "/"
314
314
self.headers['Depth'] = "1"
315
315
response = self.request("PROPFIND", d)
323
323
res = self.request("MKCOL", d)
324
324
if res.status != 201:
325
raise BackendException("WebDAV MKCOL %s failed: %s %s" % (d,res.status,res.reason))
325
raise BackendException("WebDAV MKCOL %s failed: %s %s" % (d, res.status, res.reason))
327
327
def taste_href(self, href):
356
356
raise BackendException(m)
358
358
if filename.startswith(self.directory):
359
filename = filename.replace(self.directory,'',1)
359
filename = filename.replace(self.directory, '', 1)