~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/http/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
import os
2
 
from Cookie import SimpleCookie
 
2
import re
 
3
from Cookie import SimpleCookie, CookieError
3
4
from pprint import pformat
4
 
from urllib import urlencode, quote
5
 
from django.utils.datastructures import MultiValueDict
6
 
 
7
 
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
8
 
 
 
5
from urllib import urlencode
 
6
from urlparse import urljoin
9
7
try:
10
8
    # The mod_python version is more efficient, so try importing it first.
11
9
    from mod_python.util import parse_qsl
12
10
except ImportError:
13
11
    from cgi import parse_qsl
14
12
 
 
13
from django.utils.datastructures import MultiValueDict, ImmutableList
 
14
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
 
15
from django.http.multipartparser import MultiPartParser
 
16
from django.conf import settings
 
17
from django.core.files import uploadhandler
 
18
from utils import *
 
19
 
 
20
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
 
21
 
 
22
absolute_http_url_re = re.compile(r"^https?://", re.I)
 
23
 
15
24
class Http404(Exception):
16
25
    pass
17
26
 
18
27
class HttpRequest(object):
19
 
    "A basic HTTP request"
 
28
    """A basic HTTP request."""
 
29
 
 
30
    # The encoding used in GET/POST dicts. None means use default setting.
 
31
    _encoding = None
 
32
    _upload_handlers = []
 
33
 
20
34
    def __init__(self):
21
35
        self.GET, self.POST, self.COOKIES, self.META, self.FILES = {}, {}, {}, {}, {}
22
36
        self.path = ''
 
37
        self.path_info = ''
23
38
        self.method = None
24
39
 
25
40
    def __repr__(self):
27
42
            (pformat(self.GET), pformat(self.POST), pformat(self.COOKIES),
28
43
            pformat(self.META))
29
44
 
30
 
    def __getitem__(self, key):
31
 
        for d in (self.POST, self.GET):
32
 
            if d.has_key(key):
33
 
                return d[key]
34
 
        raise KeyError, "%s not found in either POST or GET" % key
35
 
 
36
 
    def has_key(self, key):
37
 
        return self.GET.has_key(key) or self.POST.has_key(key)
 
45
    def get_host(self):
 
46
        """Returns the HTTP host using the environment or request headers."""
 
47
        # We try three options, in order of decreasing preference.
 
48
        if 'HTTP_X_FORWARDED_HOST' in self.META:
 
49
            host = self.META['HTTP_X_FORWARDED_HOST']
 
50
        elif 'HTTP_HOST' in self.META:
 
51
            host = self.META['HTTP_HOST']
 
52
        else:
 
53
            # Reconstruct the host using the algorithm from PEP 333.
 
54
            host = self.META['SERVER_NAME']
 
55
            server_port = str(self.META['SERVER_PORT'])
 
56
            if server_port != (self.is_secure() and '443' or '80'):
 
57
                host = '%s:%s' % (host, server_port)
 
58
        return host
38
59
 
39
60
    def get_full_path(self):
40
61
        return ''
41
62
 
 
63
    def build_absolute_uri(self, location=None):
 
64
        """
 
65
        Builds an absolute URI from the location and the variables available in
 
66
        this request. If no location is specified, the absolute URI is built on
 
67
        ``request.get_full_path()``.
 
68
        """
 
69
        if not location:
 
70
            location = self.get_full_path()
 
71
        if not absolute_http_url_re.match(location):
 
72
            current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
 
73
                                         self.get_host(), self.path)
 
74
            location = urljoin(current_uri, location)
 
75
        return location
 
76
 
42
77
    def is_secure(self):
43
78
        return os.environ.get("HTTPS") == "on"
44
79
 
45
 
def parse_file_upload(header_dict, post_data):
46
 
    "Returns a tuple of (POST MultiValueDict, FILES MultiValueDict)"
47
 
    import email, email.Message
48
 
    from cgi import parse_header
49
 
    raw_message = '\r\n'.join(['%s:%s' % pair for pair in header_dict.items()])
