Skip to content

Commit 2b4bdcf

Browse files
committed
Fix issue with retreiving raw template files using the @@file mechanism.
This changes the static_files option in config.ini from supporting a single directory to support multiple directories. If one of the directory elements is '-' (i.e. a lone hyphen) the search is stopped and the TEMPLATES directory is not searched. Since the TEMPLATES directory is not searched the raw templates aren't accessed. See: https://sourceforge.net/p/roundup/mailman/message/35773357/ Message subject: showing template sources to all for details. Also check in CHANGES.txt that mentions a couple of other small improvements in the roundup-admin command.
1 parent 1126f31 commit 2b4bdcf

File tree

5 files changed

+183
-21
lines changed

5 files changed

+183
-21
lines changed

CHANGES.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ Features:
192192
session cookie. Default is lax, but there is a settable
193193
option in config.ini file to change to strict or
194194
suppress it entirely. See ``upgrading.txt``. (John Rouillard)
195+
- Added a new roundup-admin command: updateconfig. Similar to
196+
genconfig but it uses values from an existing config.ini
197+
rather than default values. Use to update an existing
198+
config.ini with new options and help text. (John Rouillard)
195199

196200
Fixed:
197201

@@ -427,6 +431,14 @@ Fixed:
427431
- issue2550937: fix crash by verifying that sendto is not null before
428432
calling mailer.smtp_send. Discovered and patched by Trent Gamblin.
429433
Applied by John Rouillard.
434+
- removed old code from roundup-admin that implemented the obsolete
435+
config (do_config) command. (John Rouillard)
436+
- Modified configuration option static_files to be a space separated
437+
list of directories to search for static files in the web interface.
438+
If one of the elements is -, the search stops and the TEMPLATES
439+
directory is not searched. See:
440+
https://sourceforge.net/p/roundup/mailman/message/35773357/
441+
subject is "showing template sources to all".
430442

431443
2016-01-11: 1.5.1
432444

doc/customizing.txt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ section "ConfigParser -- Configuration file parser":
8787

8888
__ http://docs.python.org/lib/module-ConfigParser.html
8989

90+
Example configuration settings are below.
91+
9092
Section **main**
9193
database -- ``db``
9294
Database directory path. The path may be either absolute or relative
@@ -97,12 +99,13 @@ Section **main**
9799
or relative to the directory containing this config file.
98100

99101
static_files -- default *blank*
100-
Path to directory holding additional static files available via Web
101-
UI. This directory may contain sitewide images, CSS stylesheets etc.
102-
and is searched for these files prior to the TEMPLATES directory
103-
specified above. If this option is not set, all static files are
104-
taken from the TEMPLATES directory The path may be either absolute or
105-
relative to the directory containig this config file.
102+
A list of space separated directory paths (or a single directory).
103+
These directories hold additional static files available via Web UI.
104+
These directories may contain sitewide images, CSS stylesheets etc. If
105+
a '-' is included, the list processing ends and the TEMPLATES
106+
directory is not searched after the specified directories. If this
107+
option is not set, all static files are taken from the TEMPLATES
108+
directory.
106109

107110
admin_email -- ``roundup-admin``
108111
Email address that roundup will complain to if it runs into trouble. If
@@ -459,7 +462,8 @@ Section **nosy**
459462

460463

461464
You may generate a new default config file using the ``roundup-admin
462-
genconfig`` command.
465+
genconfig`` command. You can generate a new config file merging in
466+
existing settings using the ``roundup-admin updateconfig`` command.
463467

464468
Configuration variables may be referred to in lower or upper case. In code,
465469
variables not in the "main" section are referred to using their section and

roundup/cgi/client.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,13 +1439,33 @@ def serve_static_file(self, file):
14391439
prefix = self.instance.config[dir_option]
14401440
if not prefix:
14411441
continue
1442-
# ensure the load doesn't try to poke outside
1443-
# of the static files directory
1444-
prefix = os.path.normpath(prefix)
1445-
filename = os.path.normpath(os.path.join(prefix, file))
1446-
if os.path.isfile(filename) and filename.startswith(prefix):
1442+
if type(prefix) is str:
1443+
# prefix can be a string or list depending on
1444+
# option. Make it a list to iterate over.
1445+
prefix = [ prefix ]
1446+
1447+
for p in prefix:
1448+
# if last element of STATIC_FILES ends with '/-',
1449+
# we failed to find the file and we should
1450+
# not look in TEMPLATES. So raise exception.
1451+
if dir_option == 'STATIC_FILES' and p[-2:] == '/-':
1452+
raise NotFound(file)
1453+
1454+
# ensure the load doesn't try to poke outside
1455+
# of the static files directory
1456+
p = os.path.normpath(p)
1457+
filename = os.path.normpath(os.path.join(p, file))
1458+
if os.path.isfile(filename) and filename.startswith(p):
1459+
break # inner loop over list of directories
1460+
else:
1461+
# reset filename to None as sentinel for use below.
1462+
filename = None
1463+
1464+
# break out of outer loop over options
1465+
if filename:
14471466
break
1448-
else:
1467+
1468+
if filename is None: # we didn't find a filename
14491469
raise NotFound(file)
14501470

