[MERGE] lp:~xrg/openobject-addons/trunk-patch18
[odoo/odoo.git] / addons / document_webdav / webdav_server.py
index 3dae8f9..e23daab 100644 (file)
@@ -1,9 +1,15 @@
 # -*- encoding: utf-8 -*-
 ############################################################################9
 #
-# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
+# Copyright P. Christeas <p_christ@hol.gr> 2008-2010
 # Copyright OpenERP SA, 2010 (http://www.openerp.com )
 #
+# Disclaimer: Many of the functions below borrow code from the
+#   python-webdav library (http://code.google.com/p/pywebdav/ ),
+#   which they import and override to suit OpenERP functionality.
+# python-webdav was written by: Simon Pamies <s.pamies@banality.de>
+#                               Christian Scholz <mrtopf@webdav.de>
+#                               Vince Spicer <vince@vince.ca>
 #
 # WARNING: This program as such is intended to be used by professional
 # programmers who take the whole responsability of assessing all potential
@@ -28,6 +34,8 @@
 ###############################################################################
 
 
+import logging
+import netsvc
 from dav_fs import openerp_dav_handler
 from tools.config import config
 from DAV.WebDAVServer import DAVRequestHandler
@@ -40,9 +48,11 @@ import re
 import time
 from string import atoi
 import addons
+from DAV.utils import IfParser, TagList
 from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound
 from DAV.propfind import PROPFIND
 # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2
+from xml.dom import minidom
 from redirect import RedirectHTTPHandler
 
 khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
@@ -63,6 +73,7 @@ def OpenDAVConfig(**kw):
 
 class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
     verbose = False
+    _logger = logging.getLogger('webdav')
     protocol_version = 'HTTP/1.1'
     _HTTP_OPTIONS= { 'DAV' : ['1', '2'],
                     'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT',
@@ -72,8 +83,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
 
     def get_userinfo(self,user,pw):
         return False
+
     def _log(self, message):
-        netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
+        self._logger.debug(message)
 
     def handle(self):
         self._init_buffer()
@@ -108,16 +120,17 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
             self.headers['Destination'] = up.path[len(self.davpath):]
         else:
             raise DAV_Forbidden("Not allowed to copy/move outside webdav path")
+        # TODO: locks
         DAVRequestHandler.copymove(self, CLASS)
 
     def get_davpath(self):
         return self.davpath
 
     def log_message(self, format, *args):
-        netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args)
+        self._logger.log(netsvc.logging.DEBUG_RPC,format % args)
 
     def log_error(self, format, *args):
-        netsvc.Logger().notifyChannel('xmlrpc', netsvc.LOG_WARNING, format % args)
+        self._logger.warning(format % args)
 
     def _prep_OPTIONS(self, opts):
         ret = opts
@@ -267,6 +280,139 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
         except DAV_Error, (ec, dd):
             return self.send_status(ec)
 
