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
31
"MultiStatusResponse",
33
"PropertyStatusResponseQueue",
39
from twisted.python import log
40
from twisted.python.failure import Failure
41
from twisted.web2 import responsecode
42
from twisted.web2.iweb import IResponse
43
from twisted.web2.http import Response, HTTPError, StatusResponse
44
from twisted.web2.http_headers import MimeType
45
from twisted.web2.dav import davxml
48
# Generating and tweaking responses
51
class ErrorResponse (Response):
53
A L{Response} object which contains a status code and a L{davxml.Error}
55
Renders itself as a DAV:error XML document.
59
def __init__(self, code, error):
61
@param code: a response code.
62
@param error: an L{davxml.WebDAVElement} identifying the error, or a
63
tuple C{(namespace, name)} with which to create an empty element
64
denoting the error. (The latter is useful in the case of
65
preconditions ans postconditions, not all of which have defined
68
if type(error) is tuple:
69
xml_namespace, xml_name = error
70
class EmptyError (davxml.WebDAVEmptyElement):
71
namespace = xml_namespace
75
output = davxml.Error(error).toxml()
77
Response.__init__(self, code=code, stream=output)
79
self.headers.setHeader("content-type", MimeType("text", "xml"))
84
return "<%s %s %s>" % (self.__class__.__name__, self.code, self.error.sname())
86
class MultiStatusResponse (Response):
88
Multi-status L{Response} object.
89
Renders itself as a DAV:multi-status XML document.
91
def __init__(self, xml_responses):
93
@param xml_responses: an interable of davxml.Response objects.
95
multistatus = davxml.MultiStatus(*xml_responses)
96
output = multistatus.toxml()
98
Response.__init__(self, code=responsecode.MULTI_STATUS,
99
stream=davxml.MultiStatus(*xml_responses).toxml())
101
self.headers.setHeader("content-type", MimeType("text", "xml"))
103
class ResponseQueue (object):
105
Stores a list of (typically error) responses for use in a
106
L{MultiStatusResponse}.
108
def __init__(self, path_basename, method, success_response):
110
@param path_basename: the base path for all responses to be added to the
112
All paths for responses added to the queue must start with
113
C{path_basename}, which will be stripped from the beginning of each
114
path to determine the response's URI.
115
@param method: the name of the method generating the queue.
116
@param success_response: the response to return in lieu of a
117
L{MultiStatusResponse} if no responses are added to this queue.
120
self.path_basename = path_basename
121
self.path_basename_len = len(path_basename)
123
self.success_response = success_response
125
def add(self, path, what):
128
@param path: a path, which must be a subpath of C{path_basename} as
129
provided to L{__init__}.
130
@param what: a status code or a L{Failure} for the given path.
132
assert path.startswith(self.path_basename), "%s does not start with %s" % (path, self.path_basename)
134
if type(what) is int:
137
message = responsecode.RESPONSES[code]
138
elif isinstance(what, Failure):
139
code = statusForFailure(what)
140
error = errorForFailure(what)
141
message = messageForFailure(what)
143
raise AssertionError("Unknown data type: %r" % (what,))
145
if code > 400: # Error codes only
146
log.err("Error during %s for %s: %s" % (self.method, path, message))
148
uri = path[self.path_basename_len:]
151
children.append(davxml.HRef(uri))
152
children.append(davxml.Status.fromResponseCode(code))
153
if error is not None:
154
children.append(error)
155
if message is not None:
156
children.append(davxml.ResponseDescription(message))
157
self.responses.append(davxml.StatusResponse(*children))
161
Generate a L{MultiStatusResponse} with the responses contained in the
162
queue or, if no such responses, return the C{success_response} provided
164
@return: the response.
167
return MultiStatusResponse(self.responses)
169
return self.success_response
171
class PropertyStatusResponseQueue (object):
173
Stores a list of propstat elements for use in a L{Response}
174
in a L{MultiStatusResponse}.
176
def __init__(self, method, uri, success_response):
178
@param method: the name of the method generating the queue.
179
@param uri: the href for the response.
180
@param success_response: the status to return if no
181
L{PropertyStatus} are added to this queue.
184
self.propstats = [davxml.HRef(uri)]
185
self.success_response = success_response
187
def add(self, what, property):
190
@param what: a status code or a L{Failure} for the given path.
191
@param property: the property whose status is being reported.
193
if type(what) is int:
196
message = responsecode.RESPONSES[code]
197
elif isinstance(what, Failure):
198
code = statusForFailure(what)
199
error = errorForFailure(what)
200
message = messageForFailure(what)
202
raise AssertionError("Unknown data type: %r" % (what,))
204
if len(property.children) > 0:
205
# Re-instantiate as empty element.
206
property = property.__class__()
208
if code > 400: # Error codes only
209
log.err("Error during %s for %s: %s" % (self.method, property, message))
212
children.append(davxml.PropertyContainer(property))
213
children.append(davxml.Status.fromResponseCode(code))
214
if error is not None:
215
children.append(error)
216
if message is not None:
217
children.append(davxml.ResponseDescription(message))
218
self.propstats.append(davxml.PropertyStatus(*children))
222
Convert any 2xx codes in the propstat responses to 424 Failed Dependency.
224
for propstat in self.propstats:
226
changed_status = False
227
for index, child in enumerate(propstat.children):
228
if isinstance(child, davxml.Status) and (child.code / 100 == 2):
230
propstat.children[index] = davxml.Status.fromResponseCode(
231
responsecode.FAILED_DEPENDENCY
233
changed_status = True
234
elif changed_status and isinstance(child, davxml.ResponseDescription):
235
propstat.children[index] = davxml.ResponseDescription(
236
responsecode.RESPONSES[responsecode.FAILED_DEPENDENCY]
241
Generate a response from the responses contained in the queue or, if
242
there are no such responses, return the C{success_response} provided to
244
@return: a L{davxml.PropertyStatusResponse}.
246
if len(self.propstats) == 1:
247
self.propstats.append(davxml.Status.fromResponseCode(self.success_response))
248
return davxml.PropertyStatusResponse(*self.propstats)
251
# Exceptions and response codes
254
def statusForFailure(failure, what=None):
256
@param failure: a L{Failure}.
257
@param what: a decription of what was going on when the failure occurred.
258
If what is not C{None}, emit a cooresponding message via L{log.err}.
259
@return: a response code cooresponding to the given C{failure}.
263
log.msg("%s while %s" % (err, what))
265
if failure.check(IOError, OSError):
267
if e == errno.EACCES or e == errno.EPERM:
268
msg("Permission denied")
269
return responsecode.FORBIDDEN
270
elif e == errno.ENOSPC:
271
msg("Out of storage space")
272
return responsecode.INSUFFICIENT_STORAGE_SPACE
273
elif e == errno.ENOENT:
275
return responsecode.NOT_FOUND
277
failure.raiseException()
278
elif failure.check(NotImplementedError):
279
msg("Unimplemented error")
280
return responsecode.NOT_IMPLEMENTED
281
elif failure.check(HTTPError):
282
code = IResponse(failure.value.response).code
283
msg("%d response" % (code,))
286
failure.raiseException()
288
def errorForFailure(failure):
289
if failure.check(HTTPError) and isinstance(failure.value.response, ErrorResponse):
290
return davxml.Error(failure.value.response.error)
294
def messageForFailure(failure):
295
if failure.check(HTTPError):
296
if isinstance(failure.value.response, ErrorResponse):
298
if isinstance(failure.value.response, StatusResponse):
299
return failure.value.response.description