diff options
Diffstat (limited to 'djangorestframework/compat.py')
| -rw-r--r-- | djangorestframework/compat.py | 480 |
1 files changed, 0 insertions, 480 deletions
diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py deleted file mode 100644 index f21dc4ef..00000000 --- a/djangorestframework/compat.py +++ /dev/null @@ -1,480 +0,0 @@ -""" -The :mod:`compat` module provides support for backwards compatibility with older versions of django/python. -""" -import django - -# cStringIO only if it's available, otherwise StringIO -try: - import cStringIO as StringIO -except ImportError: - import StringIO - - -# parse_qs from 'urlparse' module unless python 2.5, in which case from 'cgi' -try: - # python >= 2.6 - from urlparse import parse_qs -except ImportError: - # python < 2.6 - from cgi import parse_qs - - -# django.test.client.RequestFactory (Required for Django < 1.3) -try: - from django.test.client import RequestFactory -except ImportError: - from django.test import Client - from django.core.handlers.wsgi import WSGIRequest - - # From: http://djangosnippets.org/snippets/963/ - # Lovely stuff - class RequestFactory(Client): - """ - Class that lets you create mock :obj:`Request` objects for use in testing. - - Usage:: - - rf = RequestFactory() - get_request = rf.get('/hello/') - post_request = rf.post('/submit/', {'foo': 'bar'}) - - This class re-uses the :class:`django.test.client.Client` interface. Of which - you can find the docs here__. - - __ http://www.djangoproject.com/documentation/testing/#the-test-client - - Once you have a `request` object you can pass it to any :func:`view` function, - just as if that :func:`view` had been hooked up using a URLconf. - """ - def request(self, **request): - """ - Similar to parent class, but returns the :obj:`request` object as soon as it - has created it. - """ - environ = { - 'HTTP_COOKIE': self.cookies, - 'PATH_INFO': '/', - 'QUERY_STRING': '', - 'REQUEST_METHOD': 'GET', - 'SCRIPT_NAME': '', - 'SERVER_NAME': 'testserver', - 'SERVER_PORT': 80, - 'SERVER_PROTOCOL': 'HTTP/1.1', - } - environ.update(self.defaults) - environ.update(request) - return WSGIRequest(environ) - -# django.views.generic.View (Django >= 1.3) -try: - from django.views.generic import View - if not hasattr(View, 'head'): - # First implementation of Django class-based views did not include head method - # in base View class - https://code.djangoproject.com/ticket/15668 - class ViewPlusHead(View): - def head(self, request, *args, **kwargs): - return self.get(request, *args, **kwargs) - View = ViewPlusHead - -except ImportError: - from django import http - from django.utils.functional import update_wrapper - # from django.utils.log import getLogger - # from django.utils.decorators import classonlymethod - - # logger = getLogger('django.request') - We'll just drop support for logger if running Django <= 1.2 - # Might be nice to fix this up sometime to allow djangorestframework.compat.View to match 1.3's View more closely - - class View(object): - """ - Intentionally simple parent class for all views. Only implements - dispatch-by-method and simple sanity checking. - """ - - http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'] - - def __init__(self, **kwargs): - """ - Constructor. Called in the URLconf; can contain helpful extra - keyword arguments, and other things. - """ - # Go through keyword arguments, and either save their values to our - # instance, or raise an error. - for key, value in kwargs.iteritems(): - setattr(self, key, value) - - # @classonlymethod - We'll just us classmethod instead if running Django <= 1.2 - @classmethod - def as_view(cls, **initkwargs): - """ - Main entry point for a request-response process. - """ - # sanitize keyword arguments - for key in initkwargs: - if key in cls.http_method_names: - raise TypeError(u"You tried to pass in the %s method name as a " - u"keyword argument to %s(). Don't do that." - % (key, cls.__name__)) - if not hasattr(cls, key): - raise TypeError(u"%s() received an invalid keyword %r" % ( - cls.__name__, key)) - - def view(request, *args, **kwargs): - self = cls(**initkwargs) - return self.dispatch(request, *args, **kwargs) - - # take name and docstring from class - update_wrapper(view, cls, updated=()) - - # and possible attributes set by decorators - # like csrf_exempt from dispatch - update_wrapper(view, cls.dispatch, assigned=()) - return view - - def dispatch(self, request, *args, **kwargs): - # Try to dispatch to the right method; if a method doesn't exist, - # defer to the error handler. Also defer to the error handler if the - # request method isn't on the approved list. - if request.method.lower() in self.http_method_names: - handler = getattr(self, request.method.lower(), self.http_method_not_allowed) - else: - handler = self.http_method_not_allowed - self.request = request - self.args = args - self.kwargs = kwargs - return handler(request, *args, **kwargs) - - def http_method_not_allowed(self, request, *args, **kwargs): - allowed_methods = [m for m in self.http_method_names if hasattr(self, m)] - #logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path), - # extra={ - # 'status_code': 405, - # 'request': self.request - # } - #) - return http.HttpResponseNotAllowed(allowed_methods) - - def head(self, request, *args, **kwargs): - return self.get(request, *args, **kwargs) - -# PUT, DELETE do not require CSRF until 1.4. They should. Make it better. -if django.VERSION >= (1, 4): - from django.middleware.csrf import CsrfViewMiddleware -else: - import hashlib - import re - import random - import logging - import urlparse - - from django.conf import settings - from django.core.urlresolvers import get_callable - - try: - from logging import NullHandler - except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - - logger = logging.getLogger('django.request') - - if not logger.handlers: - logger.addHandler(NullHandler()) - - def same_origin(url1, url2): - """ - Checks if two URLs are 'same-origin' - """ - p1, p2 = urlparse.urlparse(url1), urlparse.urlparse(url2) - return p1[0:2] == p2[0:2] - - def constant_time_compare(val1, val2): - """ - Returns True if the two strings are equal, False otherwise. - - The time taken is independent of the number of characters that match. - """ - if len(val1) != len(val2): - return False - result = 0 - for x, y in zip(val1, val2): - result |= ord(x) ^ ord(y) - return result == 0 - - # Use the system (hardware-based) random number generator if it exists. - if hasattr(random, 'SystemRandom'): - randrange = random.SystemRandom().randrange - else: - randrange = random.randrange - _MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 - - REASON_NO_REFERER = "Referer checking failed - no Referer." - REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." - REASON_NO_CSRF_COOKIE = "CSRF cookie not set." - REASON_BAD_TOKEN = "CSRF token missing or incorrect." - - def _get_failure_view(): - """ - Returns the view to be used for CSRF rejections - """ - return get_callable(settings.CSRF_FAILURE_VIEW) - - def _get_new_csrf_key(): - return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() - - def get_token(request): - """ - Returns the the CSRF token required for a POST form. The token is an - alphanumeric value. - - A side effect of calling this function is to make the the csrf_protect - decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' - header to the outgoing response. For this reason, you may need to use this - function lazily, as is done by the csrf context processor. - """ - request.META["CSRF_COOKIE_USED"] = True - return request.META.get("CSRF_COOKIE", None) - - def _sanitize_token(token): - # Allow only alphanum, and ensure we return a 'str' for the sake of the post - # processing middleware. - token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) - if token == "": - # In case the cookie has been truncated to nothing at some point. - return _get_new_csrf_key() - else: - return token - - class CsrfViewMiddleware(object): - """ - Middleware that requires a present and correct csrfmiddlewaretoken - for POST requests that have a CSRF cookie, and sets an outgoing - CSRF cookie. - - This middleware should be used in conjunction with the csrf_token template - tag. - """ - # The _accept and _reject methods currently only exist for the sake of the - # requires_csrf_token decorator. - def _accept(self, request): - # Avoid checking the request twice by adding a custom attribute to - # request. This will be relevant when both decorator and middleware - # are used. - request.csrf_processing_done = True - return None - - def _reject(self, request, reason): - return _get_failure_view()(request, reason=reason) - - def process_view(self, request, callback, callback_args, callback_kwargs): - - if getattr(request, 'csrf_processing_done', False): - return None - - try: - csrf_token = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) - # Use same token next time - request.META['CSRF_COOKIE'] = csrf_token - except KeyError: - csrf_token = None - # Generate token and store it in the request, so it's available to the view. - request.META["CSRF_COOKIE"] = _get_new_csrf_key() - - # Wait until request.META["CSRF_COOKIE"] has been manipulated before - # bailing out, so that get_token still works - if getattr(callback, 'csrf_exempt', False): - return None - - # Assume that anything not defined as 'safe' by RC2616 needs protection. - if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): - if getattr(request, '_dont_enforce_csrf_checks', False): - # Mechanism to turn off CSRF checks for test suite. It comes after - # the creation of CSRF cookies, so that everything else continues to - # work exactly the same (e.g. cookies are sent etc), but before the - # any branches that call reject() - return self._accept(request) - - if request.is_secure(): - # Suppose user visits http://example.com/ - # An active network attacker,(man-in-the-middle, MITM) sends a - # POST form which targets https://example.com/detonate-bomb/ and - # submits it via javascript. - # - # The attacker will need to provide a CSRF cookie and token, but - # that is no problem for a MITM and the session independent - # nonce we are using. So the MITM can circumvent the CSRF - # protection. This is true for any HTTP connection, but anyone - # using HTTPS expects better! For this reason, for - # https://example.com/ we need additional protection that treats - # http://example.com/ as completely untrusted. Under HTTPS, - # Barth et al. found that the Referer header is missing for - # same-domain requests in only about 0.2% of cases or less, so - # we can use strict Referer checking. - referer = request.META.get('HTTP_REFERER') - if referer is None: - logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_NO_REFERER) - - # Note that request.get_host() includes the port - good_referer = 'https://%s/' % request.get_host() - if not same_origin(referer, good_referer): - reason = REASON_BAD_REFERER % (referer, good_referer) - logger.warning('Forbidden (%s): %s' % (reason, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, reason) - - if csrf_token is None: - # No CSRF cookie. For POST requests, we insist on a CSRF cookie, - # and in this way we can avoid all CSRF attacks, including login - # CSRF. - logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_NO_CSRF_COOKIE) - - # check non-cookie token for match - request_csrf_token = "" - if request.method == "POST": - request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') - - if request_csrf_token == "": - # Fall back to X-CSRFToken, to make things easier for AJAX, - # and possible for PUT/DELETE - request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '') - - if not constant_time_compare(request_csrf_token, csrf_token): - logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path), - extra={ - 'status_code': 403, - 'request': request, - } - ) - return self._reject(request, REASON_BAD_TOKEN) - - return self._accept(request) - -# timezone support is new in Django 1.4 -try: - from django.utils import timezone -except ImportError: - timezone = None - -# dateparse is ALSO new in Django 1.4 -try: - from django.utils.dateparse import parse_date, parse_datetime -except ImportError: - import datetime - import re - - date_re = re.compile( - r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})$' - ) - - datetime_re = re.compile( - r'(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})' - r'[T ](?P<hour>\d{1,2}):(?P<minute>\d{1,2})' - r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?' - r'(?P<tzinfo>Z|[+-]\d{1,2}:\d{1,2})?$' - ) - - time_re = re.compile( - r'(?P<hour>\d{1,2}):(?P<minute>\d{1,2})' - r'(?::(?P<second>\d{1,2})(?:\.(?P<microsecond>\d{1,6})\d{0,6})?)?' - ) - - def parse_date(value): - match = date_re.match(value) - if match: - kw = dict((k, int(v)) for k, v in match.groupdict().iteritems()) - return datetime.date(**kw) - - def parse_time(value): - match = time_re.match(value) - if match: - kw = match.groupdict() - if kw['microsecond']: - kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) - return datetime.time(**kw) - - def parse_datetime(value): - """Parse datetime, but w/o the timezone awareness in 1.4""" - match = datetime_re.match(value) - if match: - kw = match.groupdict() - if kw['microsecond']: - kw['microsecond'] = kw['microsecond'].ljust(6, '0') - kw = dict((k, int(v)) for k, v in kw.iteritems() if v is not None) - return datetime.datetime(**kw) - -# Markdown is optional -try: - import markdown - - def apply_markdown(text): - """ - Simple wrapper around :func:`markdown.markdown` to set the base level - of '#' style headers to <h2>. - """ - - extensions = ['headerid(level=2)'] - safe_mode = False, - md = markdown.Markdown(extensions=extensions, safe_mode=safe_mode) - return md.convert(text) - -except ImportError: - apply_markdown = None - - -# Yaml is optional -try: - import yaml -except ImportError: - yaml = None - - -import unittest -try: - import unittest.skip -except ImportError: # python < 2.7 - from unittest import TestCase - import functools - - def skip(reason): - # Pasted from py27/lib/unittest/case.py - """ - Unconditionally skip a test. - """ - def decorator(test_item): - if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): - @functools.wraps(test_item) - def skip_wrapper(*args, **kwargs): - pass - test_item = skip_wrapper - - test_item.__unittest_skip__ = True - test_item.__unittest_skip_why__ = reason - return test_item - return decorator - - unittest.skip = skip - - -# xml.etree.parse only throws ParseError for python >= 2.7 -try: - from xml.etree import ParseError as ETParseError -except ImportError: # python < 2.7 - ETParseError = None |
