Doc Webdav: reply with relative URIs when the request was relative.
[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
31 try:
32     from DAV import utils
33     from DAV.propfind import PROPFIND
34     from DAV.report import REPORT
35 except ImportError:
36     raise osv.except_osv('PyWebDAV Import Error!','Please install PyWebDAV \
37 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 def createText2Node(doc, data):
49     if not isinstance(data, StringTypes):
50         raise TypeError, "node contents must be a string"
51     t = Text2()
52     t.data = data
53     t.ownerDocument = doc
54     return t
55
56
57 super_mk_prop_response = PROPFIND.mk_prop_response
58 def mk_prop_response(self, uri, good_props, bad_props, doc):
59     """ make a new <prop> result element
60
61     We differ between the good props and the bad ones for
62     each generating an extra <propstat>-Node (for each error
63     one, that means).
64
65     """
66     re=doc.createElement("D:response")
67     # append namespaces to response
68     nsnum=0
69     namespaces = self.namespaces[:]
70     if 'DAV:' in namespaces:
71         namespaces.remove('DAV:')
72     for nsname in namespaces:
73         re.setAttribute("xmlns:ns"+str(nsnum),nsname)
74         nsnum=nsnum+1
75
76     def _prop_child(xnode, ns, prop, value):
77         """Append a property xml node to xnode, with <prop>value</prop>
78
79            And a little smarter than that, it will consider namespace and
80            also allow nested properties etc.
81
82            :param ns the namespace of the <prop/> node
83            :param prop the name of the property
84            :param value the value. Can be:
85                     string: text node
86                     tuple ('elem', 'ns') for empty sub-node <ns:elem />
87                     tuple ('elem', 'ns', sub-elems) for sub-node with elements
88                     tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with 
89                             optional elements and attributes
90                     list, of above tuples
91         """
92         if ns == 'DAV:':
93             ns_prefix = 'D:'
94         else:
95             ns_prefix="ns"+str(namespaces.index(ns))+":"
96
97         pe=doc.createElement(ns_prefix+str(prop))
98         if hasattr(value, '__class__') and value.__class__.__name__ == 'Element':
99             pe.appendChild(value)
100         else:
101             if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int):
102                 # hack, to go..
103                 if value == 1:
104                     ve=doc.createElement("D:collection")
105                     pe.appendChild(ve)
106             else:
107                 _prop_elem_child(pe, ns, value, ns_prefix)
108
109             xnode.appendChild(pe)
110
111     def _prop_elem_child(pnode, pns, v, pns_prefix):
112
113         if isinstance(v, list):
114             for vit in v:
115                 _prop_elem_child(pnode, pns, vit, pns_prefix)
116         elif isinstance(v,tuple):
117             need_ns = False
118             if v[1] == pns:
119                 ns_prefix = pns_prefix
120             elif v[1] == 'DAV:':
121                 ns_prefix = 'D:'
122             elif v[1] in namespaces:
123                 ns_prefix="ns"+str(namespaces.index(v[1]))+":"
124             else:
125                 ns_prefix="ns"+str(nsnum)+":"
126                 need_ns = True
127
128             ve=doc.createElement(ns_prefix+v[0])
129             if need_ns:
130                 ve.setAttribute("xmlns:ns"+str(nsnum), v[1])
131             if len(v) > 2 and v[2] is not None:
132                 if isinstance(v[2], (list, tuple)):
133                     # support nested elements like:
134                     # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...]
135                     _prop_elem_child(ve, v[1], v[2], ns_prefix)
136                 else:
137                     vt=createText2Node(doc,tools.ustr(v[2]))
138                     ve.appendChild(vt)
139             if len(v) > 3 and v[3]:
140                 assert isinstance(v[3], dict)
141                 for ak, av in v[3].items():
142                     ve.setAttribute(ak, av)
143             pnode.appendChild(ve)
144         else:
145             ve=createText2Node(doc, tools.ustr(v))
146             pnode.appendChild(ve)
147
148     # write href information
149     uparts=urlparse.urlparse(uri)
150     fileloc=uparts[2]
151     if isinstance(fileloc, unicode):
152         fileloc = fileloc.encode('utf-8')
153     href=doc.createElement("D:href")
154     davpath = self._dataclass.parent.get_davpath()
155     if uparts[0] and uparts[1]:
156         hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
157     else:
158         # When the request has been relative, we don't have enough data to
159         # reply with absolute url here.
160         hurl = '%s%s' % (davpath, urllib.quote(fileloc))
161     huri=doc.createTextNode(hurl)
162     href.appendChild(huri)
163     re.appendChild(href)
164
165     # write good properties
166     ps=doc.createElement("D:propstat")
167     if good_props:
168         re.appendChild(ps)
169     s=doc.createElement("D:status")
170     t=doc.createTextNode("HTTP/1.1 200 OK")
171     s.appendChild(t)
172     ps.appendChild(s)
173
174     gp=doc.createElement("D:prop")
175     for ns in good_props.keys():
176         if ns == 'DAV:':
177             ns_prefix = 'D:'
178         else:
179             ns_prefix="ns"+str(namespaces.index(ns))+":"
180         for p,v in good_props[ns].items():
181             if v is None:
182                 continue
183             _prop_child(gp, ns, p, v)
184
185     ps.appendChild(gp)
186     re.appendChild(ps)
187
188     # now write the errors!
189     if len(bad_props.items()):
190
191         # write a propstat for each error code
192         for ecode in bad_props.keys():
193             ps=doc.createElement("D:propstat")
194             re.appendChild(ps)
195             s=doc.createElement("D:status")
196             t=doc.createTextNode(utils.gen_estring(ecode))
197             s.appendChild(t)
198             ps.appendChild(s)
199             bp=doc.createElement("D:prop")
200             ps.appendChild(bp)
201
202             for ns in bad_props[ecode].keys():
203                 if ns == 'DAV:':
204                     ns_prefix='D:'
205                 else:
206                     ns_prefix="ns"+str(self.namespaces.index(ns))+":"
207
208             for p in bad_props[ecode][ns]:
209                 pe=doc.createElement(ns_prefix+str(p))
210                 bp.appendChild(pe)
211
212             re.appendChild(ps)
213
214     # return the new response element
215     return re
216
217
218 def mk_propname_response(self,uri,propnames,doc):
219     """ make a new <prop> result element for a PROPNAME request
220
221     This will simply format the propnames list.
222     propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
223
224     """
225     re=doc.createElement("D:response")
226
227     # write href information
228     uparts=urlparse.urlparse(uri)
229     fileloc=uparts[2]
230     if isinstance(fileloc, unicode):
231         fileloc = fileloc.encode('utf-8')
232     href=doc.createElement("D:href")
233     davpath = self._dataclass.parent.get_davpath()
234     if uparts[0] and uparts[1]:
235         hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
236     else:
237         # When the request has been relative, we don't have enough data to
238         # reply with absolute url here.
239         hurl = '%s%s' % (davpath, urllib.quote(fileloc))
240     huri=doc.createTextNode(hurl)
241     href.appendChild(huri)
242     re.appendChild(href)
243
244     ps=doc.createElement("D:propstat")
245     nsnum=0
246
247     for ns,plist in propnames.items():
248         # write prop element
249         pr=doc.createElement("D:prop")
250         if ns == 'DAV':
251             nsp = 'D'
252         else:
253             nsp="ns"+str(nsnum)
254             ps.setAttribute("xmlns:"+nsp,ns)
255             nsnum=nsnum+1
256
257         # write propertynames
258         for p in plist:
259             pe=doc.createElement(nsp+":"+p)
260             pr.appendChild(pe)
261
262         ps.appendChild(pr)
263
264     re.appendChild(ps)
265
266     return re
267
268 PROPFIND.mk_prop_response = mk_prop_response
269 PROPFIND.mk_propname_response = mk_propname_response
270
271 super_create_prop = REPORT.create_prop
272
273 def create_prop(self):
274     try:
275         if (self.filter is not None) and self._depth == "0":
276             hrefs = self.filter.getElementsByTagNameNS('DAV:', 'href')
277             if hrefs:
278                 self._depth = "1"
279     except Exception:
280         pass
281     return super_create_prop(self)
282
283 REPORT.create_prop = create_prop
284
285 #eof