2
# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
4
# Permission is hereby granted, free of charge, to any person obtaining a copy
5
# of this software and associated documentation files (the "Software"), to deal
6
# in the Software without restriction, including without limitation the rights
7
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
# copies of the Software, and to permit persons to whom the Software is
9
# furnished to do so, subject to the following conditions:
11
# The above copyright notice and this permission notice shall be included in all
12
# copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
# DRI: Wilfredo Sanchez, wsanchez@apple.com
27
from twisted.trial.unittest import SkipTest
28
from twisted.web2 import responsecode
29
from twisted.web2.iweb import IResponse
30
from twisted.web2.stream import MemoryStream
31
from twisted.web2 import http_headers
32
from twisted.web2.dav import davxml
33
from twisted.web2.dav.resource import DAVResource
34
from twisted.web2.dav.davxml import dav_namespace, lookupElement
35
from twisted.web2.dav.util import davXMLFromStream
36
from twisted.web2.test.test_server import SimpleRequest
37
from twisted.web2.dav.test.util import serialize
38
import twisted.web2.dav.test.util
40
live_properties = [lookupElement(qname)() for qname in DAVResource.liveProperties if qname[0] == dav_namespace]
43
# See whether dead properties are available
45
from twisted.web2.dav.noneprops import NonePropertyStore
46
from twisted.web2.dav.static import DeadPropertyStore
47
if DeadPropertyStore == NonePropertyStore:
48
have_dead_properties = False
50
have_dead_properties = True
52
class PROP(twisted.web2.dav.test.util.TestCase):
54
PROPFIND, PROPPATCH requests
56
def test_PROPFIND_basic(self):
60
def check_result(response):
61
response = IResponse(response)
63
if response.code != responsecode.MULTI_STATUS:
64
self.fail("Incorrect response code for PROPFIND (%s != %s)"
65
% (response.code, responsecode.MULTI_STATUS))
67
content_type = response.headers.getHeader("content-type")
68
if content_type not in (http_headers.MimeType("text", "xml"),
69
http_headers.MimeType("application", "xml")):
70
self.fail("Incorrect content-type for PROPFIND response (%r not in %r)"
71
% (content_type, (http_headers.MimeType("text", "xml"),
72
http_headers.MimeType("application", "xml"))))
74
return davXMLFromStream(response.stream).addCallback(check_xml)
77
multistatus = doc.root_element
79
if not isinstance(multistatus, davxml.MultiStatus):
80
self.fail("PROPFIND response XML root element is not multistatus: %r" % (multistatus,))
82
for response in multistatus.childrenOfType(davxml.PropertyStatusResponse):
83
if response.childOfType(davxml.HRef) == "/":
84
for propstat in response.childrenOfType(davxml.PropertyStatus):
85
status = propstat.childOfType(davxml.Status)
86
properties = propstat.childOfType(davxml.PropertyContainer).children
88
if status.code != responsecode.OK:
89
self.fail("PROPFIND failed (status %s) to locate live properties: %s"
90
% (status.code, properties))
92
properties_to_find = [p.qname() for p in live_properties]
94
for property in properties:
95
qname = property.qname()
96
if qname in properties_to_find:
97
properties_to_find.remove(qname)
99
self.fail("PROPFIND found property we didn't ask for: %r" % (property,))
101
if properties_to_find:
102
self.fail("PROPFIND failed to find properties: %r" % (properties_to_find,))
107
self.fail("No response for URI /")
109
query = davxml.PropertyFind(davxml.PropertyContainer(*live_properties))
111
request = SimpleRequest(self.site, "PROPFIND", "/")
113
depth = random.choice(("0", "1", "infinity", None))
114
if depth is not None:
115
request.headers.setHeader("depth", depth)
117
request.stream = MemoryStream(query.toxml())
119
return self.send(request, check_result)
121
def test_PROPFIND_list(self):
123
PROPFIND with allprop, propname
125
def check_result(which):
126
def _check_result(response):
127
response = IResponse(response)
129
if response.code != responsecode.MULTI_STATUS:
130
self.fail("Incorrect response code for PROPFIND (%s != %s)"
131
% (response.code, responsecode.MULTI_STATUS))
133
return davXMLFromStream(response.stream).addCallback(check_xml, which)
136
def check_xml(doc, which):
137
response = doc.root_element.childOfType(davxml.PropertyStatusResponse)
140
response.childOfType(davxml.HRef) == "/",
141
"Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef),)
144
for propstat in response.childrenOfType(davxml.PropertyStatus):
145
status = propstat.childOfType(davxml.Status)
146
properties = propstat.childOfType(davxml.PropertyContainer).children
148
if status.code != responsecode.OK:
149
self.fail("PROPFIND failed (status %s) to locate live properties: %s"
150
% (status.code, properties))
152
if which.name == "allprop":
153
properties_to_find = [p.qname() for p in live_properties if not p.hidden]
155
properties_to_find = [p.qname() for p in live_properties]
157
for property in properties:
158
qname = property.qname()
159
if qname in properties_to_find:
160
properties_to_find.remove(qname)
161
elif qname[0] != dav_namespace:
164
self.fail("PROPFIND with %s found property we didn't expect: %r" % (which.name, property))
166
if which.name == "propname":
167
# Element should be empty
168
self.failUnless(len(property.children) == 0)
170
# Element should have a value
171
# Actually, this isn't necessarily true, but it is for the live
172
# properties we've defined so far...
173
self.failIf(len(property.children) == 0)
175
if properties_to_find:
176
self.fail("PROPFIND with %s failed to find properties: %r" % (which.name, properties_to_find))
178
properties = propstat.childOfType(davxml.PropertyContainer).children
181
for which in (davxml.AllProperties(), davxml.PropertyName()):
182
query = davxml.PropertyFind(which)
184
request = SimpleRequest(self.site, "PROPFIND", "/")
185
request.headers.setHeader("depth", "0")
186
request.stream = MemoryStream(query.toxml())
188
yield (request, check_result(which))
190
return serialize(self.send, work())
192
def test_PROPPATCH_basic(self):
197
# Do PROPFIND to make sure it's still there
198
# Test nonexistant resource
199
# Test None namespace in property
201
def check_patch_response(response):
202
response = IResponse(response)
204
if response.code != responsecode.MULTI_STATUS:
205
self.fail("Incorrect response code for PROPFIND (%s != %s)"
206
% (response.code, responsecode.MULTI_STATUS))
208
content_type = response.headers.getHeader("content-type")
209
if content_type not in (http_headers.MimeType("text", "xml"),
210
http_headers.MimeType("application", "xml")):
211
self.fail("Incorrect content-type for PROPPATCH response (%r not in %r)"
212
% (content_type, (http_headers.MimeType("text", "xml"),
213
http_headers.MimeType("application", "xml"))))
215
return davXMLFromStream(response.stream).addCallback(check_patch_xml)
217
def check_patch_xml(doc):
218
multistatus = doc.root_element
220
if not isinstance(multistatus, davxml.MultiStatus):
221
self.fail("PROPFIND response XML root element is not multistatus: %r" % (multistatus,))
223
# Requested a property change one resource, so there should be exactly one response
224
response = multistatus.childOfType(davxml.Response)
226
# Should have a response description (its contents are arbitrary)
227
response.childOfType(davxml.ResponseDescription)
229
# Requested property change was on /
231
response.childOfType(davxml.HRef) == "/",
232
"Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef),)
235
# Requested one property change, so there should be exactly one property status
236
propstat = response.childOfType(davxml.PropertyStatus)
238
# And the contained property should be a SpiffyProperty
240
propstat.childOfType(davxml.PropertyContainer).childOfType(SpiffyProperty) is None,
241
"Not a SpiffyProperty in PROPPATCH property status: %s" % (propstat.toxml())
244
if not have_dead_properties:
246
"No dead property store available for DAVFile. "
247
"Install xattr (http://undefined.org/python/#xattr) to enable use of dead properties."
250
# And the status should be 200
252
propstat.childOfType(davxml.Status).code == responsecode.OK,
253
"Incorrect status code for PROPPATCH of property %s: %s != %s"
254
% (propstat.childOfType(davxml.PropertyContainer).toxml(),
255
propstat.childOfType(davxml.Status).code, responsecode.OK)
258
patch = davxml.PropertyUpdate(
260
davxml.PropertyContainer(
261
SpiffyProperty.fromString("This is a spiffy resource.")
266
request = SimpleRequest(self.site, "PROPPATCH", "/")
267
request.stream = MemoryStream(patch.toxml())
268
return self.send(request, check_patch_response)
270
def test_PROPPATCH_liveprop(self):
272
PROPPATCH on a live property
274
prop = davxml.GETETag.fromString("some-etag-string")
275
patch = davxml.PropertyUpdate(davxml.Set(davxml.PropertyContainer(prop)))
277
return self._simple_PROPPATCH(patch, prop, responsecode.FORBIDDEN, "edit of live property")
279
def test_PROPPATCH_exists_not(self):
281
PROPPATCH remove a non-existant property
283
prop = davxml.Timeout() # Timeout isn't a valid property, so it won't exist.
284
patch = davxml.PropertyUpdate(davxml.Remove(davxml.PropertyContainer(prop)))
286
return self._simple_PROPPATCH(patch, prop, responsecode.OK, "remove of non-existant property")
288
def _simple_PROPPATCH(self, patch, prop, expected_code, what):
289
def check_result(response):
290
response = IResponse(response)
292
if response.code != responsecode.MULTI_STATUS:
293
self.fail("Incorrect response code for PROPPATCH (%s != %s)"
294
% (response.code, responsecode.MULTI_STATUS))
296
return davXMLFromStream(response.stream).addCallback(check_xml)
299
response = doc.root_element.childOfType(davxml.Response)
300
propstat = response.childOfType(davxml.PropertyStatus)
303
response.childOfType(davxml.HRef) == "/",
304
"Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef),)
308
propstat.childOfType(davxml.PropertyContainer).childOfType(prop) is None,
309
"Not a %s in PROPPATCH property status: %s" % (prop.sname(), propstat.toxml())
312
if not have_dead_properties:
314
"No dead property store available for DAVFile. "
315
"Install xattr (http://undefined.org/python/#xattr) to enable use of dead properties."
319
propstat.childOfType(davxml.Status).code == expected_code,
320
"Incorrect status code for PROPPATCH %s: %s != %s"
321
% (what, propstat.childOfType(davxml.Status).code, expected_code)
324
request = SimpleRequest(self.site, "PROPPATCH", "/")
325
request.stream = MemoryStream(patch.toxml())
326
return self.send(request, check_result)
328
class SpiffyProperty (davxml.WebDAVTextElement):
329
namespace = "http://twistedmatrix.com/ns/private/tests"
330
name = "spiffyproperty"