[MERGE] lp:~xrg/openobject-addons/trunk-patch18
[odoo/odoo.git] / addons / document_webdav / dav_fs.py
index 8800741..22f50cb 100644 (file)
 ##############################################################################
 import pooler
 
-import base64
-import sys
 import os
 import time
-from string import joinfields, split, lower
-
-from service import security
+import errno
 
 import netsvc
 import urlparse
 
-from DAV.constants import COLLECTION, OBJECT
-from DAV.errors import *
-from DAV.iface import *
+from DAV.constants import COLLECTION  #, OBJECT
+from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound
+from DAV.iface import dav_interface
 import urllib
 
 from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
-from document.nodes import node_res_dir, node_res_obj
 from cache import memoize
 from tools import misc
+
+from webdav import mk_lock_response
+
+try:
+    from tools.dict_tools import dict_merge2
+except ImportError:
+    from document.dict_tools import dict_merge2
+
 CACHE_SIZE=20000
 
 #hack for urlparse: add webdav in the net protocols
 urlparse.uses_netloc.append('webdav')
 urlparse.uses_netloc.append('webdavs')
 
+day_names = { 0: 'Mon', 1: 'Tue' , 2: 'Wed', 3: 'Thu', 4: 'Fri', 5: 'Sat', 6: 'Sun' }
+month_names = { 1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
+        7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec' }
+
+class DAV_NotFound2(DAV_NotFound):
+    """404 exception, that accepts our list uris
+    """
+    def __init__(self, *args):
+        if len(args) and isinstance(args[0], (tuple, list)):
+            path = ''.join([ '/' + x for x in args[0]])
+            args = (path, )
+        DAV_NotFound.__init__(self, *args)
+
+
+def _str2time(cre):
+    """ Convert a string with time representation (from db) into time (float)
+    """
+    if not cre:
+        return time.time()
+    frac = 0.0
+    if isinstance(cre, basestring) and '.' in cre:
+        fdot = cre.find('.')
+        frac = float(cre[fdot:])
+        cre = cre[:fdot]
+    return time.mktime(time.strptime(cre,'%Y-%m-%d %H:%M:%S')) + frac
+
+class BoundStream2(object):
+    """Wraps around a seekable buffer, reads a determined range of data
+    
+        Note that the supplied stream object MUST support a size() which
+        should return its data length (in bytes).
+    
+        A variation of the class in websrv_lib.py
+    """
+    
+    def __init__(self, stream, offset=None, length=None, chunk_size=None):
+        self._stream = stream
+        self._offset = offset or 0
+        self._length = length or self._stream.size()
+        self._rem_length = length
+        assert length and isinstance(length, (int, long))
+        assert length and length >= 0, length
+        self._chunk_size = chunk_size
+        if offset is not None:
+            self._stream.seek(offset)
+
+    def read(self, size=-1):
+        if not self._stream:
+            raise IOError(errno.EBADF, "read() without stream")
+        
+        if self._rem_length == 0:
+            return ''
+        elif self._rem_length < 0:
+            raise EOFError()
+
+        rsize = self._rem_length
+        if size > 0 and size < rsize:
+            rsize = size
+        if self._chunk_size and self._chunk_size < rsize:
+            rsize = self._chunk_size
+        
+        data = self._stream.read(rsize)
+        self._rem_length -= len(data)
+
+        return data
+
+    def __len__(self):
+        return self._length
+
+    def tell(self):
+        res = self._stream.tell()
+        if self._offset:
+            res -= self._offset
+        return res
+
+    def __iter__(self):
+        return self
+
+    def next(self):
+        return self.read(65536)
+
+    def seek(self, pos, whence=os.SEEK_SET):
+        """ Seek, computing our limited range
+        """
+        if whence == os.SEEK_SET:
+            if pos < 0 or pos > self._length:
+                raise IOError(errno.EINVAL,"Cannot seek")
+            self._stream.seek(pos - self._offset)
+            self._rem_length = self._length - pos
+        elif whence == os.SEEK_CUR:
+            if pos > 0:
+                if pos > self._rem_length:
+                    raise IOError(errno.EINVAL,"Cannot seek past end")
+                elif pos < 0:
+                    oldpos = self.tell()
+                    if oldpos + pos < 0:
+                        raise IOError(errno.EINVAL,"Cannot seek before start")
+                self._stream.seek(pos, os.SEEK_CUR)
+                self._rem_length -= pos
+        elif whence == os.SEEK_END:
+            if pos > 0:
+                raise IOError(errno.EINVAL,"Cannot seek past end")
+            else:
+                if self._length + pos < 0:
+                    raise IOError(errno.EINVAL,"Cannot seek before start")
+            newpos = self._offset + self._length + pos
+            self._stream.seek(newpos, os.SEEK_SET)
+            self._rem_length = 0 - pos
+
 class openerp_dav_handler(dav_interface):
     """
     This class models a OpenERP interface for the DAV server
@@ -54,14 +166,14 @@ class openerp_dav_handler(dav_interface):
 
     M_NS={ "DAV:" : dav_interface.M_NS['DAV:'],}
 
-    def __init__(self,  parent, verbose=False):        
+    def __init__(self,  parent, verbose=False):
         self.db_name_list=[]
         self.parent = parent
         self.baseuri = parent.baseuri
         self.verbose = verbose
 
     def get_propnames(self, uri):
-        props = self.PROPS   
+        props = self.PROPS
         self.parent.log_message('get propnames: %s' % uri)
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
@@ -70,28 +182,106 @@ class openerp_dav_handler(dav_interface):
             return props
         node = self.uri2object(cr, uid, pool, uri2)
         if node:
-            props.update(node.get_dav_props(cr))
-        cr.close()     
+            props = dict_merge2(props, node.get_dav_props(cr))
+        cr.close()
         return props
 
+    def _try_function(self, funct, args, opname='run function', cr=None,
+            default_exc=DAV_Forbidden):
+        """ Try to run a function, and properly convert exceptions to DAV ones.
+
+            @objname the name of the operation being performed
+            @param cr if given, the cursor to close at exceptions
+        """
+
+        try:
+            return funct(*args)
+        except DAV_Error:
+            if cr: cr.close()
+            raise
+        except NotImplementedError, e:
+            if cr: cr.close()
+            import traceback
+            self.parent.log_error("Cannot %s: %s", opname, str(e))
+            self.parent.log_message("Exc: %s",traceback.format_exc())
+            # see par 9.3.1 of rfc
+            raise DAV_Error(403, str(e) or 'Not supported at this path')
+        except EnvironmentError, err:
+            if cr: cr.close()
+            import traceback
+            self.parent.log_error("Cannot %s: %s", opname, err.strerror)
+            self.parent.log_message("Exc: %s",traceback.format_exc())
+            raise default_exc(err.strerror)
+        except Exception, e:
+            import traceback
+            if cr: cr.close()
+            self.parent.log_error("Cannot %s: %s", opname, str(e))
+            self.parent.log_message("Exc: %s",traceback.format_exc())
+            raise default_exc("Operation failed")
+
     def _get_dav_lockdiscovery(self, uri):
+        """ We raise that so that the node API is used """
         raise DAV_NotFound
 
     def _get_dav_supportedlock(self, uri):
+        """ We raise that so that the node API is used """
         raise DAV_NotFound
 
-    def match_prop(self, uri, match, ns, propname):        
+    def match_prop(self, uri, match, ns, propname):
+        if self.M_NS.has_key(ns):
+            return match == dav_interface.get_prop(self, uri, ns, propname)
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
             if cr: cr.close()
             raise DAV_NotFound
-        node = self.uri2object(cr, uid, pool, uri2)        
+        node = self.uri2object(cr, uid, pool, uri2)
         if not node:
             cr.close()
             raise DAV_NotFound
-        res = node.match_dav_eprop(cr, match, ns, propname)        
-        cr.close()          
-        return res  
+        res = node.match_dav_eprop(cr, match, ns, propname)
+        cr.close()
+        return res
+
+    def prep_http_options(self, uri, opts):
+        """see HttpOptions._prep_OPTIONS """
+        self.parent.log_message('get options: %s' % uri)
+        cr, uid, pool, dbname, uri2 = self.get_cr(uri, allow_last=True)
+
+        if not dbname:
+            if cr: cr.close()
+            return opts
+        node = self.uri2object(cr, uid, pool, uri2[:])
+
+        if not node:
+            if cr: cr.close()
+            return opts
+        else:
+            if hasattr(node, 'http_options'):
+                ret = opts.copy()
+                for key, val in node.http_options.items():
+                    if isinstance(val, basestring):
+                        val = [val, ]
+                    if key in ret:
+                        ret[key] = ret[key][:]  # copy the orig. array
+                    else:
+                        ret[key] = []
+                    ret[key].extend(val)
+
+                self.parent.log_message('options: %s' % ret)
+            else:
+                ret = opts
+            cr.close()
+            return ret
+
+    def reduce_useragent(self):
+        ua = self.parent.headers.get('User-Agent', False)
+        ctx = {}
+        if ua:
+            if 'iPhone' in ua:
+                ctx['DAV-client'] = 'iPhone'
+            elif 'Konqueror' in ua:
+                ctx['DAV-client'] = 'GroupDAV'
+        return ctx
 
     def get_prop(self, uri, ns, propname):
         """ return the value of a given property
@@ -99,37 +289,43 @@ class openerp_dav_handler(dav_interface):
             uri        -- uri of the object to get the property of
             ns        -- namespace of the property
             pname        -- name of the property
-         """            
+         """
         if self.M_NS.has_key(ns):
-            return dav_interface.get_prop(self, uri, ns, propname)
+            try:
+                # if it's not in the interface class, a "DAV:" property
+                # may be at the node class. So shouldn't give up early.
+                return dav_interface.get_prop(self, uri, ns, propname)
+            except DAV_NotFound:
+                pass
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
             if cr: cr.close()
             raise DAV_NotFound
-        node = self.uri2object(cr, uid, pool, uri2)
-        if not node:
+        try:
+            node = self.uri2object(cr, uid, pool, uri2)
+            if not node:
+                raise DAV_NotFound
+            res = node.get_dav_eprop(cr, ns, propname)
+        finally:
             cr.close()
-            raise DAV_NotFound
-        res = node.get_dav_eprop(cr, ns, propname)
-        cr.close()        
-        return res    
+        return res
 
     def get_db(self, uri, rest_ret=False, allow_last=False):
         """Parse the uri and get the dbname and the rest.
            Db name should be the first component in the unix-like
            path supplied in uri.
-           
+
            @param rest_ret Instead of the db_name, return (db_name, rest),
                 where rest is the remaining path
            @param allow_last If the dbname is the last component in the
                 path, allow it to be resolved. The default False value means
                 we will not attempt to use the db, unless there is more
                 path.
-                
+
            @return db_name or (dbname, rest) depending on rest_ret,
                 will return dbname=False when component is not found.
         """
-        
+
         uri2 = self.uri2local(uri)
         if uri2.startswith('/'):
             uri2 = uri2[1:]
@@ -155,7 +351,7 @@ class openerp_dav_handler(dav_interface):
         """ Return the base URI of this request, or even join it with the
             ajoin path elements
         """
-        return self.baseuri+ '/'.join(ajoin)
+        return self.parent.get_baseuri(self) + '/'.join(ajoin)
 
     @memoize(4)
     def db_list(self):
@@ -178,38 +374,67 @@ class openerp_dav_handler(dav_interface):
                     cr.close()
         return self.db_name_list
 
-    def get_childs(self, uri, filters=None):
-        """ return the child objects as self.baseuris for the given URI """        
-        self.parent.log_message('get childs: %s' % uri)
+    def get_childs(self,uri, filters=None):
+        """ return the child objects as self.baseuris for the given URI """
+        self.parent.log_message('get children: %s' % uri)
         cr, uid, pool, dbname, uri2 = self.get_cr(uri, allow_last=True)
-        
-        if not dbname:            
+
+        if not dbname:
             if cr: cr.close()
-            res = map(lambda x: self.urijoin(x), self.db_list())            
+            res = map(lambda x: self.urijoin(x), self.db_list())
             return res
         result = []
         node = self.uri2object(cr, uid, pool, uri2[:])
-        
-        if not node:
-            if cr: cr.close()
-            raise DAV_NotFound(uri2)
-        else:
-            fp = node.full_path()
-            if fp and len(fp):
-                self.parent.log_message('childs: @%s' % fp)
-                fp = '/'.join(fp)
+
+        try:
+            if not node:
+                raise DAV_NotFound2(uri2)
             else:
-                fp = None
-            domain = None
-            if filters:
-                domain = node.get_domain(cr, filters)
-            for d in node.children(cr, domain):
-                self.parent.log_message('child: %s' % d.path)                
-                if fp:
-                    result.append( self.urijoin(dbname,fp,d.path) )
+                fp = node.full_path()
+                if fp and len(fp):
+                    fp = '/'.join(fp)
+                    self.parent.log_message('children for: %s' % fp)
                 else:
-                    result.append( self.urijoin(dbname,d.path) )
-        if cr: cr.close()        
+                    fp = None
+                domain = None
+                if filters:
+                    domain = node.get_domain(cr, filters)
+                    
+                    if hasattr(filters, 'getElementsByTagNameNS'):
+                        hrefs = filters.getElementsByTagNameNS('DAV:', 'href')
+                        if hrefs:
+                            ul = self.parent.davpath + self.uri2local(uri)
+                            for hr in hrefs:
+                                turi = ''
+                                for tx in hr.childNodes:
+                                    if tx.nodeType == hr.TEXT_NODE:
+                                        turi += tx.data
+                                if not turi.startswith('/'):
+                                    # it may be an absolute URL, decode to the
+                                    # relative part, because ul is relative, anyway
+                                    uparts=urlparse.urlparse(turi)
+                                    turi=uparts[2]
+                                if turi.startswith(ul):
+                                    result.append( turi[len(self.parent.davpath):])
+                                else:
+                                    self.parent.log_error("ignore href %s because it is not under request path %s", turi, ul)
+                            return result
+                            # We don't want to continue with the children found below
+                            # Note the exceptions and that 'finally' will close the
+                            # cursor
+                for d in node.children(cr, domain):
+                    self.parent.log_message('child: %s' % d.path)
+                    if fp:
+                        result.append( self.urijoin(dbname,fp,d.path) )
+                    else:
+                        result.append( self.urijoin(dbname,d.path) )
+        except DAV_Error:
+            raise
+        except Exception, e:
+            self.parent.log_error("cannot get_children: "+ str(e))
+            raise
+        finally:
+            if cr: cr.close()
         return result
 
     def uri2local(self, uri):
@@ -245,7 +470,8 @@ class openerp_dav_handler(dav_interface):
     def uri2object(self, cr, uid, pool, uri):
         if not uid:
             return None
-        return pool.get('document.directory').get_object(cr, uid, uri)
+        context = self.reduce_useragent()
+        return pool.get('document.directory').get_object(cr, uid, uri, context=context)
 
     def get_data(self,uri, rrange=None):
         self.parent.log_message('GET: %s' % uri)
@@ -253,45 +479,64 @@ class openerp_dav_handler(dav_interface):
         try:
             if not dbname:
                 raise DAV_Error, 409
-            node = self.uri2object(cr, uid, pool, uri2)   
+            node = self.uri2object(cr, uid, pool, uri2)
             if not node:
-                raise DAV_NotFound(uri2)
+                raise DAV_NotFound2(uri2)
+            # TODO: if node is a collection, for some specific set of
+            # clients ( web browsers; available in node context), 
+            # we may return a pseydo-html page with the directory listing.
             try:
+                res = node.open_data(cr,'r')
                 if rrange:
-                    self.parent.log_error("Doc get_data cannot use range")
-                    raise DAV_Error(409)
-                datas = node.get_data(cr)
+                    assert isinstance(rrange, (tuple,list))
+                    start, end = map(long, rrange)
+                    if not start:
+                        start = 0
+                    assert start >= 0
+                    if end and end < start:
+                        self.parent.log_error("Invalid range for data: %s-%s" %(start, end))
+                        raise DAV_Error(416, "Invalid range for data")
+                    if end:
+                        if end >= res.size():
+                            raise DAV_Error(416, "Requested data exceeds available size")
+                        length = (end + 1) - start
+                    else:
+                        length = res.size() - start
+                    res = BoundStream2(res, offset=start, length=length)
+                
             except TypeError,e:
-                import traceback                
-                self.parent.log_error("GET typeError: %s", str(e))
-                self.parent.log_message("Exc: %s",traceback.format_exc())
-                raise DAV_Forbidden
+                # for the collections that return this error, the DAV standard
+                # says we'd better just return 200 OK with empty data
+                return ''
             except IndexError,e :
                 self.parent.log_error("GET IndexError: %s", str(e))
-                raise DAV_NotFound(uri2)
+                raise DAV_NotFound2(uri2)
             except Exception,e:
                 import traceback
                 self.parent.log_error("GET exception: %s",str(e))
                 self.parent.log_message("Exc: %s", traceback.format_exc())
                 raise DAV_Error, 409
-            return datas
+            return res
         finally:
-            if cr: cr.close()    
+            if cr: cr.close()
 
     @memoize(CACHE_SIZE)
-    def _get_dav_resourcetype(self,uri):
-        """ return type of object """        
+    def _get_dav_resourcetype(self, uri):
+        """ return type of object """
         self.parent.log_message('get RT: %s' % uri)
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         try:
             if not dbname:
                 return COLLECTION
-            node = self.uri2object(cr,uid,pool, uri2)
+            node = self.uri2object(cr, uid, pool, uri2)
             if not node:
-                raise DAV_NotFound(uri2)
-            if node.type in ('collection','database'):
-                return COLLECTION
-            return OBJECT
+                raise DAV_NotFound2(uri2)
+            try:
+                return node.get_dav_resourcetype(cr)
+            except NotImplementedError:
+                if node.type in ('collection','database'):
+                    return ('collection', 'DAV:')
+                return ''
         finally:
             if cr: cr.close()
 
@@ -299,12 +544,16 @@ class openerp_dav_handler(dav_interface):
         self.parent.log_message('get DN: %s' % uri)
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
-            cr.close()
-            return COLLECTION
+            if cr: cr.close()
+            # at root, dbname, just return the last component
+            # of the path.
+            if uri2 and len(uri2) < 2:
+                return uri2[-1]
+            return ''
         node = self.uri2object(cr, uid, pool, uri2)
         if not node:
-            cr.close()
-            raise DAV_NotFound(uri2)
+            if cr: cr.close()
+            raise DAV_NotFound2(uri2)
         cr.close()
         return node.displayname
 
@@ -319,8 +568,8 @@ class openerp_dav_handler(dav_interface):
             return str(result)
         node = self.uri2object(cr, uid, pool, uri2)
         if not node:
-            cr.close()
-            raise DAV_NotFound(uri2)
+            if cr: cr.close()
+            raise DAV_NotFound2(uri2)
         result = node.content_length or 0
         cr.close()
         return str(result)
@@ -332,34 +581,41 @@ class openerp_dav_handler(dav_interface):
         result = 0
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
-            cr.close()
+            if cr: cr.close()
             return '0'
         node = self.uri2object(cr, uid, pool, uri2)
         if not node:
             cr.close()
-            raise DAV_NotFound(uri2)
-        result = node.get_etag(cr)
+            raise DAV_NotFound2(uri2)
+        result = self._try_function(node.get_etag ,(cr,), "etag %s" %uri, cr=cr)
         cr.close()
         return str(result)
 
     @memoize(CACHE_SIZE)
     def get_lastmodified(self, uri):
         """ return the last modified date of the object """
-        today = time.time()
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
-            return today
+            return time.time()
         try:            
             node = self.uri2object(cr, uid, pool, uri2)
             if not node:
-                raise DAV_NotFound(uri2)
-            if node.write_date:
-                return time.mktime(time.strptime(node.write_date,'%Y-%m-%d %H:%M:%S'))
-            else:
-                return today
+                raise DAV_NotFound2(uri2)
+            return _str2time(node.write_date)
         finally:
             if cr: cr.close()
 
+    def _get_dav_getlastmodified(self,uri):
+        """ return the last modified date of a resource
+        """
+        d=self.get_lastmodified(uri)
+        # format it. Note that we explicitly set the day, month names from
+        # an array, so that strftime() doesn't use its own locale-aware
+        # strings.
+        gmt = time.gmtime(d)
+        return time.strftime("%%s, %d %%s %Y %H:%M:%S GMT", gmt ) % \
+                    (day_names[gmt.tm_wday], month_names[gmt.tm_mon])
+
     @memoize(CACHE_SIZE)
     def get_creationdate(self, uri):
         """ return the last modified date of the object """        
@@ -369,12 +625,9 @@ class openerp_dav_handler(dav_interface):
         try:            
             node = self.uri2object(cr, uid, pool, uri2)
             if not node:
-                raise DAV_NotFound(uri2)            
-            if node.create_date:
-                result = time.mktime(time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S'))
-            else:
-                result = time.time()
-            return result
+                raise DAV_NotFound2(uri2)
+
+            return _str2time(node.create_date)
         finally:
             if cr: cr.close()
 
@@ -383,11 +636,12 @@ class openerp_dav_handler(dav_interface):
         self.parent.log_message('get contenttype: %s' % uri)
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)
         if not dbname:
+            if cr: cr.close()
             return 'httpd/unix-directory'
         try:            
             node = self.uri2object(cr, uid, pool, uri2)
             if not node:
-                raise DAV_NotFound(uri2)
+                raise DAV_NotFound2(uri2)
             result = str(node.mimetype)
             return result
             #raise DAV_NotFound, 'Could not find %s' % path
@@ -395,69 +649,93 @@ class openerp_dav_handler(dav_interface):
             if cr: cr.close()    
     
     def mkcol(self,uri):
-        """ create a new collection """                  
+        """ create a new collection
+            see par. 9.3 of rfc4918
+        """
         self.parent.log_message('MKCOL: %s' % uri)
-        uri = self.uri2local(uri)[1:]
-        if uri[-1]=='/':uri=uri[:-1]
-        parent = '/'.join(uri.split('/')[:-1])        
-        parent = self.baseuri + parent
-        uri = self.baseuri + uri        
-        cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+        cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+        if not uri2[-1]:
+            if cr: cr.close()
+            raise DAV_Error(409, "Cannot create nameless collection")
         if not dbname:
+            if cr: cr.close()
             raise DAV_Error, 409
         node = self.uri2object(cr,uid,pool, uri2[:-1])
-        if node:
-            node.create_child_collection(cr, uri2[-1])  
-            cr.commit()      
+        if not node:
+            cr.close()
+            raise DAV_Error(409, "Parent path %s does not exist" % uri2[:-1])
+        nc = node.child(cr, uri2[-1])
+        if nc:
+            cr.close()
+            raise DAV_Error(405, "Path already exists")
+        self._try_function(node.create_child_collection, (cr, uri2[-1]),
+                    "create col %s" % uri2[-1], cr=cr)
+        cr.commit()
         cr.close()
         return True
 
     def put(self, uri, data, content_type=None):
         """ put the object into the filesystem """
         self.parent.log_message('Putting %s (%d), %s'%( misc.ustr(uri), data and len(data) or 0, content_type))
-        parent='/'.join(uri.split('/')[:-1])
         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
         if not dbname:
+            if cr: cr.close()
             raise DAV_Forbidden
         try:
             node = self.uri2object(cr, uid, pool, uri2[:])
-        except:
+        except Exception:
             node = False
         
-        objname = uri2[-1]
-        ext = objname.find('.') >0 and objname.split('.')[1] or False
-
+        objname = misc.ustr(uri2[-1])
+        
+        ret = None
         if not node:
             dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
             if not dir_node:
+                cr.close()
                 raise DAV_NotFound('Parent folder not found')
+
+            newchild = self._try_function(dir_node.create_child, (cr, objname, data),
+                    "create %s" % objname, cr=cr)
+            if not newchild:
+                cr.commit()
+                cr.close()
+                raise DAV_Error(400, "Failed to create resource")
+            
+            uparts=urlparse.urlparse(uri)
+            fileloc = '/'.join(newchild.full_path())
+            if isinstance(fileloc, unicode):
+                fileloc = fileloc.encode('utf-8')
+            # the uri we get is a mangled one, where the davpath has been removed
+            davpath = self.parent.get_davpath()
+            
+            surl = '%s://%s' % (uparts[0], uparts[1])
+            uloc = urllib.quote(fileloc)
+            hurl = False
+            if uri != ('/'+uloc) and uri != (surl + '/' + uloc):
+                hurl = '%s%s/%s/%s' %(surl, davpath, dbname, uloc)
+            etag = False
             try:
-                dir_node.create_child(cr, objname, data)
-            except Exception,e:
-                import traceback
-                self.parent.log_error("Cannot create %s: %s", objname, str(e))
-                self.parent.log_message("Exc: %s",traceback.format_exc())
-                raise DAV_Forbidden
+                etag = str(newchild.get_etag(cr))
+            except Exception, e:
+                self.parent.log_error("Cannot get etag for node: %s" % e)
+            ret = (str(hurl), etag)
         else:
-            try:
-                node.set_data(cr, data)
-            except Exception,e:
-                import traceback
-                self.parent.log_error("Cannot save %s: %s", objname, str(e))
-                self.parent.log_message("Exc: %s",traceback.format_exc())
-                raise DAV_Forbidden
+            self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr)
             
         cr.commit()
         cr.close()
