[MERGE] lp881356
[odoo/odoo.git] / addons / document_webdav / webdav.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #    Copyright (c) 1999 Christian Scholz (ruebe@aachen.heimat.de)
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as
10 #    published by the Free Software Foundation, either version 3 of the
11 #    License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import xml.dom.minidom
24 domimpl = xml.dom.minidom.getDOMImplementation()
25 from xml.dom.minicompat import StringTypes
26
27 import urlparse
28 import urllib
29 from osv import osv
30 from tools.translate import _
31
32 try:
33     from DAV import utils
34     from DAV.propfind import PROPFIND
35     from DAV.report import REPORT
36 except ImportError:
37     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=/'))
38
39 import tools
40
41 class Text2(xml.dom.minidom.Text):
42     def writexml(self, writer, indent="", addindent="", newl=""):
43         data = "%s%s%s" % (indent, self.data, newl)
44         data = data.replace("&", "&amp;").replace("<", "&lt;")
45         data = data.replace(">", "&gt;")
46         writer.write(data)
47
48 class Prop2xml(object):
49     """ A helper class to convert property structs to DAV:XML
50     
51         Written to generalize the use of _prop_child(), a class is 
52         needed to hold some persistent data accross the recursions 
53         of _prop_elem_child().
54     """
55     
56     def __init__(self, doc, namespaces, nsnum):
57         """ Init the structure
58         @param doc the xml doc element
59         @param namespaces a dict of namespaces
60         @param nsnum the next namespace number to define
61         """
62         self.doc = doc
63         self.namespaces = namespaces
64         self.nsnum = nsnum
65
66     def createText2Node(self, data):
67         if not isinstance(data, StringTypes):
68             raise TypeError, "node contents must be a string"
69         t = Text2()
70         t.data = data
71         t.ownerDocument = self.doc
72         return t
73
74     def _prop_child(self, xnode, ns, prop, value):
75         """Append a property xml node to xnode, with <prop>value</prop>
76
77            And a little smarter than that, it will consider namespace and
78            also allow nested properties etc.
79
80            :param ns the namespace of the <prop/> node
81            :param prop the name of the property
82            :param value the value. Can be:
83                     string: text node
84                     tuple ('elem', 'ns') for empty sub-node <ns:elem />
85                     tuple ('elem', 'ns', sub-elems) for sub-node with elements
86                     tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with 
87                             optional elements and attributes
88                     list, of above tuples
89         """
90         if ns == 'DAV:':
91             ns_prefix = 'D:'
92         else:
93             ns_prefix="ns"+str(self.namespaces.index(ns))+":"
94
95         pe = self.doc.createElement(ns_prefix+str(prop))
96         if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
97             pe.appendChild(value)
98         else:
99             if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
100                 # hack, to go..
101                 if value == 1:
102                     ve = self.doc.createElement("D:collection")
103                     pe.appendChild(ve)
104             else:
105                 self._prop_elem_child(pe, ns, value, ns_prefix)
106
107             xnode.appendChild(pe)
108
109     def _prop_elem_child(self, pnode, pns, v, pns_prefix):
110
111         if isinstance(v, list):
112             for vit in v:
113                 self._prop_elem_child(pnode, pns, vit, pns_prefix)
114         elif isinstance(v,tuple):
115             need_ns = False
116             if v[1] == pns:
117                 ns_prefix = pns_prefix
118             elif v[1] == 'DAV:':
119                 ns_prefix = 'D:'
120             elif v[1] in self.namespaces:
121                 ns_prefix="ns"+str(self.namespaces.index(v[1]))+":"
122             else:
123                 ns_prefix="ns"+str(self.nsnum)+":"
124                 need_ns = True
125
126             ve = self.doc.createElement(ns_prefix+v[0])
127             if need_ns:
128                 ve.setAttribute("xmlns:ns"+str(self.nsnum), v[1])
129             if len(v) > 2 and v[2] is not None:
130                 if isinstance(v[2], (list, tuple)):
131                     # support nested elements like:
132                     # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
133                     self._prop_elem_child(ve, v[1], v[2], ns_prefix)
134                 else:
135                     vt = self.createText2Node(tools.ustr(v[2]))
136                     ve.appendChild(vt)
137             if len(v) > 3 and v[3]:
138                 assert isinstance(v[3], dict)
139                 for ak, av in v[3].items():
140                     ve.setAttribute(ak, av)
141             pnode.appendChild(ve)
142         else:
143             ve = self.createText2Node(tools.ustr(v))
144             pnode.appendChild(ve)
145
146
147 super_mk_prop_response = PROPFIND.mk_prop_response
148 def mk_prop_response(self, uri, good_props, bad_props, doc):
149     """ make a new <prop> result element
150
151     We differ between the good props and the bad ones for
152     each generating an extra <propstat>-Node (for each error
153     one, that means).
154
155     """
156     re=doc.createElement("D:response")
157     # append namespaces to response
158     nsnum=0
159     namespaces = self.namespaces[:]
160     if 'DAV:' in namespaces:
161         namespaces.remove('DAV:')
162     for nsname in namespaces:
163         re.setAttribute("xmlns:ns"+str(nsnum),nsname)
164         nsnum=nsnum+1
165
166     propgen = Prop2xml(doc, namespaces, nsnum)
167     # write href information
168     uparts=urlparse.urlparse(uri)
169     fileloc=uparts[2]
170     if uparts[3]:
171         fileloc += ';' + uparts[3]
172     if isinstance(fileloc, unicode):
173         fileloc = fileloc.encode('utf-8')
174     href=doc.createElement("D:href")
175     davpath = self._dataclass.parent.get_davpath()
176     if uparts[0] and uparts[1]:
177         hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
178     else:
179         # When the request has been relative, we don't have enough data to
180         # reply with absolute url here.
181         hurl = '%s%s' % (davpath, urllib.quote(fileloc))
182     huri=doc.createTextNode(hurl)
183     href.appendChild(huri)
184     re.appendChild(href)
185
186     # write good properties
187     ps=doc.createElement("D:propstat")
188     if good_props:
189         re.appendChild(ps)
190     s=doc.createElement("D:status")
191     t=doc.createTextNode("HTTP/1.1 200 OK")
192     s.appendChild(t)
193     ps.appendChild(s)
194
195     gp=doc.createElement("D:prop")
196     for ns in good_props.keys():
197         if ns == 'DAV:':
198             ns_prefix = 'D:'
199         else:
200             ns_prefix="ns"+str(namespaces.index(ns))+":"
201         for p,v in good_props[ns].items():
202             if v is None:
203                 continue
204             propgen._prop_child(gp, ns, p, v)
205
206     ps.appendChild(gp)
207     re.appendChild(ps)
208
209     # now write the errors!
210     if len(bad_props.items()):
211
212         # write a propstat for each error code
213         for ecode in bad_props.keys():
214             ps=doc.createElement("D:propstat")
215             re.appendChild(ps)
216             s=doc.createElement("D:status")
217             t=doc.createTextNode(utils.gen_estring(ecode))
218             s.appendChild(t)
219             ps.appendChild(s)
220             bp=doc.createElement("D:prop")
221             ps.appendChild(bp)
222
223             for ns in bad_props[ecode].keys():
224                 if ns == 'DAV:':
225                     ns_prefix='D:'
226                 else:
227                     ns_prefix="ns"+str(self.namespaces.index(ns))+":"
228
229             for p in bad_props[ecode][ns]:
230                 pe=doc.createElement(ns_prefix+str(p))
231                 bp.appendChild(pe)
232
233             re.appendChild(ps)
234
235     # return the new response element
236     return re
237
238
239 def mk_propname_response(self,uri,propnames,doc):
240     """ make a new <prop> result element for a PROPNAME request
241
242     This will simply format the propnames list.
243     propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
244
245     """
246     re=doc.createElement("D:response")
247
248     # write href information
249     uparts=urlparse.urlparse(uri)
250     fileloc=uparts[2]
251     if uparts[3]:
252         fileloc += ';' + uparts[3]
253     if isinstance(fileloc, unicode):
254         fileloc = fileloc.encode('utf-8')
255     href=doc.createElement("D:href")
256     davpath = self._dataclass.parent.get_davpath()
257     if uparts[0] and uparts[1]:
258         hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
259     else:
260         # When the request has been relative, we don't have enough data to
261         # reply with absolute url here.
262         hurl = '%s%s' % (davpath, urllib.quote(fileloc))
263     huri=doc.createTextNode(hurl)
264     href.appendChild(huri)
265     re.appendChild(href)
266
267     ps=doc.createElement("D:propstat")
268     nsnum=0
269
270     for ns,plist in propnames.items():
271         # write prop element
272         pr=doc.createElement("D:prop")
273         if ns == 'DAV':
274             nsp = 'D'
275         else:
276             nsp="ns"+str(nsnum)
277             ps.setAttribute("xmlns:"+nsp,ns)
278             nsnum=nsnum+1
279
280         # write propertynames
281         for p in plist:
282             pe=doc.createElement(nsp+":"+p)
283             pr.appendChild(pe)
284
285         ps.appendChild(pr)
286
287     re.appendChild(ps)
288
289     return re
290
291 PROPFIND.mk_prop_response = mk_prop_response
292 PROPFIND.mk_propname_response = mk_propname_response
293
294 def mk_lock_response(self, uri, props):
295     """ Prepare the data response to a DAV LOCK command
296     
297     This function is here, merely to be in the same file as the
298     ones above, that have similar code.
299     """
300     doc = domimpl.createDocument('DAV:', "D:prop", None)
301     ms = doc.documentElement
302     ms.setAttribute("xmlns:D", "DAV:")
303     # ms.tagName = 'D:multistatus'
304     namespaces = []
305     nsnum = 0
306     propgen = Prop2xml(doc, namespaces, nsnum)
307     # write href information
308     uparts=urlparse.urlparse(uri)
309     fileloc=uparts[2]
310     if uparts[3]:
311         fileloc += ';' + uparts[3]
312     if isinstance(fileloc, unicode):
313         fileloc = fileloc.encode('utf-8')
314     davpath = self.parent.get_davpath()
315     if uparts[0] and uparts[1]:
316         hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
317     else:
318         # When the request has been relative, we don't have enough data to
319         # reply with absolute url here.
320         hurl = '%s%s' % (davpath, urllib.quote(fileloc))
321         
322     props.append( ('lockroot', 'DAV:', ('href', 'DAV:', (hurl))))
323     pld = doc.createElement('D:lockdiscovery')
324     ms.appendChild(pld)
325     propgen._prop_child(pld, 'DAV:', 'activelock', props)
326
327     return doc.toxml(encoding="utf-8")
328
329 super_create_prop = REPORT.create_prop
330
331 def create_prop(self):
332     try:
333         if (self.filter is not None) and self._depth == "0":
334             hrefs = self.filter.getElementsByTagNameNS('DAV:', 'href')
335             if hrefs:
336                 self._depth = "1"
337     except Exception:
338         pass
339     return super_create_prop(self)
340
341 REPORT.create_prop = create_prop
342
343 #eof
344
345 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: