summaryrefslogtreecommitdiff
path: root/addons/document_webdav/DAV
diff options
context:
space:
mode:
authorFabien Pinckaers <fp@tinyerp.com>2009-12-01 09:26:50 +0100
committerFabien Pinckaers <fp@tinyerp.com>2009-12-01 09:26:50 +0100
commit90336e2e4e16de500c83db7aef8cd6a2a6678e00 (patch)
treebfa4f205a17ce2f2c61052cfaa8c9c683b72bddd /addons/document_webdav/DAV
parent86794c725285c43083b1c807b92b250c34d16f24 (diff)
parent96737d284fef562e8e6b096592d284aa6db8fa4a (diff)
merge
bzr revid: fp@tinyerp.com-20091201082650-e6n201rwss3u0etn
Diffstat (limited to 'addons/document_webdav/DAV')
-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
13 files changed, 1914 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
+