-        return 201
+        return ret
 
     def rmcol(self,uri):
         """ delete a collection """
         cr, uid, pool, dbname, uri2 = self.get_cr(uri)        
         if not dbname:
+            if cr: cr.close()
             raise DAV_Error, 409
+
         node = self.uri2object(cr, uid, pool, uri2)             
-        node.rmcol(cr)
+        self._try_function(node.rmcol, (cr,), "rmcol %s" % uri, cr=cr)
 
         cr.commit()
         cr.close()
@@ -466,11 +744,12 @@ class openerp_dav_handler(dav_interface):
     def rm(self,uri):
         cr, uid, pool,dbname, uri2 = self.get_cr(uri)
         if not dbname:        
-            cr.close()
+            if cr: cr.close()
             raise DAV_Error, 409
         node = self.uri2object(cr, uid, pool, uri2)
-        res = node.rm(cr)
+        res = self._try_function(node.rm, (cr,), "rm %s"  % uri, cr=cr)
         if not res:
+            if cr: cr.close()
             raise OSError(1, 'Operation not permited.')        
         cr.commit()
         cr.close()
@@ -491,7 +770,7 @@ class openerp_dav_handler(dav_interface):
         """
         if uri[-1]=='/':uri=uri[:-1]
         res=delone(self,uri)
-        parent='/'.join(uri.split('/')[:-1])
+        # parent='/'.join(uri.split('/')[:-1])
         return res
 
     def deltree(self, uri):
@@ -503,7 +782,7 @@ class openerp_dav_handler(dav_interface):
         """
         if uri[-1]=='/':uri=uri[:-1]
         res=deltree(self, uri)
