Remove misleading Content-Length.
[django-ssify.git] / ssify / middleware_debug.py
index 2bd4fa0..f463d53 100644 (file)
@@ -1,15 +1,33 @@
+# -*- coding: utf-8 -*-
+# This file is part of django-ssify, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See README.md for more information.
+#
 """
 This module should only be used for debugging SSI statements.
 
 """
 This module should only be used for debugging SSI statements.
 
-Using DebugUnSsiMiddleware in production defeats the purpose of using SSI
+Using SsiRenderMiddleware in production defeats the purpose of using SSI
 in the first place, and is unsafe. You should use a proper webserver with SSI
 support as a proxy (i.e. Nginx with ssi=on).
 
 """
 in the first place, and is unsafe. You should use a proper webserver with SSI
 support as a proxy (i.e. Nginx with ssi=on).
 
 """
+from __future__ import unicode_literals
 import re
 import re
-import urlparse
+
+try:
+    from urllib.parse import urlparse
+except ImportError:
+    from urlparse import urlparse
+
+try:
+    from django.urls import NoReverseMatch, reverse, resolve
+except ImportError:
+    # Django < 2
+    from django.core.urlresolvers import NoReverseMatch, reverse, resolve
+
 from django.core.urlresolvers import resolve
 from django.core.urlresolvers import resolve
-from ssify import DEBUG_VERBOSE
+from .cache import get_caches
+
+from .conf import conf
 
 
 SSI_SET = re.compile(r"<!--#set var='(?P<var>[^']+)' "
 
 
 SSI_SET = re.compile(r"<!--#set var='(?P<var>[^']+)' "
@@ -19,20 +37,21 @@ SSI_INCLUDE = re.compile(r"<!--#include (?:virtual|file)='(?P<path>[^']+)'-->")
 SSI_IF = re.compile(r"(?P<header><!--#if expr='(?P<expr>[^']*)'-->)"
                     r"(?P<value>.*?)(?:<!--#else-->(?P<else>.*?))?"
                     r"<!--#endif-->", re.S)
 SSI_IF = re.compile(r"(?P<header><!--#if expr='(?P<expr>[^']*)'-->)"
                     r"(?P<value>.*?)(?:<!--#else-->(?P<else>.*?))?"
                     r"<!--#endif-->", re.S)
-        # TODO: escaped?
-SSI_VAR = re.compile(r"\$\{(?P<var>.+)\}")  # TODO: escaped?
+SSI_VAR = re.compile(r"\$\{(?P<var>.+)\}")
+
+UNESCAPE = re.compile(r'\\(.)')
 
 
 
 
-class DebugUnSsiMiddleware(object):
+class SsiRenderMiddleware(object):
     """
     Emulates a webserver with SSI support.
 
     This middleware should only be used for debugging purposes.
     """
     Emulates a webserver with SSI support.
 
     This middleware should only be used for debugging purposes.
-    SsiMiddleware will enable it automatically, if SSIFY_DEBUG setting
+    SsiMiddleware will enable it automatically, if SSIFY_RENDER setting
     is set to True, so you don't normally need to include it in
     MIDDLEWARE_CLASSES.
 
     is set to True, so you don't normally need to include it in
     MIDDLEWARE_CLASSES.
 
-    If SSIFY_DEBUG_VERBOSE setting is True, it will also leave some
+    If SSIFY_RENDER_VERBOSE setting is True, it will also leave some
     information in HTML comments.
 
     """
     information in HTML comments.
 
     """
@@ -42,14 +61,29 @@ class DebugUnSsiMiddleware(object):
         def ssi_include(match):
             """Replaces SSI include with contents rendered by relevant view."""
             path = process_value(match.group('path'))
         def ssi_include(match):
             """Replaces SSI include with contents rendered by relevant view."""
             path = process_value(match.group('path'))
-            func, args, kwargs = resolve(path)
-            parsed = urlparse.urlparse(path)
-            request.META['PATH_INFO'] = request.path_info = \
-                request.path = parsed.path
-            request.META['QUERY_STRING'] = parsed.query
-            content = func(request, *args, **kwargs).content
-            content = process_content(content)
-            if DEBUG_VERBOSE:
+            content = None
+            for cache in get_caches():
+                content = cache.get(path)
+                if content is not None:
+                    break
+            if content is None:
+                func, args, kwargs = resolve(path)
+                parsed = urlparse(path)
+
+                # Reuse the original request, but reset some attributes.
+                request.META['PATH_INFO'] = request.path_info = \
+                    request.path = parsed.path
+                request.META['QUERY_STRING'] = parsed.query
+                request.ssi_vars_needed = {}
+
+                subresponse = func(request, *args, **kwargs)
+                # FIXME: we should deal directly with bytes here.
+                if subresponse.streaming:
+                    content = b"".join(subresponse.streaming_content)
+                else:
+                    content = subresponse.content
+            content = process_content(content.decode('utf-8'))
+            if conf.RENDER_VERBOSE:
                 return "".join((
                     match.group(0),
                     content,
                 return "".join((
                     match.group(0),
                     content,
@@ -60,16 +94,18 @@ class DebugUnSsiMiddleware(object):
 
         def ssi_set(match):
             """Interprets SSI set statement."""
 
         def ssi_set(match):
             """Interprets SSI set statement."""
-            variables[match.group('var')] = match.group('value')
-            if DEBUG_VERBOSE:
-                return match.group(0)
+            content = match.group('value')
+            content = re.sub(UNESCAPE, r'\1', content)
+            variables[match.group('var')] = content
+            if conf.RENDER_VERBOSE:
+                return content
             else:
                 return ""
 
         def ssi_echo(match):
             """Interprets SSI echo, outputting the value of the variable."""
             content = variables[match.group('var')]
             else:
                 return ""
 
         def ssi_echo(match):
             """Interprets SSI echo, outputting the value of the variable."""
             content = variables[match.group('var')]
-            if DEBUG_VERBOSE:
+            if conf.RENDER_VERBOSE:
                 return "".join((
                     match.group(0),
                     content,
                 return "".join((
                     match.group(0),
                     content,
@@ -84,8 +120,8 @@ class DebugUnSsiMiddleware(object):
             if expr:
                 content = match.group('value')
             else:
             if expr:
                 content = match.group('value')
             else:
-                content = match.group('else')
-            if DEBUG_VERBOSE:
+                content = match.group('else') or ''
+            if conf.RENDER_VERBOSE:
                 return "".join((
                     match.group('header'),
                     content,
                 return "".join((
                     match.group('header'),
                     content,
@@ -111,11 +147,14 @@ class DebugUnSsiMiddleware(object):
             return content
 
         variables = {}
             return content
 
         variables = {}
-        response.content = process_content(response.content)
+        response.content = process_content(
+            response.content.decode('utf-8')).encode('utf-8')
         response['Content-Length'] = len(response.content)
 
     def process_response(self, request, response):
         """Support for unrendered responses."""
         response['Content-Length'] = len(response.content)
 
     def process_response(self, request, response):
         """Support for unrendered responses."""
+        if response.streaming:
+            return response
         if hasattr(response, 'render') and callable(response.render):
             response.add_post_render_callback(
                 lambda r: self._process_rendered_response(request, r)
         if hasattr(response, 'render') and callable(response.render):
             response.add_post_render_callback(
                 lambda r: self._process_rendered_response(request, r)