Skip to content

Commit c315202

Browse files
committed
issue2550891: Allow subdir in template value. Anthony (antmail)
requested the ability to put templates into subdirectories. So the issue class can accept @template=issues/item to get the html/issues/issue.item.html template. Inlcude a test case for missing and existing (tal) templates. Also include a test that fails path traversal check. Add mention of subdiectoy use to customizing.txt along with some spelling fixes and ^M removal.
1 parent 0958f31 commit c315202

File tree

5 files changed

+125
-7
lines changed

5 files changed

+125
-7
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ Features:
130130
'msg_header_property' we can do this easily. Setting the
131131
'msg_header_property' to the empty string '' (not to None) will
132132
suppress the header for that property. (John Rouillard)
133+
- issue2550891: Allow subdir in template value. Anthony (antmail)
134+
requested the ability to put templates into subdirectories. So
135+
the issue class can accept @template=issues/item to get the
136+
html/issues/issue.item.html template. See ``doc/upgrading.txt``.
133137

134138
Fixed:
135139

doc/customizing.txt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Section **main**
9494

9595
templates -- ``html``
9696
Path to the HTML templates directory. The path may be either absolute
97-
or relative to the directory containig this config file.
97+
or relative to the directory containing this config file.
9898

9999
static_files -- default *blank*
100100
Path to directory holding additional static files available via Web
@@ -1480,11 +1480,11 @@ e. if the path starts with an item designator and is longer than one
14801480
with (i.e. "file1/kitten.png" is nicer to download than "file1").
14811481
This raises a ``SendFile`` exception.
14821482

1483-
Neither b. or e. use templates and stop before the template is
1484-
determined. For other contexts the template used is specified by the
1483+
Neither b. or e. use templates and stop before the template is
1484+
determined. For other contexts the template used is specified by the
14851485
``@template`` variable, which defaults to:
14861486

1487-
- only classname suplied: "index"
1487+
- only classname supplied: "index"
14881488
- full item designator supplied: "item"
14891489

14901490

@@ -1798,6 +1798,15 @@ access the test template using the "@template" URL argument::
17981798

17991799
and it won't affect your users using the "issue.item" template.
18001800

1801+
You can also put templates into a subdirectory of the template
1802+
directory. So if you specify::
1803+
1804+
http://your.tracker.example/tracker/issue?@template=test/item
1805+
1806+
you will use the template at: ``test/issue.item.html``. If that
1807+
template doesn't exit it will try to use
1808+
``test/_generic.item.html``. If that template doesn't exist
1809+
it will return an error.
18011810

18021811
How the templates work
18031812
----------------------

doc/upgrading.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@ If your deployed tracker is based on: classic, minimal, responsive or
173173
devel templates and has not changed the html/_generic.404.html file,
174174
you can copy in the new file to get this additional functionality.
175175

176+
Organize templates into subdirectories
177+
--------------------------------------
178+
179+
The @template parameter to the web interface allows the use of
180+
subdirectories. So a setting of @template=view/view for an issue would
181+
use the template in the tracker's html/view/issue.view.html. Similarly
182+
for a caller class, you could put all the templates under the
183+
html/caller directory with names like: html/caller/caller.item.html,
184+
html/caller/caller.index.html etc. You may want to symbolically link the
185+
html/_generic* templates into your subdirectory so that missing
186+
templates (e.g. a missing caller.edit.html template) can be satisfied
187+
by the _generic.edit.html template.
188+
176189
Schema change to allow "Show Unassigned" issues link to work for Anonymous user
177190
-------------------------------------------------------------------------------
178191

roundup/cgi/client.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,7 +1157,17 @@ def selectTemplate(self, name, view):
11571157

11581158
tplname = name
11591159
if view:
1160-
tplname = '%s.%s' % (name, view)
1160+
# Support subdirectories for templates. Value is path/to/VIEW
1161+
# or just VIEW if the template is in the html directory of
1162+
# the tracker.
1163+
slash_loc = view.rfind("/")
1164+
if slash_loc == -1:
1165+
# try plain class.view
1166+
tplname = '%s.%s' % (name, view)
1167+
else:
1168+
# try path/class.view
1169+
tplname = '%s/%s.%s'%(
1170+
view[:slash_loc], name, view[slash_loc+1:])
11611171

11621172
if loader.check(tplname):
11631173
return tplname
@@ -1167,7 +1177,10 @@ def selectTemplate(self, name, view):
11671177
if not view:
11681178
raise templating.NoTemplate('Template "%s" doesn\'t exist' % name)
11691179

