~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/web2/dav/http.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
##
 
2
# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 
3
#
 
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:
 
10
 
11
# The above copyright notice and this permission notice shall be included in all
 
12
# copies or substantial portions of the Software.
 
13
 
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
 
20
# SOFTWARE.
 
21
#
 
22
# DRI: Wilfredo Sanchez, wsanchez@apple.com
 
23
##
 
24
 
 
25
"""
 
26
HTTP Utilities
 
27
"""
 
28
 
 
29
__all__ = [
 
30
    "ErrorResponse",
 
31
    "MultiStatusResponse",
 
32
    "ResponseQueue",
 
33
    "PropertyStatusResponseQueue",
 
34
    "statusForFailure",
 
35
]
 
36
 
 
37
import errno
 
38
 
 
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
 
46
 
 
47
##
 
48
# Generating and tweaking responses
 
49
##
 
50
 
 
51
class ErrorResponse (Response):
 
52
    """
 
53
    A L{Response} object which contains a status code and a L{davxml.Error}
 
54
    element.
 
55
    Renders itself as a DAV:error XML document.
 
56
    """
 
57
    error = None
 
58
 
 
59
    def __init__(self, code, error):
 
60
        """
 
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
 
66
            XML element classes.)
 
67
        """
 
68
        if type(error) is tuple:
 
69
            xml_namespace, xml_name = error
 
70
            class EmptyError (davxml.WebDAVEmptyElement):
 
71
                namespace = xml_namespace
 
72
                name      = xml_name
 
73
            error = EmptyError()
 
74
 
 
75
        output = davxml.Error(error).toxml()
 
76
 
 
77
        Response.__init__(self, code=code, stream=output)
 
78
 
 
79
        self.headers.setHeader("content-type", MimeType("text", "xml"))
 
80
 
 
81
        self.error = error
 
82
 
 
83
    def __repr__(self):
 
84
        return "<%s %s %s>" % (self.__class__.__name__, self.code, self.error.sname())
 
85
 
 
86
class MultiStatusResponse (Response):
 
87
    """
 
88
    Multi-status L{Response} object.
 
89
    Renders itself as a DAV:multi-status XML document.
 
90
    """
 
91
    def __init__(self, xml_responses):
 
92
        """
 
93
        @param xml_responses: an interable of davxml.Response objects.
 
94
        """
 
95
        multistatus = davxml.MultiStatus(*xml_responses)
 
96
        output = multistatus.toxml()
 
97
 
 
98
        Response.__init__(self, code=responsecode.MULTI_STATUS,
 
99
                          stream=davxml.MultiStatus(*xml_responses).toxml())
 
100
 
 
101
        self.headers.setHeader("content-type", MimeType("text", "xml"))
 
102
 
 
103
class ResponseQueue (object):
 
104
    """
 
105
    Stores a list of (typically error) responses for use in a
 
106
    L{MultiStatusResponse}.
 
107
    """
 
108
    def __init__(self, path_basename, method, success_response):
 
109
        """
 
110
        @param path_basename: the base path for all responses to be added to the 
 
111
            queue.
 
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.
 
118
        """
 
119
        self.responses         = []
 
120
        self.path_basename     = path_basename
 
121
        self.path_basename_len = len(path_basename)
 
122
        self.method            = method
 
123
        self.success_response  = success_response
 
124
 
 
125
    def add(self, path, what):
 
126
        """
 
127
        Add a response.
 
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.
 
131
        """
 
132
        assert path.startswith(self.path_basename), "%s does not start with %s" % (path, self.path_basename)
 
133
 
 
134
        if type(what) is int:
 
135
            code    = what
 
136
            error   = None
 
137
            message = responsecode.RESPONSES[code]
 
138
        elif isinstance(what, Failure):
 
139
            code    = statusForFailure(what)
 
140
            error   = errorForFailure(what)
 
141
            message = messageForFailure(what)
 
142
        else:
 
143
            raise AssertionError("Unknown data type: %r" % (what,))
 
144
 
 
145
        if code > 400: # Error codes only
 
146
            log.err("Error during %s for %s: %s" % (self.method, path, message))
 
147
 
 
148
        uri = path[self.path_basename_len:]
 