50
 
    raw_message += '\r\n\r\n' + post_data
51
 
    msg = email.message_from_string(raw_message)
52
 
    POST = MultiValueDict()
53
 
    FILES = MultiValueDict()
54
 
    for submessage in msg.get_payload():
55
 
        if submessage and isinstance(submessage, email.Message.Message):
56
 
            name_dict = parse_header(submessage['Content-Disposition'])[1]
57
 
            # name_dict is something like {'name': 'file', 'filename': 'test.txt'} for file uploads
58
 
            # or {'name': 'blah'} for POST fields
59
 
            # We assume all uploaded files have a 'filename' set.
60
 
            if name_dict.has_key('filename'):
61
 
                assert type([]) != type(submessage.get_payload()), "Nested MIME messages are not supported"
62
 
                if not name_dict['filename'].strip():
63
 
                    continue
64
 
                # IE submits the full path, so trim everything but the basename.
65
 
                # (We can't use os.path.basename because it expects Linux paths.)
66
 
                filename = name_dict['filename'][name_dict['filename'].rfind("\\")+1:]
67
 
                FILES.appendlist(name_dict['name'], {
68
 
                    'filename': filename,
69
 
                    'content-type': (submessage.has_key('Content-Type') and submessage['Content-Type'] or None),
70
 
                    'content': submessage.get_payload(),
71
 
                })
72
 
            else:
73
 
                POST.appendlist(name_dict['name'], submessage.get_payload())
74
 
    return POST, FILES
 
80
    def is_ajax(self):
 
81
        return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
 
82
 
 
83
    def _set_encoding(self, val):
 
84
        """
 
85
        Sets the encoding used for GET/POST accesses. If the GET or POST
 
86
        dictionary has already been created, it is removed and recreated on the
 
87
        next access (so that it is decoded correctly).
 
88
        """
 
89
        self._encoding = val
 
90
        if hasattr(self, '_get'):
 
91
            del self._get
 
92
        if hasattr(self, '_post'):
 
93
            del self._post
 
94
 
 
95
    def _get_encoding(self):
 
96
        return self._encoding
 
97
 
 
98
    encoding = property(_get_encoding, _set_encoding)
 
99
 
 
100
    def _initialize_handlers(self):
 
101
        self._upload_handlers = [uploadhandler.load_handler(handler, self)
 
102
                                 for handler in settings.FILE_UPLOAD_HANDLERS]
 
103
 
 
104
    def _set_upload_handlers(self, upload_handlers):
 
105
        if hasattr(self, '_files'):
 
106
            raise AttributeError("You cannot set the upload handlers after the upload has been processed.")
 
107
        self._upload_handlers = upload_handlers
 
108
 
 
109
    def _get_upload_handlers(self):
 
110
        if not self._upload_handlers:
 
111
            # If thre are no upload handlers defined, initialize them from settings.
 
112
            self._initialize_handlers()
 
113
        return self._upload_handlers
 
114
 
 
115
    upload_handlers = property(_get_upload_handlers, _set_upload_handlers)
 
116
 
 
117
    def parse_file_upload(self, META, post_data):
 
118
        """Returns a tuple of (POST QueryDict, FILES MultiValueDict)."""
 
119
        self.upload_handlers = ImmutableList(
 
120
            self.upload_handlers,
 
121
            warning = "You cannot alter upload handlers after the upload has been processed."
 
122
        )
 
123
        parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
 
124
        return parser.parse()
75
125
 
76
126
class QueryDict(MultiValueDict):
77
 
    """A specialized MultiValueDict that takes a query string when initialized.
78
 
    This is immutable unless you create a copy of it."""
79
 
    def __init__(self, query_string, mutable=False):
 
127
    """
 
128
    A specialized MultiValueDict that takes a query string when initialized.
 
129
    This is immutable unless you create a copy of it.
 
130
 
 
131
    Values retrieved from this class are converted from the given encoding
 
132
    (DEFAULT_CHARSET by default) to unicode.
 
133
    """
 
