1 # -*- coding: utf-8 -*-
2 # This file is part of django-ssify, licensed under GNU Affero GPLv3 or later.
3 # Copyright © Fundacja Nowoczesna Polska. See README.md for more information.
6 This module should only be used for debugging SSI statements.
8 Using SsiRenderMiddleware in production defeats the purpose of using SSI
9 in the first place, and is unsafe. You should use a proper webserver with SSI
10 support as a proxy (i.e. Nginx with ssi=on).
13 from __future__ import unicode_literals
16 from urllib.parse import urlparse
18 from urlparse import urlparse
19 from django.core.urlresolvers import resolve
20 from .cache import get_caches
22 from .conf import conf
25 SSI_SET = re.compile(r"<!--#set var='(?P<var>[^']+)' "
26 r"value='(?P<value>|\\\\|.*?[^\\](?:\\\\)*)'-->", re.S)
27 SSI_ECHO = re.compile(r"<!--#echo var='(?P<var>[^']+)' encoding='none'-->")
28 SSI_INCLUDE = re.compile(r"<!--#include (?:virtual|file)='(?P<path>[^']+)'-->")
29 SSI_IF = re.compile(r"(?P<header><!--#if expr='(?P<expr>[^']*)'-->)"
30 r"(?P<value>.*?)(?:<!--#else-->(?P<else>.*?))?"
31 r"<!--#endif-->", re.S)
32 SSI_VAR = re.compile(r"\$\{(?P<var>.+)\}")
34 UNESCAPE = re.compile(r'\\(.)')
37 class SsiRenderMiddleware(object):
39 Emulates a webserver with SSI support.
41 This middleware should only be used for debugging purposes.
42 SsiMiddleware will enable it automatically, if SSIFY_RENDER setting
43 is set to True, so you don't normally need to include it in
46 If SSIFY_RENDER_VERBOSE setting is True, it will also leave some
47 information in HTML comments.
51 def _process_rendered_response(request, response):
52 """Recursively process SSI statements in the response."""
53 def ssi_include(match):
54 """Replaces SSI include with contents rendered by relevant view."""
55 path = process_value(match.group('path'))
57 for cache in get_caches():
58 content = cache.get(path)
59 if content is not None:
62 func, args, kwargs = resolve(path)
63 parsed = urlparse(path)
65 # Reuse the original request, but reset some attributes.
66 request.META['PATH_INFO'] = request.path_info = \
67 request.path = parsed.path
68 request.META['QUERY_STRING'] = parsed.query
69 request.ssi_vars_needed = {}
71 subresponse = func(request, *args, **kwargs)
72 # FIXME: we should deal directly with bytes here.
73 if subresponse.streaming:
74 content = b"".join(subresponse.streaming_content)
76 content = subresponse.content
77 content = process_content(content.decode('utf-8'))
78 if conf.RENDER_VERBOSE:
82 match.group(0).replace('<!--#', '<!--#end-'),
88 """Interprets SSI set statement."""
89 content = match.group('value')
90 content = re.sub(UNESCAPE, r'\1', content)
91 variables[match.group('var')] = content
92 if conf.RENDER_VERBOSE:
98 """Interprets SSI echo, outputting the value of the variable."""
99 content = variables[match.group('var')]
100 if conf.RENDER_VERBOSE:
104 match.group(0).replace('<!--#', '<!--#end-'),
110 """Interprets SSI if statement."""
111 expr = process_value(match.group('expr'))
113 content = match.group('value')
115 content = match.group('else') or ''
116 if conf.RENDER_VERBOSE:
118 match.group('header'),
120 match.group('header').replace('<!--#', '<!--#end-'),
126 """Resolves ${var}-style variable reference."""
127 return variables[match.group('var')]
129 def process_value(content):
130 """Resolves any ${var}-style variable references in the content."""
131 return re.sub(SSI_VAR, ssi_var, content)
133 def process_content(content):
134 """Interprets SSI statements in the content."""
135 content = re.sub(SSI_SET, ssi_set, content)
136 content = re.sub(SSI_ECHO, ssi_echo, content)
137 content = re.sub(SSI_IF, ssi_if, content)
138 content = re.sub(SSI_INCLUDE, ssi_include, content)
142 response.content = process_content(
143 response.content.decode('utf-8')).encode('utf-8')
144 response['Content-Length'] = len(response.content)
146 def process_response(self, request, response):
147 """Support for unrendered responses."""
148 if response.streaming:
150 if hasattr(response, 'render') and callable(response.render):
151 response.add_post_render_callback(
152 lambda r: self._process_rendered_response(request, r)
155 self._process_rendered_response(request, response)