Skip to content

Commit

Permalink
Port str.format fix here from 2.13.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed Sep 14, 2017
1 parent 24ffdd5 commit c48b2d5
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ For changes before version 3.0, see ``HISTORY.rst``.
4.0a8 (unreleased)
------------------

- Security fix: In ``str.format``, check the security for attributes that are
accessed. (Ported from 2.13).

- Port ``override_container`` context manager here from 2.13.


Expand Down
85 changes: 85 additions & 0 deletions src/AccessControl/safe_formatter.py
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
42 changes: 42 additions & 0 deletions src/AccessControl/tests/test_safe_formatter.py
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))

0 comments on commit c48b2d5

Please sign in to comment.