From 5cd508ffe0d7eaadd6516fc35137c93d1d6577a9 Mon Sep 17 00:00:00 2001
From: Mike Bayer <mike_mp@zzzcomputing.com>
Date: Tue, 27 Sep 2011 20:22:49 -0400
Subject: [PATCH] - A Template is explicitly disallowed   from having a url
 that normalizes to relative outside   of the root.   That is, if the Lookup
 is based   at /home/mytemplates, an include that would place   the ultimate
 template at   /home/mytemplates/../some_other_directory,   i.e. outside of
 /home/mytemplates,   is disallowed.   This usage was never intended   despite
 the lack of an explicit check.   The main issue this causes   is that module
 files can be written outside   of the module root (or raise an error, if file
 perms aren't   set up), and can also lead to the same template being   cached
 in the lookup under multiple, relative roots.   TemplateLookup instead has
 always supported multiple   file roots for this purpose.   [ticket:174]

---
 CHANGES                             | 19 +++++++++++++++++
 mako/__init__.py                    |  2 +-
 mako/lookup.py                      |  2 +-
 mako/template.py                    | 18 ++++++++++------
 test/templates/othersubdir/foo.html |  0
 test/test_lookup.py                 | 32 +++++++++++++++++++++++++++--
 test/test_template.py               | 23 ++++++++++++++++++---
 7 files changed, 83 insertions(+), 13 deletions(-)
 create mode 100644 test/templates/othersubdir/foo.html

diff --git a/CHANGES b/CHANGES
index a6bae66..01ec4dc 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,22 @@
+0.5
+- A Template is explicitly disallowed
+  from having a url that normalizes to relative outside
+  of the root.   That is, if the Lookup is based 
+  at /home/mytemplates, an include that would place
+  the ultimate template at 
+  /home/mytemplates/../some_other_directory,
+  i.e. outside of /home/mytemplates,
+  is disallowed.   This usage was never intended
+  despite the lack of an explicit check.
+  The main issue this causes
+  is that module files can be written outside 
+  of the module root (or raise an error, if file perms aren't
+  set up), and can also lead to the same template being 
+  cached in the lookup under multiple, relative roots. 
+  TemplateLookup instead has always supported multiple 
+  file roots for this purpose.
+  [ticket:174]
+
 0.4.2
 - Fixed bug regarding <%call>/def calls w/ content
   whereby the identity of the "caller" callable
diff --git a/mako/__init__.py b/mako/__init__.py
index c9913eb..5b067a3 100644
--- a/mako/__init__.py
+++ b/mako/__init__.py
@@ -5,5 +5,5 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 
-__version__ = '0.4.2'
+__version__ = '0.5.0'
 
diff --git a/mako/lookup.py b/mako/lookup.py
index b397d21..e3d92da 100644
--- a/mako/lookup.py
+++ b/mako/lookup.py
@@ -204,7 +204,7 @@ class TemplateLookup(TemplateCollection):
         Note the "relativeto" argument is not supported here at the moment.
  
         """
- 
+
         try:
             if self.filesystem_checks:
                 return self._check(uri, self._collection[uri])
diff --git a/mako/template.py b/mako/template.py
index 903dc42..3d02c55 100644
--- a/mako/template.py
+++ b/mako/template.py
@@ -163,7 +163,17 @@ class Template(object):
         else:
             self.module_id = "memory:" + hex(id(self))
             self.uri = self.module_id
- 
+
+        u_norm = self.uri
+        if u_norm.startswith("/"):
+            u_norm = u_norm[1:]
+        u_norm = os.path.normpath(u_norm)
+        if u_norm.startswith(".."):
+            raise exceptions.TemplateLookupException(
+                    "Template uri \"%s\" is invalid - "
+                    "it cannot be relative outside "
+                    "of the root path." % self.uri)
+
         self.input_encoding = input_encoding
         self.output_encoding = output_encoding
         self.encoding_errors = encoding_errors
@@ -203,18 +213,14 @@ class Template(object):
             if module_filename is not None:
                 path = module_filename
             elif module_directory is not None:
-                u = self.uri
-                if u[0] == '/':
-                    u = u[1:]
                 path = os.path.abspath(
                         os.path.join(
                             os.path.normpath(module_directory), 
-                            os.path.normpath(u) + ".py"
+                            u_norm + ".py"
                             )
                         )
             else:
                 path = None
- 
             module = self._compile_from_file(path, filename)
         else:
             raise exceptions.RuntimeException(
diff --git a/test/templates/othersubdir/foo.html b/test/templates/othersubdir/foo.html
new file mode 100644
index 0000000..e69de29
diff --git a/test/test_lookup.py b/test/test_lookup.py
index 190d8a5..40b9009 100644
--- a/test/test_lookup.py
+++ b/test/test_lookup.py
@@ -1,9 +1,11 @@
 from mako.template import Template
-from mako import lookup, exceptions
+from mako import lookup, exceptions, runtime
+from mako.util import FastEncodingBuffer
 from util import flatten_result, result_lines
 import unittest
+import os
 
-from test import TemplateTest, template_base, module_base
+from test import TemplateTest, template_base, module_base, assert_raises_message
 
 tl = lookup.TemplateLookup(directories=[template_base])
 class LookupTest(unittest.TestCase):
@@ -74,3 +76,29 @@ class LookupTest(unittest.TestCase):
         )
         assert f.uri not in tl._collection
 
+    def test_dont_accept_relative_outside_of_root(self):
+        """test the mechanics of an include where 
+        the include goes outside of the path"""
+        tl = lookup.TemplateLookup(directories=[os.path.join(template_base, "subdir")])
+        index = tl.get_template("index.html")
+
+        ctx = runtime.Context(FastEncodingBuffer())
+        ctx._with_template=index
+
+        assert_raises_message(
+            exceptions.TemplateLookupException,
+           "Template uri \"../index.html\" is invalid - it "
+            "cannot be relative outside of the root path",
+            runtime._lookup_template, ctx, "../index.html", index.uri
+        )
+
+        assert_raises_message(
+            exceptions.TemplateLookupException,
+           "Template uri \"../othersubdir/foo.html\" is invalid - it "
+            "cannot be relative outside of the root path",
+            runtime._lookup_template, ctx, "../othersubdir/foo.html", index.uri
+        )
+
+        # this is OK since the .. cancels out
+        t = runtime._lookup_template(ctx, "foo/../index.html", index.uri)
+
diff --git a/test/test_template.py b/test/test_template.py
index a62783e..4d301aa 100644
--- a/test/test_template.py
+++ b/test/test_template.py
@@ -9,7 +9,7 @@ import os
 from util import flatten_result, result_lines
 import codecs
 from test import TemplateTest, eq_, template_base, module_base, \
-    skip_if, assert_raises
+    skip_if, assert_raises, assert_raises_message
 
 class EncodingTest(TemplateTest):
     def test_unicode(self):
@@ -918,8 +918,25 @@ class FilenameToURITest(TemplateTest):
         finally:
             os.path = current_path
  
- 
- 
+    def test_dont_accept_relative_outside_of_root(self):
+        assert_raises_message(
+            exceptions.TemplateLookupException,
+            "Template uri \"../../foo.html\" is invalid - it "
+            "cannot be relative outside of the root path",
+            Template, "test", uri="../../foo.html", 
+        )
+
+        assert_raises_message(
+            exceptions.TemplateLookupException,
+            "Template uri \"/../../foo.html\" is invalid - it "
+            "cannot be relative outside of the root path",
+            Template, "test", uri="/../../foo.html", 
+        )
+
+        # normalizes in the root is OK
+        t = Template("test", uri="foo/bar/../../foo.html")
+        eq_(t.uri, "foo/bar/../../foo.html")
+
 class ModuleTemplateTest(TemplateTest):
     def test_module_roundtrip(self):
         lookup = TemplateLookup()
-- 
GitLab