WebDAV Client a la Twisted
This is a proof-of-concept effort at the moment, and SSL redirects (for example) will fail because we fall back to Twisted's default SSL handling which we do not want.
We probably are also not reusing the connection (i.e. if you do two get's in succession we create two objects and do it with two connections). One of the goals of possibly switching to twisted-based solution is so that we could be more efficient.
import twisted.internet.reactor as reactor
import twisted.web.client as client
import twisted.internet.protocol as protocol
import twisted.protocols.policies as policies
import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
import M2Crypto.SSL as SSL
import urlparse
import base64
XML_CONTENT_TYPE = 'text/xml; charset="utf-8"'
XML_DOC_HEADER = '<?xml version="1.0" encoding="utf-8"?>'
DEFAULT_RETRIES = 3
class WebDAVPageGetter(client.HTTPPageGetter):
handleStatus_204 = lambda self: self.handleStatus_200()
handleStatus_207 = lambda self: self.handleStatus_200()
class WebDAVClientFactory(client.HTTPClientFactory):
protocol = WebDAVPageGetter
# Lifted from twisted
def _parse(url, defaultPort=None):
parsed = urlparse.urlparse(url)
scheme = parsed[0]
path = urlparse.urlunparse(('','')+parsed[2:])
if defaultPort is None:
if scheme == 'https':
defaultPort = 443
else:
defaultPort = 80
host, port = parsed[1], defaultPort
if ':' in host:
host, port = host.split(':')
port = int(port)
return scheme, host, port, path
# Lifted from twisted, slightly modified
def getPage(url, contextFactory=None, *args, **kwargs):
"""Download a web page as a string.
Download a page. Return a deferred, which will callback with a
page (as a string) or errback with a description of the error.
See HTTPClientFactory to see what extra args can be passed.
"""
scheme, host, port, path = _parse(url)
factory = WebDAVClientFactory(url, *args, **kwargs)
wrappingFactory = policies.WrappingFactory(factory)
wrappingFactory.protocol = wrapper.TLSProtocolWrapper
if scheme == 'https':
factory.startTLS = 1
#factory.getContext = lambda : self.ctx or Globals.crypto.getSSLContext()
factory.sslChecker = SSL.Checker.Checker()
reactor.connectTCP(host, port, wrappingFactory)
return factory.deferred
def success(value):
print value
reactor.stop()
def failure(error):
print error
reactor.stop()
class Client(object):
def __init__(self, host, port=80, username=None, password=None,
useSSL=False, ctx=None, retries=DEFAULT_RETRIES):
self.host = host
self.port = port
self.username = username
self.password = password
self.useSSL = useSSL
if self.useSSL:
self.scheme = 'https'
else:
self.scheme = 'http'
self.ctx = ctx # TODO
self.retries = retries # TODO
def get(self, path, extraHeaders={ }):
self._request('GET', path, body=None, extraHeaders=extraHeaders)
def put(self, path, body, contentType=None, contentEncoding=None, extraHeaders={ }):
# contentType, contentEncoding TODO
self._request('PUT', path, body, extraHeaders)
def propfind(self, path, body=None, depth=None, extraHeaders={ }):
extraHeaders = extraHeaders.copy()
extraHeaders['Content-Type'] = XML_CONTENT_TYPE
if body is None: # by default, ask for etags
body = "%s\n<D:propfind xmlns:D=\"DAV:\"><D:prop><D:getetag/></D:prop></D:propfind>" % XML_DOC_HEADER
if depth is not None:
extraHeaders['Depth'] = str(depth)
self._request('PROPFIND', path, body, extraHeaders)
def _request(self, method, path, body=None, extraHeaders={ }):
if self.username:
extraHeaders = extraHeaders.copy()
extraHeaders['Authorization'] = 'Basic ' + \
base64.encodestring(self.username + ':' + self.password).strip()
url = '%s://%s:%d/%s' %(self.scheme, self.host, self.port, path)
getPage(url,
method=method,
postdata=body,
headers=extraHeaders).addCallbacks(callback=success,
errback=failure)
c = Client('www.sharemation.com',
port=443,
username='', # Fill in your username and password here
password='',
useSSL=True)
c.get('heikki2/hello.txt')
reactor.run()