diff --git a/fail2ban/client/configparserinc.py b/fail2ban/client/configparserinc.py index 7b054876..de38c32b 100644 --- a/fail2ban/client/configparserinc.py +++ b/fail2ban/client/configparserinc.py @@ -28,15 +28,45 @@ __copyright__ = 'Copyright (c) 2007 Yaroslav Halchenko' __license__ = 'GPL' import logging, os, sys + if sys.version_info >= (3,2): # pragma: no cover - # SafeConfigParser deprecitated from python 3.2 (renamed ConfigParser) - from configparser import ConfigParser as SafeConfigParser + + # SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser) + from configparser import ConfigParser as SafeConfigParser, \ + BasicInterpolation + + # And interpolation of __name__ was simply removed, thus we need to + # decorate default interpolator to handle it + class BasicInterpolationWithName(BasicInterpolation): + """Decorator to bring __name__ interpolation back. + + Original handling of __name__ was removed because of + functional deficiencies: http://bugs.python.org/issue10489 + + commit v3.2a4-105-g61f2761 + Author: Lukasz Langa + Date: Sun Nov 21 13:41:35 2010 +0000 + + Issue #10489: removed broken `__name__` support from configparser + + But should be fine to reincarnate for our use case + """ + def _interpolate_some(self, parser, option, accum, rest, section, map, + depth): + if section and not (__name__ in map): + map = map.copy() # just to be safe + map['__name__'] = section + return super(BasicInterpolationWithName, self)._interpolate_some( + parser, option, accum, rest, section, map, depth) + else: # pragma: no cover from ConfigParser import SafeConfigParser # Gets the instance of the logger. logSys = logging.getLogger(__name__) +__all__ = ['SafeConfigParserWithIncludes'] + class SafeConfigParserWithIncludes(SafeConfigParser): """ Class adds functionality to SafeConfigParser to handle included @@ -68,6 +98,14 @@ after = 1.conf SECTION_NAME = "INCLUDES" + if sys.version_info >= (3,2): + # overload constructor only for fancy new Python3's + def __init__(self, *args, **kwargs): + kwargs = kwargs.copy() + kwargs['interpolation'] = BasicInterpolationWithName() + super(SafeConfigParserWithIncludes, self).__init__( + *args, **kwargs) + #@staticmethod def getIncludes(resource, seen = []): """ diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py index 7746d291..d5fee47d 100644 --- a/fail2ban/tests/clientreadertestcase.py +++ b/fail2ban/tests/clientreadertestcase.py @@ -21,7 +21,7 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko" __license__ = "GPL" -import os, shutil, tempfile, unittest +import os, shutil, sys, tempfile, unittest from fail2ban.client.configreader import ConfigReader from fail2ban.client.jailreader import JailReader @@ -47,7 +47,7 @@ class ConfigReaderTest(unittest.TestCase): """Call after every test case.""" shutil.rmtree(self.d) - def _write(self, fname, value): + def _write(self, fname, value=None, content=None): # verify if we don't need to create .d directory if os.path.sep in fname: d = os.path.dirname(fname) @@ -55,10 +55,13 @@ class ConfigReaderTest(unittest.TestCase): if not os.path.exists(d_): os.makedirs(d_) f = open("%s/%s" % (self.d, fname), "w") - f.write(""" + if value is not None: + f.write(""" [section] option = %s -""" % value) + """ % value) + if content is not None: + f.write(content) f.close() def _remove(self, fname): @@ -105,6 +108,28 @@ option = %s self._remove("c.local") self.assertEqual(self._getoption(), 1) + def testInterpolations(self): + self.assertFalse(self.c.read('i')) # nothing is there yet + self._write("i.conf", value=None, content=""" +[DEFAULT] +b = a +zz = the%(__name__)s + +[section] +y = 4%(b)s +e = 5${b} +z = %(__name__)s + +[section2] +z = 3%(__name__)s +""") + self.assertTrue(self.c.read('i')) + self.assertEqual(self.c.sections(), ['section', 'section2']) + self.assertEqual(self.c.get('section', 'y'), '4a') # basic interpolation works + self.assertEqual(self.c.get('section', 'e'), '5${b}') # no extended interpolation + self.assertEqual(self.c.get('section', 'z'), 'section') # __name__ works + self.assertEqual(self.c.get('section', 'zz'), 'thesection') # __name__ works even 'delayed' + self.assertEqual(self.c.get('section2', 'z'), '3section2') # and differs per section ;) class JailReaderTest(unittest.TestCase):