134
    # These are both reset in __init__, but is specified here at the class
 
135
    # level so that unpickling will have valid values
 
136
    _mutable = True
 
137
    _encoding = None
 
138
 
 
139
    def __init__(self, query_string, mutable=False, encoding=None):
80
140
        MultiValueDict.__init__(self)
81
 
        self._mutable = True
 
141
        if not encoding:
 
142
            # *Important*: do not import settings any earlier because of note
 
143
            # in core.handlers.modpython.
 
144
            from django.conf import settings
 
145
            encoding = settings.DEFAULT_CHARSET
 
146
        self.encoding = encoding
82
147
        for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
83
 
            self.appendlist(key, value)
 
148
            self.appendlist(force_unicode(key, encoding, errors='replace'),
 
149
                            force_unicode(value, encoding, errors='replace'))
84
150
        self._mutable = mutable
85
151
 
 
152
    def _get_encoding(self):
 
153
        if self._encoding is None:
 
154
            # *Important*: do not import settings at the module level because
 
155
            # of the note in core.handlers.modpython.
 
156
            from django.conf import settings
 
157
            self._encoding = settings.DEFAULT_CHARSET
 
158
        return self._encoding
 
159
 
 
160
    def _set_encoding(self, value):
 
161
        self._encoding = value
 
162
 
 
163
    encoding = property(_get_encoding, _set_encoding)
 
164
 
86
165
    def _assert_mutable(self):
87
166
        if not self._mutable:
88
 
            raise AttributeError, "This QueryDict instance is immutable"
 
167
            raise AttributeError("This QueryDict instance is immutable")
89
168
 
90
169
    def __setitem__(self, key, value):
91
170
        self._assert_mutable()
 
171
        key = str_to_unicode(key, self.encoding)
 
172
        value = str_to_unicode(value, self.encoding)
92
173
        MultiValueDict.__setitem__(self, key, value)
93
174
 
 
175
    def __delitem__(self, key):
 
176
        self._assert_mutable()
 
177
        super(QueryDict, self).__delitem__(key)
 
178
 
94
179
    def __copy__(self):
95
180
        result = self.__class__('', mutable=True)
96
181
        for key, value in dict.items(self):
97
182
            dict.__setitem__(result, key, value)
98
183
        return result
99
184
 
100
 
    def __deepcopy__(self, memo={}):
 
185
    def __deepcopy__(self, memo):
101
186
        import copy
102
187
        result = self.__class__('', mutable=True)
103
188
        memo[id(self)] = result
107
192
 
108
193
    def setlist(self, key, list_):
109
194
        self._assert_mutable()
 
195
        key = str_to_unicode(key, self.encoding)
 
196
        list_ = [str_to_unicode(elt, self.encoding) for elt in list_]
110
197
        MultiValueDict.setlist(self, key, list_)
111
198
 
 
199
    def setlistdefault(self, key, default_list=()):
 
200
        self._assert_mutable()
 
201
        if key not in self:
 
202
            self.setlist(key, default_list)
 
203
        return MultiValueDict.getlist(self, key)
 
204
 
112
205
    def appendlist(self, key, value):
113
206
        self._assert_mutable()
 
207
        key = str_to_unicode(key, self.encoding)
 
208
        value = str_to_unicode(value, self.encoding)
114
209
        MultiValueDict.appendlist(self, key, value)
115
210
 
116
211
    def update(self, other_dict):
117
212
        self._assert_mutable()
118
 
        MultiValueDict.update(self, other_dict)
 
213
        f = lambda s: str_to_unicode(s, self.encoding)
 
214
        if hasattr(other_dict, 'lists'):
 
215
            for key, valuelist in other_dict.lists():
 
216
                for value in valuelist:
 
217
                    MultiValueDict.update(self, {f(key): f(value)})
 
218
        else:
 
219
            d = dict([(f(k), f(v)) for k, v in other_dict.items()])
 
220
            MultiValueDict.update(self, d)
119
221
 
120
 
    def pop(self, key):
 