1170-
generic = '_generic.%s' % view
1180+
if slash_loc == -1:
1181+
generic = '_generic.%s' % view
1182+
else:
1183+
generic = '%s/_generic.%s' % (view[:slash_loc], view[slash_loc+1:])
11711184
if loader.check(generic):
11721185
return generic
11731186

test/test_cgi.py

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from roundup.cgi import client, actions, exceptions
1414
from roundup.cgi.exceptions import FormError
15-
from roundup.cgi.templating import HTMLItem, HTMLRequest
15+
from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate
1616
from roundup.cgi.form_parser import FormParser
1717
from roundup import init, instance, password, hyperdb, date
1818

@@ -1085,4 +1085,83 @@ def testCSVExportFailPermission(self):
10851085
self.assertRaises(exceptions.SeriousError,
10861086
actions.ExportCSVAction(cl).handle)
10871087

1088+
class TemplateTestCase(unittest.TestCase):
1089+
''' Test the template resolving code, i.e. what can be given to @template
1090+
'''
1091+
def setUp(self):
1092+
self.dirname = '_test_template'
1093+
# set up and open a tracker
1094+
self.instance = db_test_base.setupTracker(self.dirname)
1095+
1096+
# open the database
1097+
self.db = self.instance.open('admin')
1098+
self.db.tx_Source = "web"
1099+
self.db.user.create(username='Chef', address='[email protected]',
1100+
realname='Bork, Chef', roles='User')
1101+
self.db.user.create(username='mary', address='[email protected]',
1102+
roles='User', realname='Contrary, Mary')
1103+
self.db.post_init()
1104+
1105+
def tearDown(self):
1106+
self.db.close()
1107+
try:
1108+
shutil.rmtree(self.dirname)
1109+
except OSError, error:
1110+
if error.errno not in (errno.ENOENT, errno.ESRCH): raise
1111+
1112+
def testTemplateSubdirectory(self):
1113+
# test for templates in subdirectories
1114+
1115+
# make the directory
1116+
subdir = self.dirname + "/html/subdir"
1117+
os.mkdir(subdir)
1118+
1119+
# get the client instance The form is needed to initialize,
1120+
# but not used since I call selectTemplate directly.
1121+
t = client.Client(self.instance, "user",
1122+
{'PATH_INFO':'/user', 'REQUEST_METHOD':'POST'},
1123+
form=makeForm({"@template": "item"}))
1124+
1125+
# create new file in subdir and a dummy file outside of
1126+
# the tracker's html subdirectory
1127+
shutil.copyfile(self.dirname + "/html/issue.item.html",
1128+
subdir + "/issue.item.html")
1129+
shutil.copyfile(self.dirname + "/html/user.item.html",
1130+
self.dirname + "/user.item.html")
1131+
1132+
# create link outside the html subdir. This should fail due to
1133+
# path traversal check.
1134+
os.symlink("../../user.item.html", subdir + "/user.item.html")
1135+
# it will be removed and replaced by a later test
1136+
1137+
# make sure a simple non-subdir template works.
1138+
# user.item.html exists so this works.
1139+
# note that the extension is not included just the basename
1140+
self.assertEqual("user.item", t.selectTemplate("user", "item"))
1141+
1142+
# there is no html/subdir/user.item.{,xml,html} so it will
1143+
# raise NoTemplate.
1144+
self.assertRaises(NoTemplate,
1145+
t.selectTemplate, "user", "subdir/item")
1146+
1147+
# there is an html/subdir/issue.item.html so this succeeeds
1148+
r = t.selectTemplate("issue", "subdir/item")
1149+
self.assertEqual("subdir/issue.item", r)
1150+
1151+
# there is a self.directory + /html/subdir/user.item.html file,
1152+
# but it is a link to self.dir /user.item.html which is outside
1153+
# the html subdir so is rejected by the path traversal check.
1154+
self.assertRaises(NoTemplate,
1155+
t.selectTemplate, "user", "subdir/item")
1156+
1157+
# clear out the link and create a new one to self.dirname +
1158+
# html/user.item.html which is inside the html subdir
1159+
# so the template check returns the symbolic link path.
1160+
os.remove(subdir + "/user.item.html")
1161+
os.symlink("../user.item.html", subdir + "/user.item.xml")
1162+
1163+
# template check works
1164+
r = t.selectTemplate("user", "subdir/item")
1165+
self.assertEquals("subdir/user.item", r)
1166+
10881167
# vim: set filetype=python sts=4 sw=4 et si :

0 commit comments

Comments
 (0)