diff options
| author | Santiago Basulto <santiago.basulto@gmail.com> | 2018-05-04 20:37:01 -0300 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2019-01-16 13:38:47 -0500 |
| commit | 4fc35a9c3efdc9154efce28cb23cb84f8834517e (patch) | |
| tree | a4f7b10244cb933d827cf72ef57dc11e68c1a6ca /django | |
| parent | aa5d0a5a90a690dc6f8fdbbffba143e32c86e40a (diff) | |
Fixed #20147 -- Added HttpRequest.headers.
Diffstat (limited to 'django')
| -rw-r--r-- | django/http/request.py | 30 | ||||
| -rw-r--r-- | django/utils/datastructures.py | 59 |
2 files changed, 88 insertions, 1 deletions
diff --git a/django/http/request.py b/django/http/request.py index 7dc758d268..02a127d664 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -12,7 +12,9 @@ from django.core.exceptions import ( ) from django.core.files import uploadhandler from django.http.multipartparser import MultiPartParser, MultiPartParserError -from django.utils.datastructures import ImmutableList, MultiValueDict +from django.utils.datastructures import ( + CaseInsensitiveMapping, ImmutableList, MultiValueDict, +) from django.utils.deprecation import RemovedInDjango30Warning from django.utils.encoding import escape_uri_path, iri_to_uri from django.utils.functional import cached_property @@ -65,6 +67,10 @@ class HttpRequest: return '<%s>' % self.__class__.__name__ return '<%s: %s %r>' % (self.__class__.__name__, self.method, self.get_full_path()) + @cached_property + def headers(self): + return HttpHeaders(self.META) + def _get_raw_host(self): """ Return the HTTP host using the environment or request headers. Skip @@ -359,6 +365,28 @@ class HttpRequest: return list(self) +class HttpHeaders(CaseInsensitiveMapping): + HTTP_PREFIX = 'HTTP_' + # PEP 333 gives two headers which aren't prepended with HTTP_. + UNPREFIXED_HEADERS = {'CONTENT_TYPE', 'CONTENT_LENGTH'} + + def __init__(self, environ): + headers = {} + for header, value in environ.items(): + name = self.parse_header_name(header) + if name: + headers[name] = value + super().__init__(headers) + + @classmethod + def parse_header_name(cls, header): + if header.startswith(cls.HTTP_PREFIX): + header = header[len(cls.HTTP_PREFIX):] + elif header not in cls.UNPREFIXED_HEADERS: + return None + return header.replace('_', '-').title() + + class QueryDict(MultiValueDict): """ A specialized MultiValueDict which represents a query string. diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index c5bda0056a..191c2348f6 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -1,5 +1,6 @@ import copy from collections import OrderedDict +from collections.abc import Mapping class OrderedSet: @@ -280,3 +281,61 @@ class DictWrapper(dict): if use_func: return self.func(value) return value + + +def _destruct_iterable_mapping_values(data): + for i, elem in enumerate(data): + if len(elem) != 2: + raise ValueError( + 'dictionary update sequence element #{} has ' + 'length {}; 2 is required.'.format(i, len(elem)) + ) + if not isinstance(elem[0], str): + raise ValueError('Element key %r invalid, only strings are allowed' % elem[0]) + yield tuple(elem) + + +class CaseInsensitiveMapping(Mapping): + """ + Mapping allowing case-insensitive key lookups. Original case of keys is + preserved for iteration and string representation. + + Example:: + + >>> ci_map = CaseInsensitiveMapping({'name': 'Jane'}) + >>> ci_map['Name'] + Jane + >>> ci_map['NAME'] + Jane + >>> ci_map['name'] + Jane + >>> ci_map # original case preserved + {'name': 'Jane'} + """ + + def __init__(self, data): + if not isinstance(data, Mapping): + data = {k: v for k, v in _destruct_iterable_mapping_values(data)} + self._store = {k.lower(): (k, v) for k, v in data.items()} + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __len__(self): + return len(self._store) + + def __eq__(self, other): + return isinstance(other, Mapping) and { + k.lower(): v for k, v in self.items() + } == { + k.lower(): v for k, v in other.items() + } + + def __iter__(self): + return (original_key for original_key, value in self._store.values()) + + def __repr__(self): + return repr({key: value for key, value in self._store.values()}) + + def copy(self): + return self |
