summaryrefslogtreecommitdiff
path: root/addons/document_webdav
diff options
context:
space:
mode:
authornch@tinyerp.com <>2009-12-08 09:45:17 +0530
committernch@tinyerp.com <>2009-12-08 09:45:17 +0530
commit3f9ed76869c572b42641e4e22e5435bb10fa5e46 (patch)
treed957a2fe852c383a7c22148ccc15c20aff028d67 /addons/document_webdav
parentd7a580e0bf1d031f3daa0bf657055ec50e307cfd (diff)
parentb1eb90aa06b4c74dd623f50f716b936a3b33a09a (diff)
[MERGE]
bzr revid: nch@tinyerp.com-20091208041517-n8932hvnsprkcme8
Diffstat (limited to 'addons/document_webdav')
-rw-r--r--addons/document_webdav/DAV/BufferingHTTPServer.py99
-rw-r--r--addons/document_webdav/DAV/WebDAVServer.py379
-rw-r--r--addons/document_webdav/DAV/__init__.py20
-rw-r--r--addons/document_webdav/DAV/constants.py18
-rw-r--r--addons/document_webdav/DAV/davcmd.py218
-rwxr-xr-xaddons/document_webdav/DAV/davcopy.py133
-rwxr-xr-xaddons/document_webdav/DAV/davmove.py102
-rwxr-xr-xaddons/document_webdav/DAV/delete.py63
-rwxr-xr-xaddons/document_webdav/DAV/errors.py56
-rw-r--r--addons/document_webdav/DAV/iface.py263
-rwxr-xr-xaddons/document_webdav/DAV/propfind.py372
-rw-r--r--addons/document_webdav/DAV/status.py31
-rwxr-xr-xaddons/document_webdav/DAV/utils.py160
-rw-r--r--addons/document_webdav/__init__.py29
-rw-r--r--addons/document_webdav/__terp__.py47
-rw-r--r--addons/document_webdav/cache.py39
-rw-r--r--addons/document_webdav/dav_fs.py653
-rw-r--r--addons/document_webdav/i18n/document_webdav.pot26
-rw-r--r--addons/document_webdav/webdav_server.py80
19 files changed, 2788 insertions, 0 deletions
diff --git a/addons/document_webdav/DAV/BufferingHTTPServer.py b/addons/document_webdav/DAV/BufferingHTTPServer.py
new file mode 100644
index 00000000000..37520421432
--- /dev/null
+++ b/addons/document_webdav/DAV/BufferingHTTPServer.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+
+"""
+ Buffering HTTP Server
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+
+from utils import VERSION, AUTHOR
+__version__ = VERSION
+__author__ = AUTHOR
+
+from BaseHTTPServer import BaseHTTPRequestHandler
+import os
+class BufferedHTTPRequestHandler(BaseHTTPRequestHandler):
+ """
+ Buffering HTTP Request Handler
+
+ This class is an extension to the BaseHTTPRequestHandler
+ class which buffers the whole output and sends it at once
+ after the processing if the request is finished.
+
+ This makes it possible to work together with some clients
+ which otherwise would break (e.g. cadaver)
+
+ """
+
+
+ def _init_buffer(self):
+ """initialize the buffer.
+
+ If you override the handle() method remember to call
+ this (see below)
+ """
+ self.__buffer=""
+ self.__outfp=os.tmpfile()
+
+ def _append(self,s):
+ """ append a string to the buffer """
+ self.__buffer=self.__buffer+s
+
+ def _flush(self):
+ """ flush the buffer to wfile """
+ self.wfile.write(self.__buffer)
+ self.__outfp.write(self.__buffer)
+ self.__outfp.flush()
+ self.wfile.flush()
+ self.__buffer=""
+
+ def handle(self):
+ """ Handle a HTTP request """
+ self._init_buffer()
+ BaseHTTPRequestHandler.handle(self)
+ self._flush()
+
+ def send_header(self, keyword, value):
+ """Send a MIME header."""
+ if self.request_version != 'HTTP/0.9':
+ self._append("%s: %s\r\n" % (keyword, value))
+
+ def end_headers(self):
+ """Send the blank line ending the MIME headers."""
+ if self.request_version != 'HTTP/0.9':
+ self._append("\r\n")
+
+ def send_response(self, code, message=None):
+ self.log_request(code)
+
+ if message is None:
+ if self.responses.has_key(code):
+ message = self.responses[code][0]
+ else:
+ message = ''
+
+ if self.request_version != 'HTTP/0.9':
+ self._append("%s %s %s\r\n" %
+ (self.protocol_version, str(code), message))
+
+ self.send_header('Server', self.version_string())
+ self.send_header('Connection', 'close')
+ self.send_header('Date', self.date_time_string())
+
+ protocol_version="HTTP/1.1"
+
diff --git a/addons/document_webdav/DAV/WebDAVServer.py b/addons/document_webdav/DAV/WebDAVServer.py
new file mode 100644
index 00000000000..cb4db2c0204
--- /dev/null
+++ b/addons/document_webdav/DAV/WebDAVServer.py
@@ -0,0 +1,379 @@
+"""
+ Python WebDAV Server.
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+This module builds on AuthServer by implementing the standard DAV
+methods.
+
+Subclass this class and specify an IFACE_CLASS. See example.
+
+"""
+
+DEBUG=None
+
+from utils import VERSION, AUTHOR
+__version__ = VERSION
+__author__ = AUTHOR
+
+
+import os
+import sys
+import time
+import socket
+import string
+import posixpath
+import base64
+import urlparse
+import urllib
+
+from propfind import PROPFIND
+from delete import DELETE
+from davcopy import COPY
+from davmove import MOVE
+
+from string import atoi,split
+from status import STATUS_CODES
+from errors import *
+
+import BaseHTTPServer
+
+class DAVRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ """Simple DAV request handler with
+
+ - GET
+ - HEAD
+ - PUT
+ - OPTIONS
+ - PROPFIND
+ - PROPPATCH
+ - MKCOL
+
+ It uses the resource/collection classes for serving and
+ storing content.
+
+ """
+
+ server_version = "DAV/" + __version__
+ protocol_version = 'HTTP/1.1'
+
+ ### utility functions
+ def _log(self, message):
+ pass
+
+ def _append(self,s):
+ """ write the string to wfile """
+ self.wfile.write(s)
+
+ def send_body(self,DATA,code,msg,desc,ctype='application/octet-stream',headers=None):
+ """ send a body in one part """
+
+ if not headers:
+ headers = {}
+ self.send_response(code,message=msg)
+ self.send_header("Connection", "keep-alive")
+ self.send_header("Accept-Ranges", "bytes")
+
+ for a,v in headers.items():
+ self.send_header(a,v)
+
+ if DATA:
+ self.send_header("Content-Length", str(len(DATA)))
+ self.send_header("Content-Type", ctype)
+ else:
+ self.send_header("Content-Length", "0")
+
+ self.end_headers()
+ if DATA:
+ self._append(DATA)
+
+ def send_body_chunks(self,DATA,code,msg,desc,ctype='text/xml; encoding="utf-8"'):
+ """ send a body in chunks """
+
+ self.responses[207]=(msg,desc)
+ self.send_response(code,message=msg)
+ self.send_header("Content-type", ctype)
+ self.send_header("Connection", "keep-alive")
+ self.send_header("Transfer-Encoding", "chunked")
+ self.end_headers()
+ self._append(hex(len(DATA))[2:]+"\r\n")
+ self._append(DATA)
+ self._append("\r\n")
+ self._append("0\r\n")
+ self._append("\r\n")
+
+ ### HTTP METHODS
+
+ def do_OPTIONS(self):
+ """return the list of capabilities """
+ self.send_response(200)
+ self.send_header("Allow", "GET, HEAD, COPY, MOVE, POST, PUT, PROPFIND, PROPPATCH, OPTIONS, MKCOL, DELETE, TRACE")
+ self.send_header("Content-Type", "text/plain")
+ self.send_header("Connection", "keep-alive")
+ self.send_header("DAV", "1")
+ self.end_headers()
+
+ def do_PROPFIND(self):
+
+ dc=self.IFACE_CLASS
+ # read the body
+ body=None
+ if self.headers.has_key("Content-Length"):
+ l=self.headers['Content-Length']
+ body=self.rfile.read(atoi(l))
+ alt_body = """<?xml version="1.0" encoding="utf-8"?>
+ <propfind xmlns="DAV:"><prop>
+ <getcontentlength xmlns="DAV:"/>
+ <getlastmodified xmlns="DAV:"/>
+ <getcreationdate xmlns="DAV:"/>
+ <checked-in xmlns="DAV:"/>
+ <executable xmlns="http://apache.org/dav/props/"/>
+ <displayname xmlns="DAV:"/>
+ <resourcetype xmlns="DAV:"/>
+ <checked-out xmlns="DAV:"/>
+ </prop></propfind>"""
+ #self.wfile.write(body)
+
+ # which Depth?
+ if self.headers.has_key('Depth'):
+ d=self.headers['Depth']
+ else:
+ d="infinity"
+
+ uri=self.geturi()
+ pf=PROPFIND(uri,dc,d)
+
+ if body:
+ pf.read_propfind(body)
+
+ try:
+ DATA=pf.createResponse()
+ DATA=DATA+"\n"
+ # print "Data:", DATA
+ except DAV_NotFound,(ec,dd):
+ return self.send_notFound(dd, uri)
+ except DAV_Error, (ec,dd):
+ return self.send_error(ec,dd)
+
+ self.send_body_chunks(DATA,207,"Multi-Status","Multiple responses")
+
+ def geturi(self):
+ buri = self.IFACE_CLASS.baseuri
+ if buri[-1] == '/':
+ return urllib.unquote(buri[:-1]+self.path)
+ else:
+ return urllib.unquote(buri+self.path)
+
+ def do_GET(self):
+ """Serve a GET request."""
+ dc=self.IFACE_CLASS
+ uri=self.geturi()
+
+ # get the last modified date
+ try:
+ lm=dc.get_prop(uri,"DAV:","getlastmodified")
+ except:
+ lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
+ headers={"Last-Modified":lm , "Connection": "keep-alive"}
+
+ # get the content type
+ try:
+ ct=dc.get_prop(uri,"DAV:","getcontenttype")
+ except:
+ ct="application/octet-stream"
+
+ # get the data
+ try:
+ data=dc.get_data(uri)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+
+ # send the data
+ self.send_body(data,200,"OK","OK",ct,headers)
+
+ def do_HEAD(self):
+ """ Send a HEAD response """
+ dc=self.IFACE_CLASS
+ uri=self.geturi()
+
+ # get the last modified date
+ try:
+ lm=dc.get_prop(uri,"DAV:","getlastmodified")
+ except:
+ lm="Sun, 01 Dec 2014 00:00:00 GMT" # dummy!
+
+ headers={"Last-Modified":lm, "Connection": "keep-alive"}
+
+ # get the content type
+ try:
+ ct=dc.get_prop(uri,"DAV:","getcontenttype")
+ except:
+ ct="application/octet-stream"
+
+ try:
+ data=dc.get_data(uri)
+ headers["Content-Length"]=str(len(data))
+ except DAV_NotFound:
+ self.send_body(None,404,"Not Found","")
+ return
+
+ self.send_body(None,200,"OK","OK",ct,headers)
+
+ def do_POST(self):
+ self.send_error(404,"File not found")
+
+ def do_MKCOL(self):
+ """ create a new collection """
+
+ dc=self.IFACE_CLASS
+ uri=self.geturi()
+ try:
+ res = dc.mkcol(uri)
+ if res:
+ self.send_body(None,201,"Created",'')
+ else:
+ self.send_body(None,415,"Cannot create",'')
+ #self.send_header("Connection", "keep-alive")
+ # Todo: some content, too
+ except DAV_Error, (ec,dd):
+ self.send_body(None,int(ec),dd,dd)
+
+ def do_DELETE(self):
+ """ delete an resource """
+ dc=self.IFACE_CLASS
+ uri=self.geturi()
+ dl=DELETE(uri,dc)
+ if dc.is_collection(uri):
+ res=dl.delcol()
+ else:
+ res=dl.delone()
+
+ if res:
+ self.send_status(207,body=res)
+ else:
+ self.send_status(204)
+
+ def do_PUT(self):
+ dc=self.IFACE_CLASS
+
+ # read the body
+ body=None
+ if self.headers.has_key("Content-Length"):
+ l=self.headers['Content-Length']
+ body=self.rfile.read(atoi(l))
+ uri=self.geturi()
+
+ ct=None
+ if self.headers.has_key("Content-Type"):
+ ct=self.headers['Content-Type']
+ try:
+ dc.put(uri,body,ct)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+ self.send_status(201)
+
+ def do_COPY(self):
+ """ copy one resource to another """
+ try:
+ self.copymove(COPY)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+
+ def do_MOVE(self):
+ """ move one resource to another """
+ try:
+ self.copymove(MOVE)
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+
+ def copymove(self,CLASS):
+ """ common method for copying or moving objects """
+ dc=self.IFACE_CLASS
+
+ # get the source URI
+ source_uri=self.geturi()
+
+ # get the destination URI
+ dest_uri=self.headers['Destination']
+ dest_uri=urllib.unquote(dest_uri)
+
+ # Overwrite?
+ overwrite=1
+ result_code=204
+ if self.headers.has_key("Overwrite"):
+ if self.headers['Overwrite']=="F":
+ overwrite=None
+ result_code=201
+
+ # instanciate ACTION class
+ cp=CLASS(dc,source_uri,dest_uri,overwrite)
+
+ # Depth?
+ d="infinity"
+ if self.headers.has_key("Depth"):
+ d=self.headers['Depth']
+
+ if d!="0" and d!="infinity":
+ self.send_status(400)
+ return
+
+ if d=="0":
+ res=cp.single_action()
+ self.send_status(res)
+ return
+
+ # now it only can be "infinity" but we nevertheless check for a collection
+ if dc.is_collection(source_uri):
+ try:
+ res=cp.tree_action()
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+ else:
+ try:
+ res=cp.single_action()
+ except DAV_Error, (ec,dd):
+ self.send_status(ec)
+ return
+
+ if res:
+ self.send_body_chunks(res,207,STATUS_CODES[207],STATUS_CODES[207],
+ ctype='text/xml; charset="utf-8"')
+ else:
+ self.send_status(result_code)
+
+ def get_userinfo(self,user,pw):
+ """ Dummy method which lets all users in """
+
+ return 1
+
+ def send_status(self,code=200,mediatype='text/xml; charset="utf-8"', \
+ msg=None,body=None):
+
+ if not msg: msg=STATUS_CODES[code]
+ self.send_body(body,code,STATUS_CODES[code],msg,mediatype)
+
+ def send_notFound(self,descr,uri):
+ body = """<?xml version="1.0" encoding="utf-8" ?>
+ <D:response xmlns:D="DAV:">
+ <D:href>%s</D:href>
+ <D:error/>
+ <D:responsedescription>%s</D:responsedescription>
+ </D:response>
+ """
+ return self.send_status(404,descr, body=body % (uri,descr))
diff --git a/addons/document_webdav/DAV/__init__.py b/addons/document_webdav/DAV/__init__.py
new file mode 100644
index 00000000000..10abdb40014
--- /dev/null
+++ b/addons/document_webdav/DAV/__init__.py
@@ -0,0 +1,20 @@
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+
diff --git a/addons/document_webdav/DAV/constants.py b/addons/document_webdav/DAV/constants.py
new file mode 100644
index 00000000000..e858ac519c2
--- /dev/null
+++ b/addons/document_webdav/DAV/constants.py
@@ -0,0 +1,18 @@
+"""
+
+constants definition
+
+
+"""
+
+# definition for resourcetype
+COLLECTION=1
+OBJECT=None
+DAV_PROPS=['creationdate', 'displayname', 'getcontentlanguage', 'getcontentlength', 'getcontenttype', 'getetag', 'getlastmodified', 'lockdiscovery', 'resourcetype', 'source', 'supportedlock']
+
+
+# Request classes in propfind
+
+RT_ALLPROP=1
+RT_PROPNAME=2
+RT_PROP=3
diff --git a/addons/document_webdav/DAV/davcmd.py b/addons/document_webdav/DAV/davcmd.py
new file mode 100644
index 00000000000..5c6be3ff9f7
--- /dev/null
+++ b/addons/document_webdav/DAV/davcmd.py
@@ -0,0 +1,218 @@
+"""
+
+davcmd.py
+---------
+
+containts commands like copy, move, delete for normal
+resources and collections
+
+"""
+
+from string import split,replace,joinfields
+import urlparse
+
+from utils import create_treelist, is_prefix
+from errors import *
+
+def deltree(dc,uri,exclude={}):
+ """ delete a tree of resources
+
+ dc -- dataclass to use
+ uri -- root uri to delete
+ exclude -- an optional list of uri:error_code pairs which should not
+ be deleted.
+
+ returns dict of uri:error_code tuples from which
+ another method can create a multistatus xml element.
+
+ Also note that we only know Depth=infinity thus we don't have
+ to test for it.
+
+ """
+
+ tlist=create_treelist(dc,uri)
+ result={}
+
+ for i in range(len(tlist),0,-1):
+ problem_uris=result.keys()
+ element=tlist[i-1]
+
+ # test here, if an element is a prefix of an uri which
+ # generated an error before.
+ # note that we walk here from childs to parents, thus
+ # we cannot delete a parent if a child made a problem.
+ # (see example in 8.6.2.1)
+ ok=1
+ for p in problem_uris:
+ if is_prefix(element,p):
+ ok=None
+ break
+
+ if not ok: continue
+
+ # here we test for the exclude list which is the other way round!
+ for p in exclude.keys():
+ if is_prefix(p,element):
+ ok=None
+ break
+
+ if not ok: continue
+
+ # now delete stuff
+ try:
+ delone(dc,element)
+ except DAV_Error, (ec,dd):
+ result[element]=ec
+
+ return result
+
+def delone(dc,uri):
+ """ delete a single object """
+ if dc.is_collection(uri):
+ dc.rmcol(uri) # should be empty
+ else:
+ dc.rm(uri)
+
+###
+### COPY
+###
+
+# helper function
+
+def copy(dc,src,dst):
+ """ only copy the element
+
+ This is just a helper method factored out from copy and
+ copytree. It will not handle the overwrite or depth header.
+
+ """
+
+ # destination should have been deleted before
+ if dc.exists(dst): raise DAV_Error, 412
+
+ # source should exist also
+ if not dc.exists(src): raise DAV_NotFound
+
+ if dc.is_collection(src):
+ dc.copycol(src,dst) # an exception will be passed thru
+ else:
+ dc.copy(src,dst) # an exception will be passed thru
+
+
+# the main functions
+
+def copyone(dc,src,dst,overwrite=None):
+ """ copy one resource to a new destination """
+
+ if overwrite and dc.exists(dst):
+ delres=deltree(dc,dst)
+ else:
+ delres={}
+
+ # if we cannot delete everything, then do not copy!
+ if delres: return delres
+
+ try:
+ copy(dc,src,dst) # pass thru exceptions
+ except DAV_Error, (ec,dd):
+ return ec
+
+def copytree(dc,src,dst,overwrite=None):
+ """ copy a tree of resources to another location
+
+ dc -- dataclass to use
+ src -- src uri from where to copy
+ dst -- dst uri
+ overwrite -- if 1 then delete dst uri before
+
+ returns dict of uri:error_code tuples from which
+ another method can create a multistatus xml element.
+
+ """
+
+
+ # first delete the destination resource
+ if overwrite and dc.exists(dst):
+ delres=deltree(dc,dst)
+ else:
+ delres={}
+
+ # if we cannot delete everything, then do not copy!
+ if delres: return delres
+
+ # get the tree we have to copy
+ tlist=create_treelist(dc,src)
+ result={}
+
+ # prepare destination URIs (get the prefix)
+ dpath=urlparse.urlparse(dst)[2]
+
+ for element in tlist:
+ problem_uris=result.keys()
+
+ # now URIs get longer and longer thus we have
+ # to test if we had a parent URI which we were not
+ # able to copy in problem_uris which is the prefix
+ # of the actual element. If it is, then we cannot
+ # copy this as well but do not generate another error.
+ ok=1
+ for p in problem_uris:
+ if is_prefix(p,element):
+ ok=None
+ break
+
+ if not ok: continue
+
+ # now create the destination URI which corresponds to
+ # the actual source URI. -> actual_dst
+ # ("subtract" the base src from the URI and prepend the
+ # dst prefix to it.)
+ esrc=replace(element,src,"")
+ actual_dst=dpath+esrc
+
+ # now copy stuff
+ try:
+ copy(dc,element,actual_dst)
+ except DAV_Error, (ec,dd):
+ result[element]=ec
+
+ return result
+
+
+
+###
+### MOVE
+###
+
+
+def moveone(dc,src,dst,overwrite=None):
+ """ move a single resource
+
+ This is done by first copying it and then deleting
+ the original.
+ """
+
+ # first copy it
+ copyone(dc,src,dst,overwrite)
+
+ # then delete it
+ dc.rm(src)
+
+def movetree(dc,src,dst,overwrite=None):
+ """ move a collection
+
+ This is done by first copying it and then deleting
+ the original.
+
+ PROBLEM: if something did not copy then we have a problem
+ when deleting as the original might get deleted!
+ """
+
+ # first copy it
+ res=copytree(dc,src,dst,overwrite)
+
+ # then delete it
+ res=deltree(dc,src,exclude=res)
+
+ return res
+
diff --git a/addons/document_webdav/DAV/davcopy.py b/addons/document_webdav/DAV/davcopy.py
new file mode 100755
index 00000000000..ec44931b107
--- /dev/null
+++ b/addons/document_webdav/DAV/davcopy.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+"""
+
+
+from xml.dom import ext
+from xml.dom.Document import Document
+
+import sys
+import string
+import urlparse
+import urllib
+from StringIO import StringIO
+
+import utils
+from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import *
+from utils import create_treelist, quote_uri, gen_estring
+
+class COPY:
+ """ copy resources and eventually create multistatus responses
+
+ This module implements the COPY class which is responsible for
+ copying resources. Usually the normal copy work is done in the
+ interface class. This class only creates error messages if error
+ occur.
+
+ """
+
+
+ def __init__(self,dataclass,src_uri,dst_uri,overwrite):
+ self.__dataclass=dataclass
+ self.__src=src_uri
+ self.__dst=dst_uri
+ self.__overwrite=overwrite
+
+
+ def single_action(self):
+ """ copy a normal resources.
+
+ We try to copy it and return the result code.
+ This is for Depth==0
+
+ """
+
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+ return dc.copyone(self.__src,self.__dst,self.__overwrite)
+
+ #return copyone(dc,self.__src,self.__dst,self.__overwrite)
+
+ def tree_action(self):
+ """ copy a tree of resources (a collection)
+
+ Here we return a multistatus xml element.
+
+ """
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+
+ result=dc.copytree(self.__src,self.__dst,self.__overwrite)
+ #result=copytree(dc,self.__src,self.__dst,self.__overwrite)
+
+ if not result: return None
+
+ ###
+ ### create the multistatus XML element
+ ### (this is also the same as in delete.py.
+ ### we might make a common method out of it)
+ ###
+
+ doc = Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ for el,ec in result.items():
+ re=doc.createElement("D:response")
+ hr=doc.createElement("D:href")
+ st=doc.createElement("D:status")
+ huri=doc.createTextNode(quote_uri(el))
+ t=doc.createTextNode(gen_estring(ec))
+ st.appendChild(t)
+ hr.appendChild(huri)
+ re.appendChild(hr)
+ re.appendChild(st)
+ ms.appendChild(re)
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
diff --git a/addons/document_webdav/DAV/davmove.py b/addons/document_webdav/DAV/davmove.py
new file mode 100755
index 00000000000..78c1e649389
--- /dev/null
+++ b/addons/document_webdav/DAV/davmove.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+"""
+
+
+import sys
+import string
+import urlparse
+import urllib
+from StringIO import StringIO
+
+import utils
+from constants import COLLECTION, OBJECT, DAV_PROPS
+from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import *
+from utils import create_treelist, quote_uri, gen_estring, make_xmlresponse
+from davcmd import moveone, movetree
+
+class MOVE:
+ """ move resources and eventually create multistatus responses
+
+ This module implements the MOVE class which is responsible for
+ moving resources.
+
+ MOVE is implemented by a COPY followed by a DELETE of the old
+ resource.
+
+ """
+
+
+ def __init__(self,dataclass,src_uri,dst_uri,overwrite):
+ self.__dataclass=dataclass
+ self.__src=src_uri
+ self.__dst=dst_uri
+ self.__overwrite=overwrite
+
+
+ def single_action(self):
+ """ move a normal resources.
+
+ We try to move it and return the result code.
+ This is for Depth==0
+
+ """
+
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+ return dc.moveone(self.__src,self.__dst,self.__overwrite)
+
+ def tree_action(self):
+ """ move a tree of resources (a collection)
+
+ Here we return a multistatus xml element.
+
+ """
+ dc=self.__dataclass
+ base=self.__src
+
+ ### some basic tests
+ # test if dest exists and overwrite is false
+ if dc.exists(self.__dst) and not self.__overwrite: raise DAV_Error, 412
+ # test if src and dst are the same
+ # (we assume that both uris are on the same server!)
+ ps=urlparse.urlparse(self.__src)[2]
+ pd=urlparse.urlparse(self.__dst)[2]
+ if ps==pd: raise DAV_Error, 403
+
+ result=dc.movetree(self.__src,self.__dst,self.__overwrite)
+ if not result: return None
+
+ # create the multistatus XML element
+ return make_xmlresponse(result)
+
diff --git a/addons/document_webdav/DAV/delete.py b/addons/document_webdav/DAV/delete.py
new file mode 100755
index 00000000000..ed017e53b6d
--- /dev/null
+++ b/addons/document_webdav/DAV/delete.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+"""
+
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+"""
+import os
+import string
+import urllib
+from StringIO import StringIO
+
+from status import STATUS_CODES
+from utils import gen_estring, quote_uri, make_xmlresponse
+from davcmd import deltree
+
+class DELETE:
+
+ def __init__(self,uri,dataclass):
+ self.__dataclass=dataclass
+ self.__uri=uri
+
+ def delcol(self):
+ """ delete a collection """
+
+ dc=self.__dataclass
+ result=dc.deltree(self.__uri)
+
+ if not len(result.items()):
+ return None # everything ok
+
+ # create the result element
+ return make_xmlresponse(result)
+
+ def delone(self):
+ """ delete a resource """
+
+ dc=self.__dataclass
+ result=dc.delone(self.__uri)
+
+ if not result: return None
+ if not len(result.items()):
+ return None # everything ok
+
+ # create the result element
+ return make_xmlresponse(result)
+
diff --git a/addons/document_webdav/DAV/errors.py b/addons/document_webdav/DAV/errors.py
new file mode 100755
index 00000000000..9c0e7667913
--- /dev/null
+++ b/addons/document_webdav/DAV/errors.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+"""
+
+ Exceptions for the DAVserver implementation
+
+"""
+
+class DAV_Error(Exception):
+ """ in general we can have the following arguments:
+
+ 1. the error code
+ 2. the error result element, e.g. a <multistatus> element
+ """
+
+ def __init__(self,*args):
+ if len(args)==1:
+ self.args=(args[0],"")
+ else:
+ self.args=args
+
+class DAV_Secret(DAV_Error):
+ """ the user is not allowed to know anything about it
+
+ returning this for a property value means to exclude it
+ from the response xml element.
+ """
+
+ def __init__(self):
+ DAV_Error.__init__(self,0)
+ pass
+
+class DAV_NotFound(DAV_Error):
+ """ a requested property was not found for a resource """
+
+ def __init__(self,*args):
+ if len(args):
+ if isinstance(args[0],list):
+ stre = "Path %s not found!"%('/'.join(args[0]))
+ else:
+ stre = args[0]
+ DAV_Error.__init__(self,404,stre)
+ else:
+ DAV_Error.__init__(self,404)
+
+ pass
+
+class DAV_Forbidden(DAV_Error):
+ """ a method on a resource is not allowed """
+
+ def __init__(self,*args):
+ if len(args):
+ DAV_Error.__init__(self,403,args[0])
+ else:
+ DAV_Error.__init__(self,403)
+ pass
+
diff --git a/addons/document_webdav/DAV/iface.py b/addons/document_webdav/DAV/iface.py
new file mode 100644
index 00000000000..8f6daa5eaa8
--- /dev/null
+++ b/addons/document_webdav/DAV/iface.py
@@ -0,0 +1,263 @@
+"""
+
+basic interface class
+
+use this for subclassing when writing your own interface
+class.
+
+"""
+
+from errors import *
+
+import time
+from string import lower
+
+class dav_interface:
+ """ interface class for implementing DAV servers """
+
+ ### defined properties (modify this but let the DAV stuff there!)
+ ### the format is namespace: [list of properties]
+
+ PROPS={"DAV:" : ('creationdate',
+ 'displayname',
+ 'getcontentlanguage',
+ 'getcontentlength',
+ 'getcontenttype',
+ 'getetag',
+ 'getlastmodified',
+ 'lockdiscovery',
+ 'resourcetype',
+ 'source',
+ 'supportedlock'),
+ "NS2" : ("p1","p2")
+ }
+
+ # here we define which methods handle which namespace
+ # the first item is the namespace URI and the second one
+ # the method prefix
+ # e.g. for DAV:getcontenttype we call dav_getcontenttype()
+ M_NS={"DAV:" : "_get_dav",
+ "NS2" : "ns2" }
+
+ def get_propnames(self,uri):
+ """ return the property names allowed for the given URI
+
+ In this method we simply return the above defined properties
+ assuming that they are valid for any resource.
+ You can override this in order to return a different set
+ of property names for each resource.
+
+ """
+ return self.PROPS
+
+ def get_prop2(self,uri,ns,pname):
+ """ return the value of a property
+ """
+ if lower(ns)=="dav:": return self.get_dav(uri,pname)
+
+ raise DAV_NotFound
+
+ 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):
+ prefix=self.M_NS[ns]
+ else:
+ print "No namespace:",ns
+ raise DAV_NotFound
+ mname=prefix+"_"+propname
+ if not hasattr(self,mname):
+ raise DAV_NotFound
+
+ try:
+ m=getattr(self,mname)
+ r=m(uri)
+ return r
+ except AttributeError, e:
+ print 'Property %s not supported' % propname
+ print "Exception:", e
+ raise DAV_NotFound
+
+ ###
+ ### DATA methods (for GET and PUT)
+ ###
+
+ def get_data(self,uri):
+ """ return the content of an object
+
+ return data or raise an exception
+
+ """
+ raise DAV_NotFound
+
+ def put(self,uri,data):
+ """ write an object to the repository
+
+ return a result code or raise an exception
+ """
+
+ raise DAV_Forbidden
+
+ ###
+ ### Methods for DAV properties
+ ###
+
+ def _get_dav_creationdate(self,uri):
+ """ return the creationdate of a resource """
+ d=self.get_creationdate(uri)
+ # format it
+ if isinstance(d, int) or isinstance(d, float):
+ d = time.localtimetime(d)
+ return time.strftime("%Y-%m-%dT%H:%M:%S%Z",d)
+
+ def _get_dav_getlastmodified(self,uri):
+ """ return the last modified date of a resource """
+ d=self.get_lastmodified(uri)
+ if isinstance(d, int) or isinstance(d, float):
+ d = time.localtime(d)
+ # format it
+ return time.asctime(d)
+
+
+ ###
+ ### OVERRIDE THESE!
+ ###
+
+ def get_creationdate(self,uri):
+ """ return the creationdate of the resource """
+ return time.time()
+
+ def get_lastmodified(self,uri):
+ """ return the last modification date of the resource """
+ return time.time()
+
+
+ ###
+ ### COPY MOVE DELETE
+ ###
+
+ ### methods for deleting a resource
+
+ def rmcol(self,uri):
+ """ delete a collection
+
+ This should not delete any children! This is automatically done
+ before by the DELETE class in DAV/delete.py
+
+ return a success code or raise an exception
+
+ """
+ raise DAV_NotFound
+
+ def rm(self,uri):
+ """ delete a single resource
+
+ return a success code or raise an exception
+
+ """
+ raise DAV_NotFound
+
+ """
+
+ COPY/MOVE HANDLER
+
+ These handler are called when a COPY or MOVE method is invoked by
+ a client. In the default implementation it works as follows:
+
+ - the davserver receives a COPY/MOVE method
+ - the davcopy or davmove module will be loaded and the corresponding
+ class will be initialized
+ - this class parses the query and decides which method of the interface class
+ to call:
+
+ copyone for a single resource to copy
+ copytree for a tree to copy (collection)
+ (the same goes for move of course).
+
+ - the interface class has now two options:
+ 1. to handle the action directly (e.g. cp or mv on filesystems)
+ 2. to let it handle via the copy/move methods in davcmd.
+
+ ad 1) The first approach can be used when we know that no error can
+ happen inside a tree or when the action can exactly tell which
+ element made which error. We have to collect these and return
+ it in a dict of the form {uri: error_code, ...}
+
+ ad 2) The copytree/movetree/... methods of davcmd.py will do the recursion
+ themselves and call for each resource the copy/move method of the
+ interface class. Thus method will then only act on a single resource.
+ (Thus a copycol on a normal unix filesystem actually only needs to do
+ an mkdir as the content will be copied by the davcmd.py function.
+ The davcmd.py method will also automatically collect all errors and
+ return the dictionary described above.
+ When you use 2) you also have to implement the copy() and copycol()
+ methods in your interface class. See the example for details.
+
+ To decide which approach is the best you have to decide if your application
+ is able to generate errors inside a tree. E.g. a function which completely
+ fails on a tree if one of the tree's childs fail is not what we need. Then
+ 2) would be your way of doing it.
+ Actually usually 2) is the better solution and should only be replaced by
+ 1) if you really need it.
+
+ The remaining question is if we should do the same for the DELETE method.
+
+ """
+
+ ### MOVE handlers
+
+ def moveone(self,src,dst,overwrite):
+ """ move one resource with Depth=0 """
+ return moveone(self,src,dst,overwrite)
+
+ def movetree(self,src,dst,overwrite):
+ """ move a collection with Depth=infinity """
+ return movetree(self,src,dst,overwrite)
+
+ ### COPY handlers
+
+ def copyone(self,src,dst,overwrite):
+ """ copy one resource with Depth=0 """
+ return copyone(self,src,dst,overwrite)
+
+ def copytree(self,src,dst,overwrite):
+ """ copy a collection with Depth=infinity """
+ return copytree(self,src,dst,overwrite)
+
+
+ ### low level copy methods (you only need these for method 2)
+ def copy(self,src,dst):
+ """ copy a resource with depth==0
+
+ You don't need to bother about overwrite or not.
+ This has been done already.
+
+ return a success code or raise an exception if something fails
+ """
+ return 201
+
+
+ def copycol(self,src,dst):
+ """ copy a resource with depth==infinity
+
+ You don't need to bother about overwrite or not.
+ This has been done already.
+
+ return a success code or raise an exception if something fails
+ """
+ return 201
+
+ ### some utility functions you need to implement
+
+ def exists(self,uri):
+ """ return 1 or None depending on if a resource exists """
+ return None # no
+
+ def is_collection(self,uri):
+ """ return 1 or None depending on if a resource is a collection """
+ return None # no
+
diff --git a/addons/document_webdav/DAV/propfind.py b/addons/document_webdav/DAV/propfind.py
new file mode 100755
index 00000000000..0fb9c229c03
--- /dev/null
+++ b/addons/document_webdav/DAV/propfind.py
@@ -0,0 +1,372 @@
+#!/usr/bin/env python
+
+"""
+ python davserver
+ Copyright (C) 1999 Christian Scholz (ruebe@aachen.heimat.de)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+
+
+from xml.dom import ext
+from xml.dom.Document import Document
+
+import sys
+import string
+import urlparse
+import urllib
+from StringIO import StringIO
+
+import utils
+from constants import COLLECTION, OBJECT, DAV_PROPS, RT_ALLPROP, RT_PROPNAME, RT_PROP
+from errors import *
+
+def utf8str(st):
+ if isinstance(st,unicode):
+ return st.encode('utf8')
+ else:
+ return str(st)
+
+class PROPFIND:
+ """ parse a propfind xml element and extract props
+
+ It will set the following instance vars:
+
+ request_class : ALLPROP | PROPNAME | PROP
+ proplist : list of properties
+ nsmap : map of namespaces
+
+ The list of properties will contain tuples of the form
+ (element name, ns_prefix, ns_uri)
+
+
+ """
+
+
+ def __init__(self,uri,dataclass,depth):
+ self.request_type=None
+ self.nsmap={}
+ self.proplist={}
+ self.default_ns=None
+ self.__dataclass=dataclass
+ self.__depth=str(depth)
+ self.__uri=uri
+ self.use_full_urls=True
+ self.__has_body=None # did we parse a body?
+
+ def read_propfind(self,xml_doc):
+ self.request_type,self.proplist,self.namespaces=utils.parse_propfind(xml_doc)
+
+ # a violation of the expected logic: client (korganizer) will ask for DAV:resourcetype
+ # but we also have to return the http://groupdav.org/:resourcetype property!
+ if self.proplist.has_key('DAV:') and 'resourcetype' in self.proplist['DAV:']:
+ if not self.proplist.has_key('http://groupdav.org/'):
+ self.proplist['http://groupdav.org/'] = []
+ self.proplist['http://groupdav.org/'].append('resourcetype')
+ if 'DAV:' in self.namespaces: #TMP
+ self.namespaces.append('http://groupdav.org/')
+
+ def createResponse(self):
+ """ create the multistatus response
+
+ This will be delegated to the specific method
+ depending on which request (allprop, propname, prop)
+ was found.
+
+ If we get a PROPNAME then we simply return the list with empty
+ values which we get from the interface class
+
+ If we get an ALLPROP we first get the list of properties and then
+ we do the same as with a PROP method.
+
+ If the uri doesn't exist, return an xml response with a 404 status
+
+ """
+
+ if not self.__dataclass.exists(self.__uri):
+ raise DAV_NotFound("Path %s doesn't exist" % self.__uri)
+
+ if self.request_type==RT_ALLPROP:
+ return self.create_allprop()
+
+ if self.request_type==RT_PROPNAME:
+ return self.create_propname()
+
+ if self.request_type==RT_PROP:
+ return self.create_prop()
+
+ # no body means ALLPROP!
+ return self.create_allprop()
+
+ def create_propname(self):
+ """ create a multistatus response for the prop names """
+
+ dc=self.__dataclass
+ # create the document generator
+ doc = Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ if self.__depth=="0":
+ pnames=dc.get_propnames(self.__uri)
+ re=self.mk_propname_response(self.__uri,pnames,doc)
+ ms.appendChild(re)
+
+ elif self.__depth=="1":
+ pnames=dc.get_propnames(self.__uri)
+ re=self.mk_propname_response(self.__uri,pnames,doc)
+ ms.appendChild(re)
+
+ for newuri in dc.get_childs(self.__uri):
+ pnames=dc.get_propnames(newuri)
+ re=self.mk_propname_response(newuri,pnames,doc)
+ ms.appendChild(re)
+ # *** depth=="infinity"
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
+ def create_allprop(self):
+ """ return a list of all properties """
+ self.proplist={}
+ self.namespaces=[]
+ for ns,plist in self.__dataclass.get_propnames(self.__uri).items():
+ self.proplist[ns]=plist
+ self.namespaces.append(ns)
+
+ return self.create_prop()
+
+ def create_prop(self):
+ """ handle a <prop> request
+
+ This will
+
+ 1. set up the <multistatus>-Framework
+
+ 2. read the property values for each URI
+ (which is dependant on the Depth header)
+ This is done by the get_propvalues() method.
+
+ 3. For each URI call the append_result() method
+ to append the actual <result>-Tag to the result
+ document.
+
+ We differ between "good" properties, which have been
+ assigned a value by the interface class and "bad"
+ properties, which resulted in an error, either 404
+ (Not Found) or 403 (Forbidden).
+
+ """
+
+
+ # create the document generator
+ doc = Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ if self.__depth=="0":
+ gp,bp=self.get_propvalues(self.__uri)
+ res=self.mk_prop_response(self.__uri,gp,bp,doc)
+ ms.appendChild(res)
+
+ elif self.__depth=="1":
+ gp,bp=self.get_propvalues(self.__uri)
+ res=self.mk_prop_response(self.__uri,gp,bp,doc)
+ ms.appendChild(res)
+
+ try:
+ for newuri in self.__dataclass.get_childs(self.__uri):
+ gp,bp=self.get_propvalues(newuri)
+ res=self.mk_prop_response(newuri,gp,bp,doc)
+ ms.appendChild(res)
+ except DAV_NotFound:
+ # If no children, never mind.
+ pass
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
+
+ def mk_propname_response(self,uri,propnames,doc):
+ """ make a new <prop> result element for a PROPNAME request
+
+ This will simply format the propnames list.
+ propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
+
+ """
+ re=doc.createElement("D:response")
+
+ # write href information
+ href=doc.createElement("D:href")
+ if self.use_full_urls:
+ huri=doc.createTextNode(uri)
+ else:
+ uparts=urlparse.urlparse(uri)
+ fileloc=uparts[2]
+ huri=doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
+ 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 mk_prop_response(self,uri,good_props,bad_props,doc):
+ """ make a new <prop> result element
+
+ We differ between the good props and the bad ones for
+ each generating an extra <propstat>-Node (for each error
+ one, that means).
+
+ """
+ re=doc.createElement("D:response")
+ # append namespaces to response
+ nsnum=0
+ for nsname in self.namespaces:
+ re.setAttribute("xmlns:ns"+str(nsnum),nsname)
+ nsnum=nsnum+1
+
+ # write href information
+ href=doc.createElement("D:href")
+ if self.use_full_urls:
+ huri=doc.createTextNode(uri)
+ else:
+ uparts=urlparse.urlparse(uri)
+ fileloc=uparts[2]
+ huri=doc.createTextNode(urllib.quote(fileloc.encode('utf8')))
+ href.appendChild(huri)
+ re.appendChild(href)
+
+ # write good properties
+ if good_props and len(good_props.items()):
+ ps=doc.createElement("D:propstat")
+
+ gp=doc.createElement("D:prop")
+ for ns in good_props.keys():
+ ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+ for p,v in good_props[ns].items():
+ pe=doc.createElement(ns_prefix+str(p))
+ if v == None:
+ pass
+ elif ns=='DAV:' and p=="resourcetype":
+ if v == 1:
+ ve=doc.createElement("D:collection")
+ pe.appendChild(ve)
+ elif isinstance(v,tuple) and v[1] == ns:
+ ve=doc.createElement(ns_prefix+v[0])
+ pe.appendChild(ve)
+ else:
+ ve=doc.createTextNode(utf8str(v))
+ pe.appendChild(ve)
+
+ gp.appendChild(pe)
+ if gp.hasChildNodes():
+ re.appendChild(ps)
+ ps.appendChild(gp)
+ s=doc.createElement("D:status")
+ t=doc.createTextNode("HTTP/1.1 200 OK")
+ s.appendChild(t)
+ ps.appendChild(s)
+ re.appendChild(ps)
+
+ # now write the errors!
+ if len(bad_props.items()):
+
+ # write a propstat for each error code
+ for ecode in bad_props.keys():
+ ps=doc.createElement("D:propstat")
+ re.appendChild(ps)
+ bp=doc.createElement("D:prop")
+ ps.appendChild(bp)
+
+ for ns in bad_props[ecode].keys():
+ ns_prefix="ns"+str(self.namespaces.index(ns))+":"
+
+ for p in bad_props[ecode][ns]:
+ pe=doc.createElement(ns_prefix+str(p))
+ bp.appendChild(pe)
+
+ s=doc.createElement("D:status")
+ t=doc.createTextNode(utils.gen_estring(ecode))
+ s.appendChild(t)
+ ps.appendChild(s)
+ re.appendChild(ps)
+
+ # return the new response element
+ return re
+
+ def get_propvalues(self,uri):
+ """ create lists of property values for an URI
+
+ We create two lists for an URI: the properties for
+ which we found a value and the ones for which we
+ only got an error, either because they haven't been
+ found or the user is not allowed to read them.
+
+ """
+ good_props={}
+ bad_props={}
+
+ for (ns,plist) in self.proplist.items():
+ good_props[ns]={}
+ bad_props={}
+ ec = 0
+ for prop in plist:
+ try:
+ ec = 0
+ r=self.__dataclass.get_prop(uri,ns,prop)
+ good_props[ns][prop]=r
+ except DAV_Error, error_code:
+ ec=error_code[0]
+
+ # ignore props with error_code if 0 (invisible)
+ if ec==0: continue
+
+ if bad_props.has_key(ec):
+ if bad_props[ec].has_key(ns):
+ bad_props[ec][ns].append(prop)
+ else:
+ bad_props[ec][ns]=[prop]
+ else:
+ bad_props[ec]={ns:[prop]}
+
+ return good_props, bad_props
+
diff --git a/addons/document_webdav/DAV/status.py b/addons/document_webdav/DAV/status.py
new file mode 100644
index 00000000000..62cb323c4fb
--- /dev/null
+++ b/addons/document_webdav/DAV/status.py
@@ -0,0 +1,31 @@
+"""
+
+status codes for DAV services
+
+
+"""
+
+
+STATUS_CODES={
+ 102: "Processing",
+ 200: "Ok",
+ 201: "Created",
+ 204: "No Content",
+ 207: "Multi-Status",
+ 201: "Created",
+ 400: "Bad Request",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 409: "Conflict",
+ 412: "Precondition failed",
+ 423: "Locked",
+ 415: "Unsupported Media Type",
+ 507: "Insufficient Storage",
+ 422: "Unprocessable Entity",
+ 423: "Locked",
+ 424: "Failed Dependency",
+ 502: "Bad Gateway",
+ 507: "Insufficient Storage",
+ 999: "Some error in Create Method please check the data of create method"
+}
diff --git a/addons/document_webdav/DAV/utils.py b/addons/document_webdav/DAV/utils.py
new file mode 100755
index 00000000000..a764a6d96c9
--- /dev/null
+++ b/addons/document_webdav/DAV/utils.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+
+"""
+
+UTILITIES
+
+- parse a propfind request body into a list of props
+
+"""
+
+from xml.dom import ext
+from xml.dom.Document import Document
+from xml.dom.ext.reader import PyExpat
+from xml.dom import Node
+from xml.dom import NodeIterator, NodeFilter
+
+from string import lower, split, atoi, joinfields
+import urlparse
+from StringIO import StringIO
+
+from constants import RT_ALLPROP, RT_PROPNAME, RT_PROP
+from status import STATUS_CODES
+
+VERSION = '0.6'
+AUTHOR = 'Simon Pamies <s.pamies@banality.de>'
+
+
+def gen_estring(ecode):
+ """ generate an error string from the given code """
+ ec=atoi(str(ecode))
+ if STATUS_CODES.has_key(ec):
+ return "HTTP/1.1 %s %s" %(ec,STATUS_CODES[ec])
+ else:
+ return "HTTP/1.1 %s" %(ec)
+
+def parse_propfind(xml_doc):
+ """ parse an propfind xml file and return a list of props
+
+ returns:
+
+ request_type -- ALLPROP, PROPNAME, PROP
+ proplist -- list of properties found
+ namespaces -- list of namespaces found
+
+ """
+ doc = PyExpat.Reader().fromString(xml_doc)
+ snit = doc.createNodeIterator(doc, NodeFilter.NodeFilter.SHOW_ELEMENT, None, None)
+
+ request_type=None
+ props={}
+ namespaces=[]
+
+ while 1:
+ curr_elem = snit.nextNode()
+ if not curr_elem: break
+ ename=fname=lower(curr_elem.nodeName)
+ if ":" in fname:
+ ename=split(fname,":")[1]
+ if ename=="prop": request_type=RT_PROP; continue
+ if ename=="propfind": continue
+ if ename=="allprop": request_type=RT_ALLPROP; continue
+ if ename=="propname": request_type=RT_PROPNAME; continue
+
+ # rest should be names of attributes
+
+ ns = curr_elem.namespaceURI
+ if props.has_key(ns):
+ props[ns].append(ename)
+ else:
+ props[ns]=[ename]
+ namespaces.append(ns)
+
+ return request_type,props,namespaces
+
+
+def create_treelist(dataclass,uri):
+ """ create a list of resources out of a tree
+
+ This function is used for the COPY, MOVE and DELETE methods
+
+ uri - the root of the subtree to flatten
+
+ It will return the flattened tree as list
+
+ """
+ queue=[uri]
+ list=[uri]
+ while len(queue):
+ element=queue[-1]
+ if dataclass.is_collection(element):
+ childs=dataclass.get_childs(element)
+ else:
+ childs=[]
+ if len(childs):
+ list=list+childs
+ # update queue
+ del queue[-1]
+ if len(childs):
+ queue=queue+childs
+ return list
+
+def is_prefix(uri1,uri2):
+ """ returns 1 of uri1 is a prefix of uri2 """
+ if uri2[:len(uri1)]==uri1:
+ return 1
+ else:
+ return None
+
+def quote_uri(uri):
+ """ quote an URL but not the protocol part """
+ import urlparse
+ import urllib
+
+ up=urlparse.urlparse(uri)
+ np=urllib.quote(up[2])
+ return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5]))
+
+def get_uriparentpath(uri):
+ """ extract the uri path and remove the last element """
+ up=urlparse.urlparse(uri)
+ return joinfields(split(up[2],"/")[:-1],"/")
+
+def get_urifilename(uri):
+ """ extract the uri path and return the last element """
+ up=urlparse.urlparse(uri)
+ return split(up[2],"/")[-1]
+
+def get_parenturi(uri):
+ """ return the parent of the given resource"""
+ up=urlparse.urlparse(uri)
+ np=joinfields(split(up[2],"/")[:-1],"/")
+ return urlparse.urlunparse((up[0],up[1],np,up[3],up[4],up[5]))
+
+### XML utilities
+
+def make_xmlresponse(result):
+ """ construct a response from a dict of uri:error_code elements """
+ doc = Document.Document(None)
+ ms=doc.createElement("D:multistatus")
+ ms.setAttribute("xmlns:D","DAV:")
+ doc.appendChild(ms)
+
+ for el,ec in result.items():
+ re=doc.createElement("D:response")
+ hr=doc.createElement("D:href")
+ st=doc.createElement("D:status")
+ huri=doc.createTextNode(quote_uri(el))
+ t=doc.createTextNode(gen_estring(ec))
+ st.appendChild(t)
+ hr.appendChild(huri)
+ re.appendChild(hr)
+ re.appendChild(st)
+ ms.appendChild(re)
+
+ sfile=StringIO()
+ ext.PrettyPrint(doc,stream=sfile)
+ s=sfile.getvalue()
+ sfile.close()
+ return s
+
diff --git a/addons/document_webdav/__init__.py b/addons/document_webdav/__init__.py
new file mode 100644
index 00000000000..08701e920d2
--- /dev/null
+++ b/addons/document_webdav/__init__.py
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import webdav_server
diff --git a/addons/document_webdav/__terp__.py b/addons/document_webdav/__terp__.py
new file mode 100644
index 00000000000..ce11511d0fa
--- /dev/null
+++ b/addons/document_webdav/__terp__.py
@@ -0,0 +1,47 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be), 2009 P. Christeas
+# All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+{
+ "name" : "WebDAV server for Document Management",
+ "version" : "2.0",
+ "author" : "Tiny",
+ "category" : "Generic Modules/Others",
+ "website": "http://www.openerp.com",
+ "description": """ With this module, the WebDAV server for the documents is activated. You can then use any compatible browser to remotely see the attachments of OpenObject.
+
+ After installation, the webDAV server can be controlled by a [webdav] section in the server's config.
+""",
+ "depends" : ["base", "document"],
+ "init_xml" : [],
+ "update_xml" : [],
+ "demo_xml" : [],
+ "active": False,
+ "installable": True
+}
diff --git a/addons/document_webdav/cache.py b/addons/document_webdav/cache.py
new file mode 100644
index 00000000000..bc75114c4ae
--- /dev/null
+++ b/addons/document_webdav/cache.py
@@ -0,0 +1,39 @@
+import time
+import heapq
+
+def memoize(maxsize):
+ """decorator to 'memoize' a function - caching its results"""
+ def decorating_function(f):
+ cache = {} # map from key to value
+ heap = [] # list of keys, in LRU heap
+ cursize = 0 # because len() is slow
+ def wrapper(*args):
+ key = repr(args)
+ # performance crap
+ _cache=cache
+ _heap=heap
+ _heappop = heapq.heappop
+ _heappush = heapq.heappush
+ _time = time.time
+ _cursize = cursize
+ _maxsize = maxsize
+ if not _cache.has_key(key):
+ if _cursize == _maxsize:
+ # pop oldest element
+ (_,oldkey) = _heappop(_heap)
+ _cache.pop(oldkey)
+ else:
+ _cursize += 1
+ # insert this element
+ _cache[key] = f(*args)
+ _heappush(_heap,(_time(),key))
+ wrapper.misses += 1
+ else:
+ wrapper.hits += 1
+ return cache[key]
+ wrapper.__doc__ = f.__doc__
+ wrapper.__name__ = f.__name__
+ wrapper.hits = wrapper.misses = 0
+ return wrapper
+ return decorating_function
+
diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py
new file mode 100644
index 00000000000..4cd4b2d6405
--- /dev/null
+++ b/addons/document_webdav/dav_fs.py
@@ -0,0 +1,653 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2004 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Fabien Pinckaers <fp@tiny.Be>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import pooler
+
+import base64
+import sys
+import os
+import time
+from string import joinfields, split, lower
+
+from service import security
+
+import netsvc
+import urlparse
+
+from DAV.constants import COLLECTION, OBJECT
+from DAV.errors import *
+from DAV.iface import *
+import urllib
+
+from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
+
+from cache import memoize
+
+CACHE_SIZE=20000
+
+#hack for urlparse: add webdav in the net protocols
+urlparse.uses_netloc.append('webdav')
+urlparse.uses_netloc.append('webdavs')
+
+class tinydav_handler(dav_interface):
+ """
+ This class models a Tiny ERP interface for the DAV server
+ """
+ PROPS={'DAV:': dav_interface.PROPS['DAV:'], }
+
+ M_NS={ "DAV:" : dav_interface.M_NS['DAV:'], }
+
+ def __init__(self, parent, verbose=False):
+ self.db_name = False
+ self.directory_id=False
+ self.db_name_list=[]
+ self.parent = parent
+ self.baseuri = parent.baseuri
+
+ def get_propnames(self,uri):
+ 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:
+ cr.close()
+ return props
+ node = self.uri2object(cr,uid,pool, uri2)
+ if node:
+ props.update(node.get_dav_props(cr))
+ cr.close()
+ return props
+
+ 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]
+ cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+ if not dbname:
+ cr.close()
+ raise DAV_NotFound
+ node = self.uri2object(cr,uid,pool, uri2)
+ if not node:
+ 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
+#
+
+ def later_get_db_from_path(self,path):
+ return "aaa"
+
+ 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)
+
+ @memoize(4)
+ def db_list(self):
+ s = netsvc.LocalService('db')
+ result = s.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()
+ return self.db_name_list
+
+ def get_childs(self,uri):
+ """ 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:
+ s = netsvc.LocalService('db')
+ return map(lambda x: self.urijoin(x), self.db_list())
+ result = []
+ node = self.uri2object(cr,uid,pool, uri2[:])
+ if not node:
+ 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)
+ else:
+ fp = None
+ for d in node.children(cr):
+ 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) )
+ cr.close()
+ return result
+
+ def uri2local(self, uri):
+ uparts=urlparse.urlparse(uri)
+ reluri=uparts[2]
+ if reluri and reluri[-1]=="/":
+ reluri=reluri[:-1]
+ return reluri
+
+ #
+ # pos: -1 to get the parent of the uri
+ #
+ def get_cr(self, uri):
+ pdb = self.parent.auth_proxy.last_auth
+ reluri = self.uri2local(uri)
+ try:
+ dbname = reluri.split('/')[2]
+ except:
+ dbname = False
+ 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)
+ user, passwd, dbn2, uid = self.parent.auth_proxy.auth_creds[pdb]
+ db,pool = pooler.get_db_and_pool(dbname)
+ cr = db.cursor()
+ uri2 = reluri.split('/')[3:]
+ return cr, uid, pool, dbname, uri2
+
+ def uri2object(self, cr,uid, pool,uri):
+ if not uid:
+ return None
+ return pool.get('document.directory').get_object(cr, uid, uri)
+
+ def get_data(self,uri):
+ 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)
+ if not node:
+ raise DAV_NotFound(uri2)
+ try:
+ datas = node.get_data(cr)
+ 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
+ except IndexError,e :
+ self.parent.log_error("GET IndexError: %s", str(e))
+ raise DAV_NotFound(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
+ finally:
+ cr.close()
+
+ @memoize(CACHE_SIZE)
+ 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)
+ if not node:
+ raise DAV_NotFound(uri2)
+ if node.type in ('collection','database'):
+ return COLLECTION
+ return OBJECT
+ finally:
+ 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
+ node = self.uri2object(cr,uid,pool, uri2)
+ if not node:
+ cr.close()
+ raise DAV_NotFound(uri2)
+ cr.close()
+ return node.displayname
+
+ @memoize(CACHE_SIZE)
+ 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:
+ cr.close()
+ return '0'
+ node = self.uri2object(cr, uid, pool, uri2)
+ if not node:
+ cr.close()
+ raise DAV_NotFound(uri2)
+ result = node.content_length or 0
+ cr.close()
+ return str(result)
+
+ @memoize(CACHE_SIZE)
+ 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()
+ return '0'
+ node = self.uri2object(cr, uid, pool, uri2)
+ if not node:
+ cr.close()
+ raise DAV_NotFound(uri2)
+ result = node.get_etag(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)
+ try:
+ if not dbname:
+ return today
+ 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
+ finally:
+ cr.close()
+
+ @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)
+ try:
+ if not dbname:
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2)
+ if not node:
+ raise DAV_NotFound(uri2)
+ if node.create_date:
+ result = time.strptime(node.create_date,'%Y-%m-%d %H:%M:%S')
+ else:
+ result = time.gmtime()
+ return result
+ finally:
+ 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)
+ try:
+ if not dbname:
+ return 'httpd/unix-directory'
+ node = self.uri2object(cr,uid,pool, uri2)
+ if not node:
+ raise DAV_NotFound(uri2)
+
+ result = 'application/octet-stream'
+ #if node.type=='collection':
+ #result ='httpd/unix-directory'
+ #else:
+ result = node.mimetype
+ return result
+ #raise DAV_NotFound, 'Could not find %s' % path
+ finally:
+ cr.close()
+
+ def mkcol(self,uri):
+ """ create a new collection """
+ self.parent.log_message('MKCOL: %s' % uri)
+ if uri[-1]=='/':uri=uri[:-1]
+ parent='/'.join(uri.split('/')[:-1])
+ if not parent.startswith(self.baseuri):
+ parent=self.baseuri + ''.join(parent[1:])
+ if not uri.startswith(self.baseuri):
+ uri=self.baseuri + ''.join(uri[1:])
+
+
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ if not dbname:
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2[:-1])
+ object2=node and node.object2 or False
+ object=node and node.object or False
+
+ objname = uri2[-1]
+ if not object:
+ pool.get('document.directory').create(cr, uid, {
+ 'name': objname,
+ 'parent_id': False,
+ 'ressource_type_id': False,
+ 'ressource_id': False
+ })
+ else:
+ pool.get('document.directory').create(cr, uid, {
+ 'name': objname,
+ 'parent_id': object.id,
+ 'ressource_type_id': object.ressource_type_id.id,
+ 'ressource_id': object2 and object2.id or False
+ })
+
+ 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'%( unicode(uri,'utf8'), len(data), content_type))
+ parent='/'.join(uri.split('/')[:-1])
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ if not dbname:
+ raise DAV_Forbidden
+ try:
+ node = self.uri2object(cr,uid,pool, uri2[:])
+ except:
+ node = False
+ objname = uri2[-1]
+ ext = objname.find('.') >0 and objname.split('.')[1] or False
+
+ if not node:
+ dir_node = self.uri2object(cr,uid,pool, uri2[:-1])
+ if not dir_node:
+ raise DAV_NotFound('Parent folder not found')
+ 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
+ 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
+
+ cr.commit()
+
+ return 201
+
+ def rmcol(self,uri):
+ """ delete a collection """
+ if uri[-1]=='/':uri=uri[:-1]
+
+ cr, uid, pool, dbname, uri2 = self.get_cr(uri)
+ if True or not dbname: # *-*
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2)
+ object2=node and node.object2 or False
+ object=node and node.object or False
+ if object._table_name=='document.directory':
+ if object.child_ids:
+ raise DAV_Forbidden # forbidden
+ if object.file_ids:
+ raise DAV_Forbidden # forbidden
+ res = pool.get('document.directory').unlink(cr, uid, [object.id])
+
+ cr.commit()
+ cr.close()
+ return 204
+
+ def rm(self,uri):
+ if uri[-1]=='/':uri=uri[:-1]
+
+ object=False
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ #if not dbname:
+ if True:
+ raise DAV_Error, 409
+ node = self.uri2object(cr,uid,pool, uri2)
+ object2=node and node.object2 or False
+ object=node and node.object or False
+ if not object:
+ raise DAV_NotFound
+
+ self.parent.log_message(' rm %s "%s"'%(object._table_name,uri))
+ if object._table_name=='ir.attachment':
+ res = pool.get('ir.attachment').unlink(cr, uid, [object.id])
+ else:
+ raise DAV_Forbidden # forbidden
+ parent='/'.join(uri.split('/')[:-1])
+ cr.commit()
+ cr.close()
+ return 204
+
+ ### DELETE handlers (examples)
+ ### (we use the predefined methods in davcmd instead of doing
+ ### a rm directly
+ ###
+
+ def delone(self,uri):
+ """ delete a single resource
+
+ You have to return a result dict of the form
+ uri:error_code
+ or None if everything's ok
+
+ """
+ if uri[-1]=='/':uri=uri[:-1]
+ res=delone(self,uri)
+ parent='/'.join(uri.split('/')[:-1])
+ return res
+
+ def deltree(self,uri):
+ """ delete a collection
+
+ You have to return a result dict of the form
+ uri:error_code
+ or None if everything's ok
+ """
+ if uri[-1]=='/':uri=uri[:-1]
+ res=deltree(self,uri)
+ parent='/'.join(uri.split('/')[:-1])
+ return res
+
+
+ ###
+ ### MOVE handlers (examples)
+ ###
+
+ def moveone(self,src,dst,overwrite):
+ """ move one resource with Depth=0
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -f '%s'" %dst)
+ if r: return 412
+ r=os.system("mv '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node.
+ """
+ res=moveone(self,src,dst,overwrite)
+ return res
+
+ def movetree(self,src,dst,overwrite):
+ """ move a collection with Depth=infinity
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -rf '%s'" %dst)
+ if r: return 412
+ r=os.system("mv '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node"""
+
+ res=movetree(self,src,dst,overwrite)
+ return res
+
+ ###
+ ### COPY handlers
+ ###
+
+ def copyone(self,src,dst,overwrite):
+ """ copy one resource with Depth=0
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -f '%s'" %dst)
+ if r: return 412
+ r=os.system("cp '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node.
+ """
+ res=copyone(self,src,dst,overwrite)
+ return res
+
+ def copytree(self,src,dst,overwrite):
+ """ copy a collection with Depth=infinity
+
+ an alternative implementation would be
+
+ result_code=201
+ if overwrite:
+ result_code=204
+ r=os.system("rm -rf '%s'" %dst)
+ if r: return 412
+ r=os.system("cp -r '%s' '%s'" %(src,dst))
+ if r: return 412
+ return result_code
+
+ (untested!). This would not use the davcmd functions
+ and thus can only detect errors directly on the root node"""
+ res=copytree(self,src,dst,overwrite)
+ return res
+
+ ###
+ ### copy methods.
+ ### This methods actually copy something. low-level
+ ### They are called by the davcmd utility functions
+ ### copytree and copyone (not the above!)
+ ### Look in davcmd.py for further details.
+ ###
+
+ def copy(self,src,dst):
+ src=urllib.unquote(src)
+ dst=urllib.unquote(dst)
+ ct = self._get_dav_getcontenttype(src)
+ data = self.get_data(src)
+ self.put(dst,data,ct)
+ return 201
+
+ def copycol(self,src,dst):
+ """ copy a collection.
+
+ As this is not recursive (the davserver recurses itself)
+ we will only create a new directory here. For some more
+ advanced systems we might also have to copy properties from
+ the source to the destination.
+ """
+ print " copy a collection."
+ return self.mkcol(dst)
+
+
+ def exists(self,uri):
+ """ test if a resource exists """
+ result = False
+ cr, uid, pool,dbname, uri2 = self.get_cr(uri)
+ if not dbname:
+ return True
+ try:
+ node = self.uri2object(cr,uid,pool, uri2)
+ if node:
+ result = True
+ except:
+ pass
+ cr.close()
+ return result
+
+ @memoize(CACHE_SIZE)
+ def is_collection(self,uri):
+ """ test if the given uri is a collection """
+ return self._get_dav_resourcetype(uri)==COLLECTION
diff --git a/addons/document_webdav/i18n/document_webdav.pot b/addons/document_webdav/i18n/document_webdav.pot
new file mode 100644
index 00000000000..e5ebe893fc7
--- /dev/null
+++ b/addons/document_webdav/i18n/document_webdav.pot
@@ -0,0 +1,26 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * document_webdav_old
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 5.0.0\n"
+"Report-Msgid-Bugs-To: support@openerp.com\n"
+"POT-Creation-Date: 2009-05-30 15:14:08+0000\n"
+"PO-Revision-Date: 2009-05-30 15:14:08+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: document_webdav_old
+#: model:ir.module.module,description:document_webdav_old.module_meta_information
+msgid "This is a complete document management system:\n"
+" * WebDav Interface\n"
+" * User Authentification\n"
+" * Document Indexation\n"
+""
+msgstr ""
+
diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py
new file mode 100644
index 00000000000..f04315e13c9
--- /dev/null
+++ b/addons/document_webdav/webdav_server.py
@@ -0,0 +1,80 @@
+# -*- encoding: utf-8 -*-
+
+#
+# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
+#
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+###############################################################################
+
+
+import netsvc
+from dav_fs import tinydav_handler
+from tools.config import config
+from DAV.WebDAVServer import DAVRequestHandler
+from service.websrv_lib import HTTPDir,FixSendError
+
+class DAVHandler(FixSendError,DAVRequestHandler):
+ verbose = False
+
+ def get_userinfo(self,user,pw):
+ print "get_userinfo"
+ return False
+ def _log(self, message):
+ netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message)
+
+ def handle(self):
+ pass
+
+ def finish(self):
+ pass
+
+ def setup(self):
+ davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
+ self.baseuri = "http://%s:%d%s"% (self.server.server_name,self.server.server_port,davpath)
+ self.IFACE_CLASS = tinydav_handler(self)
+ pass
+
+ def log_message(self, format, *args):
+ netsvc.Logger().notifyChannel('webdav',netsvc.LOG_DEBUG_RPC,format % args)
+
+ def log_error(self, format, *args):
+ netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_WARNING,format % args)
+
+
+try:
+ from service.http_server import reg_http_service,OpenERPAuthProvider
+ if (config.get_misc('webdav','enable',False)):
+ davpath = '/'+config.get_misc('webdav','vdir','webdav')+'/'
+ handler = DAVHandler
+ handler.verbose = config.get_misc('webdav','verbose',True)
+ handler.debug = config.get_misc('webdav','debug',True)
+ reg_http_service(HTTPDir(davpath,DAVHandler,OpenERPAuthProvider()))
+ netsvc.Logger().notifyChannel('webdav',netsvc.LOG_INFO,"WebDAV service registered at path: %s/ "% davpath)
+except Exception, e:
+ logger = netsvc.Logger()
+ logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e)
+
+#eof
+
+
+