Files
odoo_source/addons/document_webdav/webdav.py
T
P. Christeas 6b2c832f5f doc webdav: Implement locking support
This replaces the pseydo-locking of python-webdav library with a real
db-based locking. Locks are stored as DAV properties, which will
effectively also be listed in the PROPFIND response of the nodes.

With locking in place, Office suites can collaborate on documents online.

bzr revid: p_christ@hol.gr-20101207134041-8negkvxrbscv7fs7
2010-12-07 15:40:41 +02:00

319 lines
10 KiB
Python

# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# Copyright (c) 1999 Christian Scholz (ruebe@aachen.heimat.de)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import xml.dom.minidom
domimpl = xml.dom.minidom.getDOMImplementation()
from xml.dom.minicompat import StringTypes
import urlparse
import urllib
from osv import osv
from tools.translate import _
try:
from DAV import utils
from DAV.propfind import PROPFIND
from DAV.report import REPORT
except ImportError:
raise osv.except_osv(_('PyWebDAV Import Error!'), _('Please install PyWebDAV from http://code.google.com/p/pywebdav/downloads/detail?name=PyWebDAV-0.9.4.tar.gz&can=2&q=/'))
import tools
class Text2(xml.dom.minidom.Text):
def writexml(self, writer, indent="", addindent="", newl=""):
data = "%s%s%s" % (indent, self.data, newl)
data = data.replace("&", "&amp;").replace("<", "&lt;")
data = data.replace(">", "&gt;")
writer.write(data)
def createText2Node(doc, data):
if not isinstance(data, StringTypes):
raise TypeError, "node contents must be a string"
t = Text2()
t.data = data
t.ownerDocument = doc
return t
super_mk_prop_response = PROPFIND.mk_prop_response
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
namespaces = self.namespaces[:]
if 'DAV:' in namespaces:
namespaces.remove('DAV:')
for nsname in namespaces:
re.setAttribute("xmlns:ns"+str(nsnum),nsname)
nsnum=nsnum+1
def _prop_child(xnode, ns, prop, value):
"""Append a property xml node to xnode, with <prop>value</prop>
And a little smarter than that, it will consider namespace and
also allow nested properties etc.
:param ns the namespace of the <prop/> node
:param prop the name of the property
:param value the value. Can be:
string: text node
tuple ('elem', 'ns') for empty sub-node <ns:elem />
tuple ('elem', 'ns', sub-elems) for sub-node with elements
tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with
optional elements and attributes
list, of above tuples
"""
if ns == 'DAV:':
ns_prefix = 'D:'
else:
ns_prefix="ns"+str(namespaces.index(ns))+":"
pe=doc.createElement(ns_prefix+str(prop))
if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
pe.appendChild(value)
else:
if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
# hack, to go..
if value == 1:
ve=doc.createElement("D:collection")
pe.appendChild(ve)
else:
_prop_elem_child(pe, ns, value, ns_prefix)
xnode.appendChild(pe)
def _prop_elem_child(pnode, pns, v, pns_prefix):
if isinstance(v, list):
for vit in v:
_prop_elem_child(pnode, pns, vit, pns_prefix)
elif isinstance(v,tuple):
need_ns = False
if v[1] == pns:
ns_prefix = pns_prefix
elif v[1] == 'DAV:':
ns_prefix = 'D:'
elif v[1] in namespaces:
ns_prefix="ns"+str(namespaces.index(v[1]))+":"
else:
ns_prefix="ns"+str(nsnum)+":"
need_ns = True
ve=doc.createElement(ns_prefix+v[0])
if need_ns:
ve.setAttribute("xmlns:ns"+str(nsnum), v[1])
if len(v) > 2 and v[2] is not None:
if isinstance(v[2], (list, tuple)):
# support nested elements like:
# ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
_prop_elem_child(ve, v[1], v[2], ns_prefix)
else:
vt=createText2Node(doc,tools.ustr(v[2]))
ve.appendChild(vt)
if len(v) > 3 and v[3]:
assert isinstance(v[3], dict)
for ak, av in v[3].items():
ve.setAttribute(ak, av)
pnode.appendChild(ve)
else:
ve=createText2Node(doc, tools.ustr(v))
pnode.appendChild(ve)
# write href information
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
if isinstance(fileloc, unicode):
fileloc = fileloc.encode('utf-8')
href=doc.createElement("D:href")
davpath = self._dataclass.parent.get_davpath()
if uparts[0] and uparts[1]:
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
else:
# When the request has been relative, we don't have enough data to
# reply with absolute url here.
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
huri=doc.createTextNode(hurl)
href.appendChild(huri)
re.appendChild(href)
# write good properties
ps=doc.createElement("D:propstat")
if good_props:
re.appendChild(ps)
s=doc.createElement("D:status")
t=doc.createTextNode("HTTP/1.1 200 OK")
s.appendChild(t)
ps.appendChild(s)
gp=doc.createElement("D:prop")
for ns in good_props.keys():
if ns == 'DAV:':
ns_prefix = 'D:'
else:
ns_prefix="ns"+str(namespaces.index(ns))+":"
for p,v in good_props[ns].items():
if v is None:
continue
_prop_child(gp, ns, p, v)
ps.appendChild(gp)
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)
s=doc.createElement("D:status")
t=doc.createTextNode(utils.gen_estring(ecode))
s.appendChild(t)
ps.appendChild(s)
bp=doc.createElement("D:prop")
ps.appendChild(bp)
for ns in bad_props[ecode].keys():
if ns == 'DAV:':
ns_prefix='D:'
else:
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)
re.appendChild(ps)
# return the new response element
return re
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
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
if isinstance(fileloc, unicode):
fileloc = fileloc.encode('utf-8')
href=doc.createElement("D:href")
davpath = self._dataclass.parent.get_davpath()
if uparts[0] and uparts[1]:
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
else:
# When the request has been relative, we don't have enough data to
# reply with absolute url here.
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
huri=doc.createTextNode(hurl)
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")
if ns == 'DAV':
nsp = 'D'
else:
nsp="ns"+str(nsnum)
ps.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
PROPFIND.mk_prop_response = mk_prop_response
PROPFIND.mk_propname_response = mk_propname_response
def mk_lock_response(self, uri, props):
""" Prepare the data response to a DAV LOCK command
This function is here, merely to be in the same file as the
ones above, that have similar code.
"""
doc = domimpl.createDocument('DAV:', "D:prop", None)
ms = doc.documentElement
ms.setAttribute("xmlns:D", "DAV:")
# ms.tagName = 'D:multistatus'
namespaces = []
nsnum = 0
propgen = Prop2xml(doc, namespaces, nsnum)
# write href information
uparts=urlparse.urlparse(uri)
fileloc=uparts[2]
if isinstance(fileloc, unicode):
fileloc = fileloc.encode('utf-8')
davpath = self.parent.get_davpath()
if uparts[0] and uparts[1]:
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
else:
# When the request has been relative, we don't have enough data to
# reply with absolute url here.
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
props.append( ('lockroot', 'DAV:', ('href', 'DAV:', (hurl))))
pld = doc.createElement('D:lockdiscovery')
ms.appendChild(pld)
propgen._prop_child(pld, 'DAV:', 'activelock', props)
return doc.toxml(encoding="utf-8")
super_create_prop = REPORT.create_prop
def create_prop(self):
try:
if (self.filter is not None) and self._depth == "0":
hrefs = self.filter.getElementsByTagNameNS('DAV:', 'href')
if hrefs:
self._depth = "1"
except Exception:
pass
return super_create_prop(self)
REPORT.create_prop = create_prop
#eof