Commit cdc6c520 by James Cammarata

Clean string data run through the template engine

Also strip UnsafeProxy off of low level srings and objects to ensure
they don't cause issues later down the road

Fixes #12513
parent ae9b34b1
...@@ -37,7 +37,7 @@ from ansible.playbook.included_file import IncludedFile ...@@ -37,7 +37,7 @@ from ansible.playbook.included_file import IncludedFile
from ansible.playbook.role import hash_params from ansible.playbook.role import hash_params
from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader
from ansible.template import Templar from ansible.template import Templar
from ansible.vars.unsafe_proxy import UnsafeProxy from ansible.vars.unsafe_proxy import wrap_var
try: try:
from __main__ import display from __main__ import display
...@@ -244,31 +244,8 @@ class StrategyBase: ...@@ -244,31 +244,8 @@ class StrategyBase:
# the variable goes in the fact_cache # the variable goes in the fact_cache
host = result[1] host = result[1]
var_name = result[2] var_name = result[2]
var_value = result[3] var_value = wrap_var(result[3])
def _wrap_var(v):
if isinstance(v, dict):
v = _wrap_dict(v)
elif isinstance(v, list):
v = _wrap_list(v)
else:
if v is not None and not isinstance(v, UnsafeProxy):
v = UnsafeProxy(v)
return v
def _wrap_dict(v):
for k in v.keys():
if v[k] is not None and not isinstance(v[k], UnsafeProxy):
v[k] = _wrap_var(v[k])
return v
def _wrap_list(v):
for idx, item in enumerate(v):
if item is not None and not isinstance(item, UnsafeProxy):
v[idx] = _wrap_var(item)
return v
var_value = _wrap_var(var_value)
self._variable_manager.set_nonpersistent_facts(host, {var_name: var_value}) self._variable_manager.set_nonpersistent_facts(host, {var_name: var_value})
elif result[0] in ('set_host_var', 'set_host_facts'): elif result[0] in ('set_host_var', 'set_host_facts'):
...@@ -291,7 +268,8 @@ class StrategyBase: ...@@ -291,7 +268,8 @@ class StrategyBase:
if result[0] == 'set_host_var': if result[0] == 'set_host_var':
var_name = result[4] var_name = result[4]
var_value = result[5] var_value = wrap_var(result[5])
self._variable_manager.set_host_variable(target_host, var_name, var_value) self._variable_manager.set_host_variable(target_host, var_name, var_value)
elif result[0] == 'set_host_facts': elif result[0] == 'set_host_facts':
facts = result[4] facts = result[4]
......
...@@ -19,7 +19,10 @@ ...@@ -19,7 +19,10 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import StringIO
import ast import ast
import contextlib
import os
import re import re
from six import string_types, text_type, binary_type from six import string_types, text_type, binary_type
...@@ -49,6 +52,7 @@ NON_TEMPLATED_TYPES = ( bool, Number ) ...@@ -49,6 +52,7 @@ NON_TEMPLATED_TYPES = ( bool, Number )
JINJA2_OVERRIDE = '#jinja2:' JINJA2_OVERRIDE = '#jinja2:'
def _escape_backslashes(data, jinja_env): def _escape_backslashes(data, jinja_env):
"""Double backslashes within jinja2 expressions """Double backslashes within jinja2 expressions
...@@ -108,6 +112,7 @@ def _count_newlines_from_end(in_str): ...@@ -108,6 +112,7 @@ def _count_newlines_from_end(in_str):
# Uncommon cases: zero length string and string containing only newlines # Uncommon cases: zero length string and string containing only newlines
return i return i
class Templar: class Templar:
''' '''
The main class for templating, with the main entry-point of template(). The main class for templating, with the main entry-point of template().
...@@ -148,6 +153,12 @@ class Templar: ...@@ -148,6 +153,12 @@ class Templar:
self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string)) self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % (self.environment.variable_start_string, self.environment.variable_end_string))
self.block_start = self.environment.block_start_string
self.block_end = self.environment.block_end_string
self.variable_start = self.environment.variable_start_string
self.variable_end = self.environment.variable_end_string
self._clean_regex = re.compile(r'(?:%s[%s%s]|[%s%s]%s)' % (self.variable_start[0], self.variable_start[1], self.block_start[1], self.block_end[0], self.variable_end[0], self.variable_end[1]))
def _get_filters(self): def _get_filters(self):
''' '''
Returns filter plugins, after loading and caching them if need be Returns filter plugins, after loading and caching them if need be
...@@ -197,6 +208,47 @@ class Templar: ...@@ -197,6 +208,47 @@ class Templar:
return jinja_exts return jinja_exts
def _clean_data(self, orig_data):
''' remove jinja2 template tags from a string '''
if not isinstance(orig_data, string_types):
return orig_data
with contextlib.closing(StringIO.StringIO(orig_data)) as data:
# these variables keep track of opening block locations, as we only
# want to replace matched pairs of print/block tags
print_openings = []
block_openings = []
for mo in self._clean_regex.finditer(orig_data):
token = mo.group(0)
token_start = mo.start(0)
if token[0] == self.variable_start[0]:
if token == self.block_start:
block_openings.append(token_start)
elif token == self.variable_start:
print_openings.append(token_start)
elif token[1] == self.variable_end[1]:
prev_idx = None
if token == '%}' and block_openings:
prev_idx = block_openings.pop()
elif token == '}}' and print_openings:
prev_idx = print_openings.pop()
if prev_idx is not None:
# replace the opening
data.seek(prev_idx, os.SEEK_SET)
data.write('{#')
# replace the closing
data.seek(token_start, os.SEEK_SET)
data.write('#}')
else:
raise AnsibleError("Error while cleaning data for safety: unhandled regex match")
return data.getvalue()
def set_available_variables(self, variables): def set_available_variables(self, variables):
''' '''
Sets the list of template variables this Templar instance will use Sets the list of template variables this Templar instance will use
...@@ -218,11 +270,11 @@ class Templar: ...@@ -218,11 +270,11 @@ class Templar:
# their constituent type. # their constituent type.
if hasattr(variable, '__UNSAFE__'): if hasattr(variable, '__UNSAFE__'):
if isinstance(variable, text_type): if isinstance(variable, text_type):
return text_type(variable) return self._clean_data(text_type(variable))
elif isinstance(variable, binary_type): elif isinstance(variable, binary_type):
return bytes(variable) return self._clean_data(bytes(variable))
else: else:
return variable return self._clean_data(variable._obj)
try: try:
if convert_bare: if convert_bare:
...@@ -258,6 +310,7 @@ class Templar: ...@@ -258,6 +310,7 @@ class Templar:
# FIXME: if the safe_eval raised an error, should we do something with it? # FIXME: if the safe_eval raised an error, should we do something with it?
pass pass
#return self._clean_data(result)
return result return result
elif isinstance(variable, (list, tuple)): elif isinstance(variable, (list, tuple)):
......
...@@ -50,6 +50,8 @@ ...@@ -50,6 +50,8 @@
# http://code.activestate.com/recipes/496741-object-proxying/ # http://code.activestate.com/recipes/496741-object-proxying/
# Author: Tomer Filiba # Author: Tomer Filiba
__all__ = ['UnsafeProxy', 'wrap_var']
class UnsafeProxy(object): class UnsafeProxy(object):
__slots__ = ["_obj", "__weakref__"] __slots__ = ["_obj", "__weakref__"]
def __init__(self, obj): def __init__(self, obj):
...@@ -149,3 +151,25 @@ class UnsafeProxy(object): ...@@ -149,3 +151,25 @@ class UnsafeProxy(object):
ins = object.__new__(theclass) ins = object.__new__(theclass)
return ins return ins
def _wrap_dict(v):
for k in v.keys():
if v[k] is not None and not isinstance(v[k], UnsafeProxy):
v[k] = _wrap_var(v[k])
return v
def _wrap_list(v):
for idx, item in enumerate(v):
if item is not None and not isinstance(item, UnsafeProxy):
v[idx] = _wrap_var(item)
return v
def wrap_var(v):
if isinstance(v, dict):
v = _wrap_dict(v)
elif isinstance(v, list):
v = _wrap_list(v)
else:
if v is not None and not isinstance(v, UnsafeProxy):
v = UnsafeProxy(v)
return v
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment