diff --git a/changelogs/fragments/unsafe-intern.yml b/changelogs/fragments/unsafe-intern.yml new file mode 100644 index 00000000000..616e6918de3 --- /dev/null +++ b/changelogs/fragments/unsafe-intern.yml @@ -0,0 +1,3 @@ +bugfixes: +- unsafe data - Enable directly using ``AnsibleUnsafeText`` with Python ``pathlib`` + (https://github.com/ansible/ansible/issues/82414) diff --git a/lib/ansible/utils/unsafe_proxy.py b/lib/ansible/utils/unsafe_proxy.py index b2c08fbb852..b3e738368ef 100644 --- a/lib/ansible/utils/unsafe_proxy.py +++ b/lib/ansible/utils/unsafe_proxy.py @@ -53,6 +53,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import sys +import types +import warnings +from sys import intern as _sys_intern from collections.abc import Mapping, Set from ansible.module_utils.common.text.converters import to_bytes, to_text @@ -369,3 +373,20 @@ def to_unsafe_text(*args, **kwargs): def _is_unsafe(obj): return getattr(obj, '__UNSAFE__', False) is True + + +def _intern(string): + """This is a monkey patch for ``sys.intern`` that will strip + the unsafe wrapper prior to interning the string. + + This will not exist in future versions. + """ + if isinstance(string, AnsibleUnsafeText): + string = string._strip_unsafe() + return _sys_intern(string) + + +if isinstance(sys.intern, types.BuiltinFunctionType): + sys.intern = _intern +else: + warnings.warn("skipped sys.intern patch; appears to have already been patched", RuntimeWarning) diff --git a/test/lib/ansible_test/_util/target/sanity/import/importer.py b/test/lib/ansible_test/_util/target/sanity/import/importer.py index 7919fa0d7d1..38a73643258 100644 --- a/test/lib/ansible_test/_util/target/sanity/import/importer.py +++ b/test/lib/ansible_test/_util/target/sanity/import/importer.py @@ -552,6 +552,12 @@ def main(): "Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography," " and will be removed in the next release.") + # ansible.utils.unsafe_proxy attempts patching sys.intern generating a warning if it was already patched + warnings.filterwarnings( + "ignore", + "skipped sys.intern patch; appears to have already been patched" + ) + try: yield finally: diff --git a/test/units/utils/test_unsafe_proxy.py b/test/units/utils/test_unsafe_proxy.py index fbb0bcdc5fa..55f1b6dd03b 100644 --- a/test/units/utils/test_unsafe_proxy.py +++ b/test/units/utils/test_unsafe_proxy.py @@ -5,6 +5,9 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import pathlib +import sys + from ansible.utils.unsafe_proxy import AnsibleUnsafe, AnsibleUnsafeBytes, AnsibleUnsafeText, wrap_var from ansible.module_utils.common.text.converters import to_text, to_bytes @@ -115,3 +118,10 @@ def test_to_text_unsafe(): def test_to_bytes_unsafe(): assert isinstance(to_bytes(AnsibleUnsafeText(u'foo')), AnsibleUnsafeBytes) assert to_bytes(AnsibleUnsafeText(u'foo')) == AnsibleUnsafeBytes(b'foo') + + +def test_unsafe_with_sys_intern(): + # Specifically this is actually about sys.intern, test of pathlib + # because that is a specific affected use + assert sys.intern(AnsibleUnsafeText('foo')) == 'foo' + assert pathlib.Path(AnsibleUnsafeText('/tmp')) == pathlib.Path('/tmp')