14511471
# last-modified time

roundup/configuration.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,31 @@ def get(self):
379379
_val = os.path.join(self.config["HOME"], _val)
380380
return _val
381381

382+
class MultiFilePathOption(Option):
383+
384+
"""List of space seperated File or directory path name
385+
386+
Paths may be either absolute or relative to the HOME. None
387+
is returned if there are no elements.
388+
389+
"""
390+
391+
class_description = "The space separated paths may be either absolute or\n" \
392+
"relative to the directory containing this config file."
393+
394+
def get(self):
395+
pathlist = []
396+
_val = Option.get(self)
397+
for elem in _val.split():
398+
if elem and not os.path.isabs(elem):
399+
pathlist.append(os.path.join(self.config["HOME"], elem))
400+
else:
401+
pathlist.append(elem)
402+
if pathlist:
403+
return pathlist
404+
else:
405+
return None
406+
382407
class FloatNumberOption(Option):
383408

384409
"""Floating point numbers"""
@@ -526,13 +551,15 @@ def str2value(self, value):
526551
"ported from Zope, or 'chameleon' for Chameleon."),
527552
(FilePathOption, "templates", "html",
528553
"Path to the HTML templates directory."),
529-
(NullableFilePathOption, "static_files", "",
530-
"Path to directory holding additional static files\n"
531-
"available via Web UI. This directory may contain\n"
532-
"sitewide images, CSS stylesheets etc. and is searched\n"
533-
"for these files prior to the TEMPLATES directory\n"
534-
"specified above. If this option is not set, all static\n"
535-
"files are taken from the TEMPLATES directory"),
554+
(MultiFilePathOption, "static_files", "",
555+
"A list of space separated directory paths (or a single\n"
556+
"directory). These directories hold additional static\n"
557+
"files available via Web UI. These directories may\n"
558+
"contain sitewide images, CSS stylesheets etc. If a '-'\n"
559+
"is included, the list processing ends and the TEMPLATES\n"
560+
"directory is not searched after the specified\n"
561+
"directories. If this option is not set, all static\n"
562+
"files are taken from the TEMPLATES directory."),
536563
(MailAddressOption, "admin_email", "roundup-admin",
537564
"Email address that roundup will complain to if it runs\n"
538565
"into trouble.\n"

test/test_cgi.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import unittest, os, shutil, errno, sys, difflib, cgi, re, StringIO
1212

1313
from roundup.cgi import client, actions, exceptions
14-
from roundup.cgi.exceptions import FormError
14+
from roundup.cgi.exceptions import FormError, NotFound
1515
from roundup.exceptions import UsageError
1616
from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate, anti_csrf_nonce
1717
from roundup.cgi.templating import HTMLProperty, _HTMLItem
@@ -1390,6 +1390,105 @@ def testEditCSV(self):
13901390
k = self.db.keyword.getnode('1')
13911391
self.assertEqual(k.name, u'\xe4\xf6\xfc'.encode('utf-8'))
13921392

1393+
def testserve_static_files(self):
1394+
# make a client instance
1395+
cl = self._make_client({})
1396+
1397+
# hijack _serve_file so I can see what is found
1398+
output = []
1399+
def my_serve_file(a, b, c, d):
1400+
output.append((a,b,c,d))
1401+
cl._serve_file = my_serve_file
1402+
1403+
# check case where file is not found.
1404+
self.assertRaises(NotFound,
1405+
cl.serve_static_file,"missing.css")
1406+
1407+
# TEMPLATES dir is searched by default. So this file exists.
1408+
# Check the returned values.
1409+
cl.serve_static_file("issue.index.html")
1410+
self.assertEquals(output[0][1], "text/html")
1411+
self.assertEquals(output[0][3], "_test_cgi_form/html/issue.index.html")
1412+
del output[0] # reset output buffer
1413+
1414+
# stop searching TEMPLATES for the files.
1415+
cl.instance.config['STATIC_FILES'] = '-'
1416+
# previously found file should not be found
1417+
self.assertRaises(NotFound,
1418+
cl.serve_static_file,"issue.index.html")
1419+
1420+
# explicitly allow html directory
1421+
cl.instance.config['STATIC_FILES'] = 'html -'
1422+
cl.serve_static_file("issue.index.html")
1423+
self.assertEquals(output[0][1], "text/html")
1424+
self.assertEquals(output[0][3], "_test_cgi_form/html/issue.index.html")
1425+
del output[0] # reset output buffer
1426+
1427+
# set the list of files and do not look at the templates directory
1428+
cl.instance.config['STATIC_FILES'] = 'detectors extensions - '
1429+
1430+
# find file in first directory
1431+
cl.serve_static_file("messagesummary.py")
1432+
self.assertEquals(output[0][1], "text/x-python")
1433+
self.assertEquals(output[0][3], "_test_cgi_form/detectors/messagesummary.py")
1434+
del output[0] # reset output buffer
1435+
1436+
# find file in second directory
1437+
cl.serve_static_file("README.txt")
1438+
self.assertEquals(output[0][1], "text/plain")
1439+
self.assertEquals(output[0][3], "_test_cgi_form/extensions/README.txt")
1440+
del output[0] # reset output buffer
1441+
1442+
# make sure an embedded - ends the searching.
1443+
cl.instance.config['STATIC_FILES'] = ' detectors - extensions '
1444+
self.assertRaises(NotFound, cl.serve_static_file, "README.txt")
1445+
1446+
cl.instance.config['STATIC_FILES'] = ' detectors - extensions '
1447+
self.assertRaises(NotFound, cl.serve_static_file, "issue.index.html")
1448+
1449+
# create an empty README.txt in the first directory
1450+
f = open('_test_cgi_form/detectors/README.txt', 'a').close()
1451+
# find file now in first directory
1452+
cl.serve_static_file("README.txt")
1453+
self.assertEquals(output[0][1], "text/plain")
1454+
self.assertEquals(output[0][3], "_test_cgi_form/detectors/README.txt")
1455+
del output[0] # reset output buffer
1456+
1457+
cl.instance.config['STATIC_FILES'] = ' detectors extensions '
1458+
# make sure lack of trailing - allows searching TEMPLATES
1459+
cl.serve_static_file("issue.index.html")
1460+
self.assertEquals(output[0][1], "text/html")
1461+
self.assertEquals(output[0][3], "_test_cgi_form/html/issue.index.html")
1462+
del output[0] # reset output buffer
1463+
1464+
# Make STATIC_FILES a single element.
1465+
cl.instance.config['STATIC_FILES'] = 'detectors'
1466+
# find file now in first directory
1467+
cl.serve_static_file("messagesummary.py")
1468+
self.assertEquals(output[0][1], "text/x-python")
1469+
self.assertEquals(output[0][3], "_test_cgi_form/detectors/messagesummary.py")
1470+
del output[0] # reset output buffer
1471+
1472+
# make sure files found in subdirectory
1473+
os.mkdir('_test_cgi_form/detectors/css')
1474+
f = open('_test_cgi_form/detectors/css/README.css', 'a').close()
1475+
# use subdir in filename
1476+
cl.serve_static_file("css/README.css")
1477+
self.assertEquals(output[0][1], "text/css")
1478+
self.assertEquals(output[0][3], "_test_cgi_form/detectors/css/README.css")
1479+
del output[0] # reset output buffer
1480+
1481+
1482+
# use subdir in static files path
1483+
cl.instance.config['STATIC_FILES'] = 'detectors html/css'
1484+
os.mkdir('_test_cgi_form/html/css')
1485+
f = open('_test_cgi_form/html/css/README1.css', 'a').close()
1486+
cl.serve_static_file("README1.css")
1487+
self.assertEquals(output[0][1], "text/css")
1488+
self.assertEquals(output[0][3], "_test_cgi_form/html/css/README1.css")
1489+
del output[0] # reset output buffer
1490+
1491+
13931492
def testRoles(self):
13941493
cl = self._make_client({})
13951494
self.db.user.set('1', roles='aDmin, uSer')

0 commit comments

Comments
 (0)