222
    def pop(self, key, *args):
121
223
        self._assert_mutable()
122
 
        return MultiValueDict.pop(self, key)
 
224
        return MultiValueDict.pop(self, key, *args)
123
225
 
124
226
    def popitem(self):
125
227
        self._assert_mutable()
129
231
        self._assert_mutable()
130
232
        MultiValueDict.clear(self)
131
233
 
132
 
    def setdefault(self, *args):
 
234
    def setdefault(self, key, default=None):
133
235
        self._assert_mutable()
134
 
        return MultiValueDict.setdefault(self, *args)
 
236
        key = str_to_unicode(key, self.encoding)
 
237
        default = str_to_unicode(default, self.encoding)
 
238
        return MultiValueDict.setdefault(self, key, default)
135
239
 
136
240
    def copy(self):
137
 
        "Returns a mutable copy of this object."
138
 
        return self.__deepcopy__()
 
241
        """Returns a mutable copy of this object."""
 
242
        return self.__deepcopy__({})
139
243
 
140
244
    def urlencode(self):
141
245
        output = []
142
246
        for k, list_ in self.lists():
143
 
            output.extend([urlencode({k: v}) for v in list_])
 
247
            k = smart_str(k, self.encoding)
 
248
            output.extend([urlencode({k: smart_str(v, self.encoding)}) for v in list_])
144
249
        return '&'.join(output)
145
250
 
146
251
def parse_cookie(cookie):
147
252
    if cookie == '':
148
253
        return {}
149
 
    c = SimpleCookie()
150
 
    c.load(cookie)
 
254
    try:
 
255
        c = SimpleCookie()
 
256
        c.load(cookie)
 
257
    except CookieError:
 
258
        # Invalid cookie
 
259
        return {}
 
260
 
151
261
    cookiedict = {}
152
262
    for key in c.keys():
153
263
        cookiedict[key] = c.get(key).value
154
264
    return cookiedict
155
265
 
156
266
class HttpResponse(object):
157
 
    "A basic HTTP response, with content and dictionary-accessed headers"
158
 
    def __init__(self, content='', mimetype=None):
 
267
    """A basic HTTP response, with content and dictionary-accessed headers."""
 
268
 
 
269
    status_code = 200
 
270
 
 
271
    def __init__(self, content='', mimetype=None, status=None,
 
272
            content_type=None):
159
273
        from django.conf import settings
160
274
        self._charset = settings.DEFAULT_CHARSET
161
 
        if not mimetype:
162
 
            mimetype = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, settings.DEFAULT_CHARSET)
 
275
        if mimetype:
 
276
            content_type = mimetype     # For backwards compatibility
 
277
        if not content_type:
 
278
            content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE,
 
279
                    settings.DEFAULT_CHARSET)
163
280
        if not isinstance(content, basestring) and hasattr(content, '__iter__'):
164
281
            self._container = content
165
282
            self._is_string = False
166
283
        else:
167
284
            self._container = [content]
168
285
            self._is_string = True
169
 
        self.headers = {'Content-Type': mimetype}
170
286
        self.cookies = SimpleCookie()
171
 
        self.status_code = 200
 
287
        if status:
 
288
            self.status_code = status
 
289
 
 
290
        # _headers is a mapping of the lower-case name to the original case of
 
291
        # the header (required for working with legacy systems) and the header
 
292
        # value.
 
293
        self._headers = {'content-type': ('Content-Type', content_type)}
172
294
 
173
295
    def __str__(self):
174
 
        "Full HTTP message, including headers"
 
296
        """Full HTTP message, including headers."""
175
297
        return '\n'.join(['%s: %s' % (key, value)
176
 
            for key, value in self.headers.items()]) \
 
298
            for key, value in self._headers.values()]) \
177
299
            + '\n\n' + self.content
178
300
 
 
301
    def _convert_to_ascii(self, *values):
 
302
        """Converts all values to ascii strings."""
 
303
        for value in values:
 
304
            if isinstance(value, unicode):
 
305
                try:
 
