import re

SECURE_GET_VARIABLE = 'FAKE_HTTPS'
HTTPS_PATTERN = re.compile(r'https://([\w./\-:\?=%_]+)', re.IGNORECASE)
FAKE_HTTPS_PATTERN = re.compile(
    r'http://([\w./\-:\?=%%]+)[\?&]%s=1' % SECURE_GET_VARIABLE, re.IGNORECASE)
ATTRIBUTE_NAMES = ('href', 'action')
ATTRIBUTE_PATTERN = re.compile(
    r"""(?P<attribute_name>%s)\s*="""
    r"""\s*(?P<quote_delimiter>["'])(?P<attribute_value>.*?[^\\])\2""" %
    '|'.join(ATTRIBUTE_NAMES), re.IGNORECASE)
ATTRIBUTE_FORMAT = (
    "%(attribute_name)s="
    "%(quote_delimiter)c%(attribute_value)s%(quote_delimiter)c")

# note that SECURE_GET_VARIABLE is currently assume to be at the end of
# the query string. I have not been bitten by this assumption yet...

def secure_url(url):
    """ Adds SECURE_GET_VARIABLE to query string within url if absent.

    >>> secure_url('http://localhost:8000')
    'http://localhost:8000?FAKE_HTTPS=1'
    >>> secure_url('http://localhost:8000?test=true')
    'http://localhost:8000?test=true&FAKE_HTTPS=1'
    >>> secure_url('http://localhost:8000?test=true&FAKE_HTTPS=1')
    'http://localhost:8000?test=true&FAKE_HTTPS=1'
    >>> secure_url('http://localhost:8000?FAKE_HTTPS=1')
    'http://localhost:8000?FAKE_HTTPS=1'
    >>> url = 'http://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1'
    >>> secure_url(url)
    'http://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1&FAKE_HTTPS=1'
    """
    if FAKE_HTTPS_PATTERN.match(url):
        return url # unnecessary
    if '?' in url:
        joiner = '&'
    else:
        joiner = '?'
    return '%s%c%s=1' % (url, joiner, SECURE_GET_VARIABLE)

def https_to_fake(string):
    """ Convert https:// URL to http:// with SECURE_GET_VARIABLE.

    >>> https_to_fake('https://localhost:8000')
    'http://localhost:8000?FAKE_HTTPS=1'
    >>> https_to_fake('https://localhost:8000?existing_variable=1')
    'http://localhost:8000?existing_variable=1&FAKE_HTTPS=1'
    >>> url = 'https://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1'
    >>> https_to_fake(url)
    'http://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1&FAKE_HTTPS=1'
    """
    def _secure_url(match):
        return secure_url('http://%s' % match.groups()[0])
    return HTTPS_PATTERN.sub(_secure_url, string)

def fake_to_https(string):
    """ Convert http:// URL to https:// if contains SECURE_GET_VARIABLE.

    >>> fake_to_https('http://localhost:8000?FAKE_HTTPS=1')
    'https://localhost:8000'
    >>> fake_to_https('http://localhost:8000?existing_variable=1&FAKE_HTTPS=1')
    'https://localhost:8000?existing_variable=1'
    >>> url = 'http://localhost:8000/go/?next=/%3FFAKE_HTTPS%3D1&FAKE_HTTPS=1'
    >>> fake_to_https(url)
    'https://localhost:8000/go/?next=/%3FFAKE_HTTPS%3D1'
    """
    return FAKE_HTTPS_PATTERN.sub(r'https://\1', string)

def secure_relative_urls(string):
    """ Convert add SECURE_GET_VARIABLE to relative URLs in href attributes.

    >>> secure_relative_urls('... href="/test" ...')
    '... href="/test?FAKE_HTTPS=1" ...'
    >>> secure_relative_urls('... href="http://example.com" ...')
    '... href="http://example.com" ...'
    >>> secure_relative_urls('... href="/test" ...')
    '... href="/test?FAKE_HTTPS=1" ...'
    """
    def _secure_relative_url(match):
        params = match.groupdict()
        if params['attribute_value'].find('://') >= 0:
            pass # ignore absolute URLs
        else:
            params['attribute_value'] = secure_url(params['attribute_value'])
        return ATTRIBUTE_FORMAT % params
    return ATTRIBUTE_PATTERN.sub(_secure_relative_url, string)

class FakeSSLMiddleware(object):
    """ Fake SSL For local/testing environments. """
    def __init__(self, *args, **kwargs):
        if not settings.DEBUG and not getattr(settings, 'TESTING', False):
            raise RuntimeError, "Disable on production site!"

    def process_request(self, request):
        """ Hack request object to fake security.

        - replace is_secure method with test for SECURE_GET_VARIABLE in GET
        - remove SECURE_GET_VARIABLE from query string
        - undo build_absolute_uri method's work of adding https:// to
          relative URIs where request.is_secure...
          (see django.http.utils.fix_location_header)
        - make HTTP_REFERER look secure if contained SECURE_GET_VARIABLE,
          so that CSRF doesn't catch us out.
        """

        GET = request.GET.copy() # immutable QueryDict
        secure = bool(GET.pop(SECURE_GET_VARIABLE, False))
        request.GET = GET
        request.is_secure = lambda: secure

        request.environ['QUERY_STRING'] = \
            re.sub(r'&?%s=1' % SECURE_GET_VARIABLE,'',
                   request.environ['QUERY_STRING'])

        setattr(request,'_build_absolute_uri', request.build_absolute_uri)
        def build_http_only_uri(location=None):
            uri = request._build_absolute_uri(location)
            return https_to_fake(uri)
        request.build_absolute_uri = build_http_only_uri

        if 'HTTP_REFERER' in request.META:
            request.META['HTTP_REFERER'] = \
                fake_to_https(request.META['HTTP_REFERER'])

    def process_response(self, request, response):
        """ Replace https:// with http:// within HTML responses + redirects """

        if response.status_code in (301, 302):
            headers = response._headers.copy() # immutable QueryDict
            headers['location'] = (
                'Location', https_to_fake(response['Location']))
            response._headers = headers
        elif response.status_code == 200:
            if response['Content-Type'].find('html') >= 0:
                try:
                    decoded_content = response.content.decode('utf-8')
                except UnicodeDecodeError:
                    decoded_content = response.content
                response.content = \
                    https_to_fake(decoded_content).encode('utf-8')
                if request.is_secure():
                    response.content = secure_relative_urls(response.content)
        return response