##############################################################################
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
M_NS={ "DAV:" : dav_interface.M_NS['DAV:'],}
- def __init__(self, parent, verbose=False):
- self.db_name = False
- self.directory_id=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)
- if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
if cr: cr.close()
+ # TODO: maybe limit props for databases..?
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):
- if uri[-1]=='/':uri=uri[:-1]
+ 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
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)
- if uri[-1]=='/':uri=uri[:-1]
+ 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
-
- def get_db(self,uri):
- names=self.uri2local(uri).split('/')
- self.db_name=False
- if len(names) > 1:
- self.db_name=self.uri2local(uri).split('/')[1]
- if self.db_name=='':
- raise Exception,'Plese specify Database name in folder'
- return self.db_name
-
+ 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:]
+ names=uri2.split('/',1)
+ db_name=False
+ rest = None
+ if allow_last:
+ ll = 0
+ else:
+ ll = 1
+ if len(names) > ll and names[0]:
+ db_name = names[0]
+ names = names[1:]
+
+ if rest_ret:
+ if len(names):
+ rest = names[0]
+ return db_name, rest
+ return db_name
+
def urijoin(self,*ajoin):
""" 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):
result = s.exp_list()
self.db_name_list=[]
for db_name in result:
- db = pooler.get_db_only(db_name)
- cr = db.cursor()
- cr.execute("select id from ir_module_module where name = 'document' and state='installed' ")
- res=cr.fetchone()
- if res and len(res):
- self.db_name_list.append(db_name)
- cr.close()
+ cr = None
+ try:
+ db = pooler.get_db_only(db_name)
+ cr = db.cursor()
+ cr.execute("SELECT id FROM ir_module_module WHERE name = 'document' AND state='installed' ")
+ res=cr.fetchone()
+ if res and len(res):
+ self.db_name_list.append(db_name)
+ except Exception, e:
+ self.parent.log_error("Exception in db list: %s" % e)
+ finally:
+ if cr:
+ 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)
- if uri[-1]=='/':uri=uri[:-1]
- cr, uid, pool, dbname, uri2 = self.get_cr(uri)
-
- if not dbname:
+ 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 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):
#
# pos: -1 to get the parent of the uri
#
- def get_cr(self, uri):
+ def get_cr(self, uri, allow_last=False):
+ """ Split the uri, grab a cursor for that db
+ """
pdb = self.parent.auth_proxy.last_auth
- reluri = self.uri2local(uri)
- try:
- dbname = reluri.split('/')[1]
- except:
- dbname = False
+ dbname, uri2 = self.get_db(uri, rest_ret=True, allow_last=allow_last)
+ uri2 = (uri2 and uri2.split('/')) or []
if not dbname:
- return None, None, None, False, None
- if not pdb and dbname:
- # if dbname was in our uri, we should have authenticated
- # against that.
- raise Exception("Programming error")
- #assert pdb == dbname, " %s != %s" %(pdb, dbname)
+ return None, None, None, False, uri2
+ # if dbname was in our uri, we should have authenticated
+ # against that.
+ assert pdb == dbname, " %s != %s" %(pdb, dbname)
res = self.parent.auth_proxy.auth_creds.get(dbname, False)
- if not res:
+ if not res:
self.parent.auth_proxy.checkRequest(self.parent, uri, dbname)
- res = self.parent.auth_proxy.auth_creds[dbname]
+ res = self.parent.auth_proxy.auth_creds[dbname]
user, passwd, dbn2, uid = res
db,pool = pooler.get_db_and_pool(dbname)
cr = db.cursor()
- uri2 = reluri.split('/')[2:]
return cr, uid, pool, dbname, uri2
- def uri2object(self, cr, uid, pool,uri):
+ 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)
- if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
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)
- if uri[-1]=='/':uri=uri[:-1]
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()
def _get_dav_displayname(self,uri):
self.parent.log_message('get DN: %s' % uri)
- if uri[-1]=='/':uri=uri[:-1]
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
def _get_dav_getcontentlength(self, uri):
""" return the content length of an object """
self.parent.log_message('get length: %s' % uri)
- if uri[-1]=='/':uri=uri[:-1]
result = 0
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
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)
def _get_dav_getetag(self,uri):
""" return the ETag of an object """
self.parent.log_message('get etag: %s' % uri)
- if uri[-1]=='/':uri=uri[:-1]
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 """
- if uri[-1]=='/':uri=uri[:-1]
- 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 """
- if uri[-1]=='/':uri=uri[:-1]
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
if not dbname:
raise DAV_Error, 409
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()
@memoize(CACHE_SIZE)
def _get_dav_getcontenttype(self,uri):
self.parent.log_message('get contenttype: %s' % uri)
- if uri[-1]=='/':uri=uri[:-1]
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)
- result = node.mimetype
+ raise DAV_NotFound2(uri2)
+ result = str(node.mimetype)
return result
#raise DAV_NotFound, 'Could not find %s' % path
finally:
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 """
- if uri[-1]=='/':uri=uri[:-1]
-
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
- if not dbname: # *-*
+ 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()
return 204
def rm(self,uri):
- if uri[-1]=='/':uri=uri[:-1]
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()
"""
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):
"""
if uri[-1]=='/':uri=uri[:-1]
res=deltree(self, uri)
- parent='/'.join(uri.split('/')[:-1])
+ # parent='/'.join(uri.split('/')[:-1])
return res
advanced systems we might also have to copy properties from
the source to the destination.
"""
- print " copy a collection."
return self.mkcol(dst)
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