diff options
| author | nch@tinyerp.com <> | 2009-12-08 09:45:17 +0530 |
|---|---|---|
| committer | nch@tinyerp.com <> | 2009-12-08 09:45:17 +0530 |
| commit | 3f9ed76869c572b42641e4e22e5435bb10fa5e46 (patch) | |
| tree | d957a2fe852c383a7c22148ccc15c20aff028d67 /addons/document_webdav | |
| parent | d7a580e0bf1d031f3daa0bf657055ec50e307cfd (diff) | |
| parent | b1eb90aa06b4c74dd623f50f716b936a3b33a09a (diff) | |
[MERGE]
bzr revid: nch@tinyerp.com-20091208041517-n8932hvnsprkcme8
Diffstat (limited to 'addons/document_webdav')
| -rw-r--r-- | addons/document_webdav/DAV/BufferingHTTPServer.py | 99 | ||||
| -rw-r--r-- | addons/document_webdav/DAV/WebDAVServer.py | 379 | ||||
| -rw-r--r-- | addons/document_webdav/DAV/__init__.py | 20 | ||||
| -rw-r--r-- | addons/document_webdav/DAV/constants.py | 18 | ||||
| -rw-r--r-- | addons/document_webdav/DAV/davcmd.py | 218 | ||||
| -rwxr-xr-x | addons/document_webdav/DAV/davcopy.py | 133 | ||||
| -rwxr-xr-x | addons/document_webdav/DAV/davmove.py | 102 | ||||
| -rwxr-xr-x | addons/document_webdav/DAV/delete.py | 63 | ||||
| -rwxr-xr-x | addons/document_webdav/DAV/errors.py | 56 | ||||
| -rw-r--r-- | addons/document_webdav/DAV/iface.py | 263 | ||||
| -rwxr-xr-x | addons/document_webdav/DAV/propfind.py | 372 | ||||
| -rw-r--r-- | addons/document_webdav/DAV/status.py | 31 | ||||
| -rwxr-xr-x | addons/document_webdav/DAV/utils.py | 160 | ||||
| -rw-r--r-- | addons/document_webdav/__init__.py | 29 | ||||
| -rw-r--r-- | addons/document_webdav/__terp__.py | 47 | ||||
| -rw-r--r-- | addons/document_webdav/cache.py | 39 | ||||
| -rw-r--r-- | addons/document_webdav/dav_fs.py | 653 | ||||
| -rw-r--r-- | addons/document_webdav/i18n/document_webdav.pot | 26 | ||||
| -rw-r--r-- | addons/document_webdav/webdav_server.py | 80 |
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 + + + |