306
                    yield value.encode('us-ascii')
 
307
                except UnicodeError, e:
 
308
                    e.reason += ', HTTP response headers must be in US-ASCII format'
 
309
                    raise
 
310
            else:
 
311
                yield str(value)
 
312
 
179
313
    def __setitem__(self, header, value):
180
 
        self.headers[header] = value
 
314
        header, value = self._convert_to_ascii(header, value)
 
315
        self._headers[header.lower()] = (header, value)
181
316
 
182
317
    def __delitem__(self, header):
183
318
        try:
184
 
            del self.headers[header]
 
319
            del self._headers[header.lower()]
185
320
        except KeyError:
186
321
            pass
187
322
 
188
323
    def __getitem__(self, header):
189
 
        return self.headers[header]
 
324
        return self._headers[header.lower()][1]
190
325
 
191
326
    def has_header(self, header):
192
 
        "Case-insensitive check for a header"
193
 
        header = header.lower()
194
 
        for key in self.headers.keys():
195
 
            if key.lower() == header:
196
 
                return True
197
 
        return False
198
 
 
199
 
    def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
 
327
        """Case-insensitive check for a header."""
 
328
        return self._headers.has_key(header.lower())
 
329
 
 
330
    __contains__ = has_header
 
331
 
 
332
    def items(self):
 
333
        return self._headers.values()
 
334
 
 
335
    def get(self, header, alternate):
 
336
        return self._headers.get(header.lower(), (None, alternate))[1]
 
337
 
 
338
    def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
 
339
                   domain=None, secure=False):
200
340
        self.cookies[key] = value
201
 
        for var in ('max_age', 'path', 'domain', 'secure', 'expires'):
202
 
            val = locals()[var]
203
 
            if val is not None:
204
 
                self.cookies[key][var.replace('_', '-')] = val
205
 
 
206
 
    def delete_cookie(self, key, path='/', domain=None):
207
 
        self.cookies[key] = ''
 
341
        if max_age is not None:
 
342
            self.cookies[key]['max-age'] = max_age
 
343
        if expires is not None:
 
344
            self.cookies[key]['expires'] = expires
208
345
        if path is not None:
209
346
            self.cookies[key]['path'] = path
210
347
        if domain is not None:
211
348
            self.cookies[key]['domain'] = domain
212
 
        self.cookies[key]['expires'] = 0
213
 
        self.cookies[key]['max-age'] = 0
 
349
        if secure:
 
350
            self.cookies[key]['secure'] = True
 
351
 
 
352
    def delete_cookie(self, key, path='/', domain=None):
 
353
        self.set_cookie(key, max_age=0, path=path, domain=domain,
 
354
                        expires='Thu, 01-Jan-1970 00:00:00 GMT')
214
355
 
215
356
    def _get_content(self):
216
 
        content = ''.join(self._container)
217
 
        if isinstance(content, unicode):
218
 
            content = content.encode(self._charset)
219
 
        return content
 
357
        if self.has_header('Content-Encoding'):
 
358
            return ''.join(self._container)
 
359
        return smart_str(''.join(self._container), self._charset)
220
360
 
221
361
    def _set_content(self, value):
222
362
        self._container = [value]
225
365
    content = property(_get_content, _set_content)
226
366
 
227
367
    def __iter__(self):
228
 
        self._iterator = self._container.__iter__()
 
368
        self._iterator = iter(self._container)
229
369
        return self
230
370
 
231
371
    def next(self):
232
372
        chunk = self._iterator.next()
233
373
        if isinstance(chunk, unicode):
234
374
            chunk = chunk.encode(self._charset)
235
 
        return chunk
 
375
        return str(chunk)
236
376
 
237
377
    def close(self):
238
378
        if hasattr(self._container, 'close'):
242
382
    # See http://docs.python.org/lib/bltin-file-objects.html
243
383
    def write(self, content):
244
384
        if not self._is_string:
245
 
            raise Exception, "This %s instance is not writable" % self.__class__
 
385
            raise Exception("This %s instance is not writable" % self.__class__)
