-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Michael Howitz
committed
Sep 14, 2017
1 parent
24ffdd5
commit c48b2d5
Showing
3 changed files
with
130 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from AccessControl.ZopeGuards import guarded_getattr, guarded_getitem | ||
from collections import Mapping | ||
|
||
import string | ||
import six | ||
|
||
try: | ||
# Python 3 | ||
import _string | ||
except ImportError: | ||
pass | ||
|
||
|
||
def formatter_field_name_split(field_name): | ||
if six.PY3: | ||
return _string.formatter_field_name_split(field_name) | ||
else: | ||
return field_name._formatter_field_name_split() | ||
|
||
|
||
class _MagicFormatMapping(Mapping): | ||
"""Pulled from Jinja2. | ||
This class implements a dummy wrapper to fix a bug in the Python | ||
standard library for string formatting. | ||
See http://bugs.python.org/issue13598 for information about why | ||
this is necessary. | ||
""" | ||
|
||
def __init__(self, args, kwargs): | ||
self._args = args | ||
self._kwargs = kwargs | ||
self._last_index = 0 | ||
|
||
def __getitem__(self, key): | ||
if key == '': | ||
idx = self._last_index | ||
self._last_index += 1 | ||
try: | ||
return self._args[idx] | ||
except LookupError: | ||
pass | ||
key = str(idx) | ||
return self._kwargs[key] | ||
|
||
def __iter__(self): | ||
return iter(self._kwargs) | ||
|
||
def __len__(self): | ||
return len(self._kwargs) | ||
|
||
|
||
class SafeFormatter(string.Formatter): | ||
"""Formatter using guarded access.""" | ||
|
||
def __init__(self, value): | ||
self.value = value | ||
super(SafeFormatter, self).__init__() | ||
|
||
def get_field(self, field_name, args, kwargs): | ||
"""Get the field value using guarded methods.""" | ||
first, rest = formatter_field_name_split(field_name) | ||
|
||
obj = self.get_value(first, args, kwargs) | ||
|
||
# loop through the rest of the field_name, doing | ||
# getattr or getitem as needed | ||
for is_attr, i in rest: | ||
if is_attr: | ||
obj = guarded_getattr(obj, i) | ||
else: | ||
obj = guarded_getitem(obj, i) | ||
|
||
return obj, first | ||
|
||
def safe_format(self, *args, **kwargs): | ||
"""Safe variant of `format` method.""" | ||
kwargs = _MagicFormatMapping(args, kwargs) | ||
return self.vformat(self.value, args, kwargs) | ||
|
||
|
||
def safe_format(inst, method): | ||
"""Use our SafeFormatter that uses guarded_getattr for attribute access.""" | ||
return SafeFormatter(inst).safe_format |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from zExceptions import Unauthorized | ||
import unittest | ||
|
||
|
||
class FormatterTest(unittest.TestCase): | ||
"""Test SafeFormatter and SafeStr. | ||
There are some integration tests in Zope2 itself. | ||
""" | ||
|
||
def test_positional_argument_regression(self): | ||
"""Testing fix of http://bugs.python.org/issue13598 issue.""" | ||
from AccessControl.safe_formatter import SafeFormatter | ||
self.assertEqual( | ||
SafeFormatter('{} {}').safe_format('foo', 'bar'), | ||
'foo bar' | ||
) | ||
|
||
self.assertEqual( | ||
SafeFormatter('{0} {1}').safe_format('foo', 'bar'), | ||
'foo bar' | ||
) | ||
self.assertEqual( | ||
SafeFormatter('{1} {0}').safe_format('foo', 'bar'), | ||
'bar foo' | ||
) | ||
|
||
def test_prevents_bad_string_formatting(self): | ||
from AccessControl.safe_formatter import safe_format | ||
with self.assertRaises(Unauthorized) as err: | ||
safe_format('{0.__class__}', None)(1) | ||
self.assertEqual( | ||
"You are not allowed to access '__class__' in this context", | ||
str(err.exception)) | ||
|
||
def test_prevents_bad_unicode_formatting(self): | ||
from AccessControl.safe_formatter import safe_format | ||
with self.assertRaises(Unauthorized) as err: | ||
safe_format(u'{0.__class__}', None)(1) | ||
self.assertEqual( | ||
"You are not allowed to access '__class__' in this context", | ||
str(err.exception)) |