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 | |
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.
-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 |