246
386
        self._container.append(content)
247
387
 
248
388
    def flush(self):
250
390
 
251
391
    def tell(self):
252
392
        if not self._is_string:
253
 
            raise Exception, "This %s instance cannot tell its position" % self.__class__
 
393
            raise Exception("This %s instance cannot tell its position" % self.__class__)
254
394
        return sum([len(chunk) for chunk in self._container])
255
395
 
256
396
class HttpResponseRedirect(HttpResponse):
 
397
    status_code = 302
 
398
 
257
399
    def __init__(self, redirect_to):
258
400
        HttpResponse.__init__(self)
259
 
        self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
260
 
        self.status_code = 302
 
401
        self['Location'] = iri_to_uri(redirect_to)
261
402
 
262
403
class HttpResponsePermanentRedirect(HttpResponse):
 
404
    status_code = 301
 
405
 
263
406
    def __init__(self, redirect_to):
264
407
        HttpResponse.__init__(self)
265
 
        self['Location'] = quote(redirect_to, safe=RESERVED_CHARS)
266
 
        self.status_code = 301
 
408
        self['Location'] = iri_to_uri(redirect_to)
267
409
 
268
410
class HttpResponseNotModified(HttpResponse):
269
 
    def __init__(self):
270
 
        HttpResponse.__init__(self)
271
 
        self.status_code = 304
 
411
    status_code = 304
 
412
 
 
413
class HttpResponseBadRequest(HttpResponse):
 
414
    status_code = 400
272
415
 
273
416
class HttpResponseNotFound(HttpResponse):
274
 
    def __init__(self, *args, **kwargs):
275
 
        HttpResponse.__init__(self, *args, **kwargs)
276
 
        self.status_code = 404
 
417
    status_code = 404
277
418
 
278
419
class HttpResponseForbidden(HttpResponse):
279
 
    def __init__(self, *args, **kwargs):
280
 
        HttpResponse.__init__(self, *args, **kwargs)
281
 
        self.status_code = 403
 
420
    status_code = 403
282
421
 
283
422
class HttpResponseNotAllowed(HttpResponse):
 
423
    status_code = 405
 
424
 
284
425
    def __init__(self, permitted_methods):
285
426
        HttpResponse.__init__(self)
286
427
        self['Allow'] = ', '.join(permitted_methods)
287
 
        self.status_code = 405
288
428
 
289
429
class HttpResponseGone(HttpResponse):
 
430
    status_code = 410
 
431
 
290
432
    def __init__(self, *args, **kwargs):
291
433
        HttpResponse.__init__(self, *args, **kwargs)
292
 
        self.status_code = 410
293
434
 
294
435
class HttpResponseServerError(HttpResponse):
 
436
    status_code = 500
 
437
 
295
438
    def __init__(self, *args, **kwargs):
296
439
        HttpResponse.__init__(self, *args, **kwargs)
297
 
        self.status_code = 500
298
440
 
 
441
# A backwards compatible alias for HttpRequest.get_host.
299
442
def get_host(request):
300
 
    "Gets the HTTP host from the environment or request headers."
301
 
    host = request.META.get('HTTP_X_FORWARDED_HOST', '')
302
 
    if not host:
303
 
        host = request.META.get('HTTP_HOST', '')
304
 
    return host
 
443
    return request.get_host()
 
444
 
 
445
# It's neither necessary nor appropriate to use
 
446
# django.utils.encoding.smart_unicode for parsing URLs and form inputs. Thus,
 
447
# this slightly more restricted function.
 
448
def str_to_unicode(s, encoding):
 
449
    """
 
450
    Converts basestring objects to unicode, using the given encoding. Illegally
 
451
    encoded input characters are replaced with Unicode "unknown" codepoint
 
452
    (\ufffd).
 
453
 
 
454
    Returns any non-basestring objects without change.
 
455
    """
 
456
    if isinstance(s, str):
 
457
        return unicode(s, encoding, 'replace')
 
458
    else:
 
459
        return s
 
460