diff options
author | Sascha Silbe <sascha-pgp@silbe.org> | 2013-06-01 15:52:32 (GMT) |
---|---|---|
committer | Sascha Silbe <sascha-pgp@silbe.org> | 2013-06-01 15:52:32 (GMT) |
commit | f2608a44767b97817168dbe8507689657cf06572 (patch) | |
tree | 9340bcdfedf18eeb567a5d2c5af18914515552d3 /journal2webdav | |
parent | 6cbc55666a045c802c1b42c14a5ba950cb561ce7 (diff) |
Add another pywebdav work-around, fix up one work-around
Add a work-around for compatibility with HTTP/1.0 clients that don't
have persistent connections support and expect the connection to be
closed by the server.
Fix up the work-around for the <allprops/> response with depth!=0. It
was returning 404 for properties that are on _some_ resource, but not
the current one.
Make the work-arounds conditional, so we can deactivate them for
upstream versions that have the bugs fixed already. Otherwise we risk
breaking with future upstream versions that change implementation
details.
Diffstat (limited to 'journal2webdav')
-rwxr-xr-x | journal2webdav | 298 |
1 files changed, 200 insertions, 98 deletions
diff --git a/journal2webdav b/journal2webdav index f8b7d88..cf29f28 100755 --- a/journal2webdav +++ b/journal2webdav @@ -16,6 +16,8 @@ from BaseHTTPServer import HTTPServer from SocketServer import ThreadingMixIn import cgi +import cStringIO as StringIO +import gzip import logging import optparse import os @@ -27,19 +29,26 @@ from urlparse import urljoin, urlparse try: import pywebdav.lib as DAV except ImportError: - # old names + # pywebdav < 0.9.8 import DAV from DAV import propfind from DAV.constants import COLLECTION, OBJECT from DAV.iface import dav_interface from DAV.errors import DAV_Error, DAV_NotFound, DAV_Requested_Range_Not_Satisfiable from DAVServer.fileauth import DAVAuthHandler + _PYWEBDAV_BUGS = set(['PROPFIND_NS', 'ALLPROP_RECURSE']) else: from pywebdav.lib import propfind from pywebdav.lib.constants import COLLECTION, OBJECT from pywebdav.lib.iface import dav_interface from pywebdav.lib.errors import DAV_Error, DAV_NotFound, DAV_Requested_Range_Not_Satisfiable from pywebdav.lib.WebDAVServer import DAVRequestHandler as DAVAuthHandler + _PYWEBDAV_BUGS = set(['ALLPROP_RECURSE', 'HTTP10_KEEPALIVE']) + +if 'ALLPROP_RECURSE' in _PYWEBDAV_BUGS: + import xml.dom.minidom + domimpl = xml.dom.minidom.getDOMImplementation() + # from sugar.logger import trace @@ -67,70 +76,92 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): class PROPFIND(propfind.PROPFIND): - # pylint: disable=C0324,C0322 - def mk_propname_response(self,uri,propnames,doc): - # copy of original, but with bug fix for multiple namespaces - re=doc.createElement("D:response") - - # write href information - uparts=urlparse(uri) - fileloc=uparts[2] - href=doc.createElement("D:href") - huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc)) - href.appendChild(huri) - re.appendChild(href) - - ps=doc.createElement("D:propstat") - nsnum=0 - - for ns,plist in propnames.items(): - # write prop element - pr=doc.createElement("D:prop") - nsp="ns"+str(nsnum) - pr.setAttribute("xmlns:"+nsp,ns) - nsnum=nsnum+1 - - # write propertynames - for p in plist: - pe=doc.createElement(nsp+":"+p) - pr.appendChild(pe) - - ps.appendChild(pr) - - re.appendChild(ps) - - return re - - def create_allprop(self): - """ return a list of all properties """ - # modified to recurse over children if applicable - self.proplist={} - rel_path_q = [''] - if self._depth == 'infinity': - max_depth = sys.maxint - else: - max_depth = int(self._depth) - - while rel_path_q: - rel_path = rel_path_q.pop() - if rel_path: - uri = '%s/%s' % (self._uri, rel_path) - else: - uri = self._uri - - self.proplist.update(self._dataclass.get_propnames(uri)) - - if rel_path.count('/') >= max_depth: - continue - - if not self._dataclass.is_collection(uri): - continue - - rel_path_q += [child_uri[len(self._uri) + 1:] - for child_uri in self._dataclass.get_childs(uri)] - - self.namespaces=self.proplist.keys() - return self.create_prop() + if 'PROPFIND_NS' in _PYWEBDAV_BUGS: + # pylint: disable=C0324,C0322 + def mk_propname_response(self,uri,propnames,doc): + # copy of original, but with bug fix for multiple namespaces + re=doc.createElement("D:response") + + # write href information + uparts=urlparse(uri) + fileloc=uparts[2] + href=doc.createElement("D:href") + huri=doc.createTextNode(uparts[0]+'://'+'/'.join(uparts[1:2]) + urllib.quote(fileloc)) + href.appendChild(huri) + re.appendChild(href) + + ps=doc.createElement("D:propstat") + nsnum=0 + + for ns,plist in propnames.items(): + # write prop element + pr=doc.createElement("D:prop") + nsp="ns"+str(nsnum) + pr.setAttribute("xmlns:"+nsp,ns) + nsnum=nsnum+1 + + # write propertynames + for p in plist: + pe=doc.createElement(nsp+":"+p) + pr.appendChild(pe) + + ps.appendChild(pr) + + re.appendChild(ps) + + return re + + if 'ALLPROP_RECURSE' in _PYWEBDAV_BUGS: + # work-around for Debian#710690 + + def create_allprop(self): + return self.create_prop(True) + + def create_prop(self, allprop=False): + # create the document generator + doc = domimpl.createDocument(None, "multistatus", None) + ms = doc.documentElement + ms.setAttribute("xmlns:D", "DAV:") + ms.tagName = 'D:multistatus' + + if self._depth == "0": + if allprop: + self.proplist = self._dataclass.get_propnames(self._uri) + self.namespaces = self.proplist.keys() + gp, bp = self.get_propvalues(self._uri) + res = self.mk_prop_response(self._uri, gp, bp, doc) + ms.appendChild(res) + + elif self._depth == "1": + if allprop: + self.proplist = self._dataclass.get_propnames(self._uri) + self.namespaces = self.proplist.keys() + gp, bp = self.get_propvalues(self._uri) + res = self.mk_prop_response(self._uri, gp, bp, doc) + ms.appendChild(res) + + for newuri in self._dataclass.get_childs(self._uri): + if allprop: + self.proplist = self._dataclass.get_propnames(newuri) + self.namespaces = self.proplist.keys() + gp, bp = self.get_propvalues(newuri) + res = self.mk_prop_response(newuri, gp, bp, doc) + ms.appendChild(res) + elif self._depth == 'infinity': + uri_list = [self._uri] + while uri_list: + uri = uri_list.pop() + if allprop: + self.proplist = self._dataclass.get_propnames(uri) + self.namespaces = self.proplist.keys() + gp, bp = self.get_propvalues(uri) + res = self.mk_prop_response(uri, gp, bp, doc) + ms.appendChild(res) + uri_childs = self._dataclass.get_childs(uri) + if uri_childs: + uri_list.extend(uri_childs) + + return doc.toxml(encoding="utf-8") class JournalObjectResource(object): @@ -429,40 +460,111 @@ def setupDummyConfig(**kw): class RequestHandler(DAVAuthHandler): - # pylint: disable=W0402,W0404,C0324,W0612 - def do_PROPFIND(self): - from string import atoi - # exact copy of original, just to override the PROPFIND class - - dc = self.IFACE_CLASS - - # read the body containing the xml request - # iff there is no body then this is an ALLPROP request - body = None - if self.headers.has_key('Content-Length'): - l = self.headers['Content-Length'] - body = self.rfile.read(atoi(l)) - - uri = urljoin(self.get_baseuri(dc), self.path) - uri = urllib.unquote(uri) - - pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body) + if 'ALLPROP_RECURSE' in _PYWEBDAV_BUGS or 'PROPFIND_NS' in _PYWEBDAV_BUGS: + # pylint: disable=W0402,W0404,C0324,W0612 + def do_PROPFIND(self): + from string import atoi + # exact copy of original, just to override the PROPFIND class + + dc = self.IFACE_CLASS + + # read the body containing the xml request + # iff there is no body then this is an ALLPROP request + body = None + if 'Content-Length' in self.headers: + l = self.headers['Content-Length'] + body = self.rfile.read(atoi(l)) + + uri = urljoin(self.get_baseuri(dc), self.path) + uri = urllib.unquote(uri) + + try: + pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body) + except ExpatError: + # parse error + return self.send_status(400) + + try: + DATA = '%s\n' % pf.createResponse() + except DAV_Error, (ec,dd): + return self.send_status(ec) + + # work around MSIE DAV bug for creation and modified date + # taken from Resource.py @ Zope webdav + if (self.headers.get('User-Agent') == + 'Microsoft Data Access Internet Publishing Provider DAV 1.1'): + DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">', + '<ns0:getlastmodified xmlns:n="DAV:" ' + 'xmlns:b="urn:uuid:' + 'c2f41010-65b3-11d1-a29f-00aa00c14882/" ' + 'b:dt="dateTime.rfc1123">') + DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">', + '<ns0:creationdate xmlns:n="DAV:" ' + 'xmlns:b="urn:uuid:' + 'c2f41010-65b3-11d1-a29f-00aa00c14882/" ' + 'b:dt="dateTime.tz">') + + self.send_body_chunks_if_http11(DATA, 207, 'Multi-Status', + 'Multiple responses') + + if 'HTTP10_KEEPALIVE' in _PYWEBDAV_BUGS: + # work-around for Debian#710672 + + def send_body(self, DATA, code=None, msg=None, desc=None, + ctype='application/octet-stream', headers={}): + """ send a body in one part """ + log.debug("Use send_body method") + + if self.request_version != 'HTTP/1.1': + headers.pop('Keep-Alive', None) + headers.pop('Connection', None) + + self.send_response(code, message=msg) + if 'Connection' not in headers: + self.send_header("Connection", "close") + self.send_header("Accept-Ranges", "bytes") + + self._send_dav_version() + + for a, v in headers.items(): + self.send_header(a, v) + + if DATA: + if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \ + and len(DATA) > self.encode_threshold: + buffer = StringIO.StringIO() + output = gzip.GzipFile(mode='wb', fileobj=buffer) + if isinstance(DATA, str) or isinstance(DATA, unicode): + output.write(DATA) + else: + for buf in DATA: + output.write(buf) + output.close() + buffer.seek(0) + DATA = buffer.getvalue() + self.send_header('Content-Encoding', 'gzip') + + self.send_header('Content-Length', len(DATA)) + self.send_header('Content-Type', ctype) + else: + self.send_header('Content-Length', 0) - try: - DATA = '%s\n' % pf.createResponse() - except DAV_Error, (ec,dd): - return self.send_status(ec) - - # work around MSIE DAV bug for creation and modified date - # taken from Resource.py @ Zope webdav - if (self.headers.get('User-Agent') == - 'Microsoft Data Access Internet Publishing Provider DAV 1.1'): - DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">', - '<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">') - DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">', - '<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">') - - self.send_body_chunks_if_http11(DATA, 207,'Multi-Status','Multiple responses') + self.end_headers() + if DATA: + if isinstance(DATA, str) or isinstance(DATA, unicode): + log.debug("Don't use iterator") + self.wfile.write(DATA) + else: + if self._config.DAV.getboolean('http_response_use_iterator'): + # Use iterator to reduce using memory + log.debug("Use iterator") + for buf in DATA: + self.wfile.write(buf) + self.wfile.flush() + else: + # Don't use iterator, it's a compatibility option + log.debug("Don't use iterator") + self.wfile.write(DATA.read()) def log_message(self, format, *args): # pylint: disable=W0622 |