149
 
 
150
        children = []
 
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))
 
158
 
 
159
    def response(self):
 
160
        """
 
161
        Generate a L{MultiStatusResponse} with the responses contained in the
 
162
        queue or, if no such responses, return the C{success_response} provided
 
163
        to L{__init__}.
 
164
        @return: the response.
 
165
        """
 
166
        if self.responses:
 
167
            return MultiStatusResponse(self.responses)
 
168
        else:
 
169
            return self.success_response
 
170
 
 
171
class PropertyStatusResponseQueue (object):
 
172
    """
 
173
    Stores a list of propstat elements for use in a L{Response}
 
174
    in a L{MultiStatusResponse}.
 
175
    """
 
176
    def __init__(self, method, uri, success_response):
 
177
        """
 
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.
 
182
        """
 
183
        self.method            = method
 
184
        self.propstats         = [davxml.HRef(uri)]
 
185
        self.success_response  = success_response
 
186
 
 
187
    def add(self, what, property):
 
188
        """
 
189
        Add a response.
 
190
        @param what: a status code or a L{Failure} for the given path.
 
191
        @param property: the property whose status is being reported.
 
192
        """
 
193
        if type(what) is int:
 
194
            code    = what
 
195
            error   = None
 
196
            message = responsecode.RESPONSES[code]
 
197
        elif isinstance(what, Failure):
 
198
            code    = statusForFailure(what)
 
199
            error   = errorForFailure(what)
 
200
            message = messageForFailure(what)
 
201
        else:
 
202
            raise AssertionError("Unknown data type: %r" % (what,))
 
203
 
 
204
        if len(property.children) > 0:
 
205
            # Re-instantiate as empty element.
 
206
            property = property.__class__()
 
207
 
 
208
        if code > 400: # Error codes only
 
209
            log.err("Error during %s for %s: %s" % (self.method, property, message))
 
210
 
 
211
        children = []
 
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))
 
219
 
 
220
    def error(self):
 
221
        """
 
222
        Convert any 2xx codes in the propstat responses to 424 Failed Dependency.
 
223
        """
 
224
        for propstat in self.propstats:
 
225
            # Check the status
 
226
            changed_status = False
 
227
            for index, child in enumerate(propstat.children):
 
228
                if isinstance(child, davxml.Status) and (child.code / 100 == 2):
 
229
                    # Change the code
 
230
                    propstat.children[index] = davxml.Status.fromResponseCode(
 
231
                        responsecode.FAILED_DEPENDENCY
 
232
                    )
 
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]
 
237
                    )
 
238
 
 
239
    def response(self):
 
240
        """
 
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
 
243
        L{__init__}.
 
244
        @return: a L{davxml.PropertyStatusResponse}.
 
245
        """
 
246
        if len(self.propstats) == 1:
 
247
            self.propstats.append(davxml.Status.fromResponseCode(self.success_response))
 
248
        return davxml.PropertyStatusResponse(*self.propstats)
 
249
 
 
250
##
 
251
# Exceptions and response codes
 
252
##
 
253
 
 
254
def statusForFailure(failure, what=None):
 
255
    """
 
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}.
 
260
    """
 
261
    def msg(err):
 
262
        if what is not None:
 
263
            log.msg("%s while %s" % (err, what))
 
264
 
 
265
    if failure.check(IOError, OSError):
 
266
        e = failure.value[0]
 
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:
 
274
            msg("Not found")
 
275
            return responsecode.NOT_FOUND
 
276
        else:
 
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,))
 
284
        return code
 
285
    else:
 
286
        failure.raiseException()
 
287
 
 
288
def errorForFailure(failure):
 
289
    if failure.check(HTTPError) and isinstance(failure.value.response, ErrorResponse):
 
290
        return davxml.Error(failure.value.response.error)
 
291
    else:
 
292
        return None
 
293
 
 
294
def messageForFailure(failure):
 
295
    if failure.check(HTTPError):
 
296
        if isinstance(failure.value.response, ErrorResponse):
 
297
            return None
 
298
        if isinstance(failure.value.response, StatusResponse):
 
299
            return failure.value.response.description
 
300
    return str(failure)