-        parent='/'.join(uri.split('/')[:-1])
+        # parent='/'.join(uri.split('/')[:-1])
         return res
 
 
@@ -618,7 +897,6 @@ class openerp_dav_handler(dav_interface):
         advanced systems we might also have to copy properties from
         the source to the destination.
         """
-        print " copy a collection."
         return self.mkcol(dst)
 
 
@@ -633,14 +911,110 @@ class openerp_dav_handler(dav_interface):
             node = self.uri2object(cr, uid, pool, uri2)
             if node:
                 result = True
-        except:
+        except Exception:
             pass
         cr.close()
         return result
 
+    def unlock(self, uri, token):
+        """ Unlock a resource from that token 
+        
+        @return True if unlocked, False if no lock existed, Exceptions
+        """
+        cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+        if not dbname:
+            if cr: cr.close()
+            raise DAV_Error, 409
+
+        node = self.uri2object(cr, uid, pool, uri2)
+        try:
+            node_fn = node.dav_unlock
+        except AttributeError:
+            # perhaps the node doesn't support locks
+            cr.close()
+            raise DAV_Error(400, 'No locks for this resource')
+
+        res = self._try_function(node_fn, (cr, token), "unlock %s" % uri, cr=cr)
+        cr.commit()
+        cr.close()
+        return res
+
+    def lock(self, uri, lock_data):
+        """ Lock (may create) resource.
+            Data is a dict, may contain:
+                depth, token, refresh, lockscope, locktype, owner
+        """
+        cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+        created = False
+        if not dbname:
+            if cr: cr.close()
+            raise DAV_Error, 409
+
+        try:
+            node = self.uri2object(cr, uid, pool, uri2[:])
+        except Exception:
+            node = False
+        
+        objname = misc.ustr(uri2[-1])
+        
+        if not node:
+            dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
+            if not dir_node:
+                cr.close()
+                raise DAV_NotFound('Parent folder not found')
+
+            # We create a new node (file) but with empty data=None,
+            # as in RFC4918 p. 9.10.4
+            node = self._try_function(dir_node.create_child, (cr, objname, None),
+                    "create %s" % objname, cr=cr)
+            if not node:
+                cr.commit()
+                cr.close()
+                raise DAV_Error(400, "Failed to create resource")
+            
+            created = True
+
+        try:
+            node_fn = node.dav_lock
+        except AttributeError:
+            # perhaps the node doesn't support locks
+            cr.close()
+            raise DAV_Error(400, 'No locks for this resource')
+
+        # Obtain the lock on the node
+        lres, pid, token = self._try_function(node_fn, (cr, lock_data), "lock %s" % objname, cr=cr)
+
+        if not lres:
+            cr.commit()
+            cr.close()
+            raise DAV_Error(423, "Resource already locked")
+        
+        assert isinstance(lres, list), 'lres: %s' % repr(lres)
+        
+        try:
+            data = mk_lock_response(self, uri, lres)
+            cr.commit()
+        except Exception:
+            cr.close()
+            raise
+
+        cr.close()
+        return created, data, token
+
     @memoize(CACHE_SIZE)
     def is_collection(self, uri):
         """ test if the given uri is a collection """
-        return self._get_dav_resourcetype(uri)==COLLECTION
+        cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+        try:
+            if not dbname:
+                return True
+            node = self.uri2object(cr,uid,pool, uri2)
+            if not node:
+                raise DAV_NotFound2(uri2)
+            if node.type in ('collection','database'):
+                return True
+            return False
+        finally:
+            if cr: cr.close()
 
 #eof