+    def do_UNLOCK(self):
+        """ Unlocks given resource """
+
+        dc = self.IFACE_CLASS
+        self.log_message('UNLOCKing resource %s' % self.headers)
+
+        uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+        uri = urllib.unquote(uri)
+
+        token = self.headers.get('Lock-Token', False)
+        if token:
+            token = token.strip()
+            if token[0] == '<' and token[-1] == '>':
+                token = token[1:-1]
+            else:
+                token = False
+
+        if not token:
+            return self.send_status(400, 'Bad lock token')
+
+        try:
+            res = dc.unlock(uri, token)
+        except DAV_Error, (ec, dd):
+            return self.send_status(ec, dd)
+        
+        if res == True:
+            self.send_body(None, '204', 'OK', 'Resource unlocked.')
+        else:
+            # We just differentiate the description, for debugging purposes
+            self.send_body(None, '204', 'OK', 'Resource not locked.')
+
+    def do_LOCK(self):
+        """ Attempt to place a lock on the given resource.
+        """
+
+        dc = self.IFACE_CLASS
+        lock_data = {}
+
+        self.log_message('LOCKing resource %s' % self.headers)
+
+        body = None
+        if self.headers.has_key('Content-Length'):
+            l = self.headers['Content-Length']
+            body = self.rfile.read(atoi(l))
+
+        depth = self.headers.get('Depth', 'infinity')
+
+        uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
+        uri = urllib.unquote(uri)
+        self.log_message('do_LOCK: uri = %s' % uri)
+
+        ifheader = self.headers.get('If')
+
+        if ifheader:
+            ldif = IfParser(ifheader)
+            if isinstance(ldif, list):
+                if len(ldif) !=1 or (not isinstance(ldif[0], TagList)) \
+                        or len(ldif[0].list) != 1:
+                    raise DAV_Error(400, "Cannot accept multiple tokens")
+                ldif = ldif[0].list[0]
+                if ldif[0] == '<' and ldif[-1] == '>':
+                    ldif = ldif[1:-1]
+
+            lock_data['token'] = ldif
+
+        if not body:
+            lock_data['refresh'] = True
+        else:
+            lock_data['refresh'] = False
+            lock_data.update(self._lock_unlock_parse(body))
+
+        if lock_data['refresh'] and not lock_data.get('token', False):
+            raise DAV_Error(400, 'Lock refresh must specify token')
+
+        lock_data['depth'] = depth
+
+        try:
+            created, data, lock_token = dc.lock(uri, lock_data)
+        except DAV_Error, (ec, dd):
+            return self.send_status(ec, dd)
+
+        headers = {}
+        if not lock_data['refresh']:
+            headers['Lock-Token'] = '<%s>' % lock_token
+
+        if created:
+            self.send_body(data, '201', 'Created',  ctype='text/xml', headers=headers)
+        else:
+            self.send_body(data, '200', 'OK', ctype='text/xml', headers=headers)
+
+    def _lock_unlock_parse(self, body):
+        # Override the python-webdav function, with some improvements
+        # Unlike the py-webdav one, we also parse the owner minidom elements into
+        # pure pythonic struct.
+        doc = minidom.parseString(body)
+
+        data = {}
+        owners = []
+        for info in doc.getElementsByTagNameNS('DAV:', 'lockinfo'):
+            for scope in info.getElementsByTagNameNS('DAV:', 'lockscope'):
+                for scc in scope.childNodes:
+                    if scc.nodeType == info.ELEMENT_NODE \
+                            and scc.namespaceURI == 'DAV:':
+                        data['lockscope'] = scc.localName
+                        break
+            for ltype in info.getElementsByTagNameNS('DAV:', 'locktype'):
+                for ltc in ltype.childNodes:
+                    if ltc.nodeType == info.ELEMENT_NODE \
+                            and ltc.namespaceURI == 'DAV:':
+                        data['locktype'] = ltc.localName
+                        break
+            for own in info.getElementsByTagNameNS('DAV:', 'owner'):
+                for ono in own.childNodes:
+                    if ono.nodeType == info.TEXT_NODE:
+                        if ono.data:
+                            owners.append(ono.data)
+                    elif ono.nodeType == info.ELEMENT_NODE \
+                            and ono.namespaceURI == 'DAV:' \
+                            and ono.localName == 'href':
+                        href = ''
+                        for hno in ono.childNodes:
+                            if hno.nodeType == info.TEXT_NODE:
+                                href += hno.data
+                        owners.append(('href','DAV:', href))
+
+            if len(owners) == 1:
+                data['lockowner'] = owners[0]
+            elif not owners:
+                pass
+            else:
+                data['lockowner'] = owners
+        return data
+
 from service.http_server import reg_http_service,OpenERPAuthProvider
 
 class DAVAuthProvider(OpenERPAuthProvider):
@@ -427,7 +573,7 @@ try:
         conf = OpenDAVConfig(**_dc)
         handler._config = conf
         reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider()))
-        netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% directory)
+        logging.getLogger('webdav').info("WebDAV service registered at path: %s/ "% directory)
         
         if not (config.get_misc('webdav', 'no_root_hack', False)):
             # Now, replace the static http handler with the dav-enabled one.
@@ -451,8 +597,7 @@ try:
                                 (dir_path))
 
 except Exception, e:
-    logger = netsvc.Logger()
-    logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
+    logging.getLogger('webdav').error('Cannot launch webdav: %s' % e)
 
 
 def init_well_known():