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
This commit is contained in:
parent
ae9b34b1d9
commit
cdc6c5208e
3 changed files with 84 additions and 29 deletions
|
@ -37,7 +37,7 @@ from ansible.playbook.included_file import IncludedFile
|
|||
from ansible.playbook.role import hash_params
|
||||
from ansible.plugins import action_loader, connection_loader, filter_loader, lookup_loader, module_loader
|
||||
from ansible.template import Templar
|
||||
from ansible.vars.unsafe_proxy import UnsafeProxy
|
||||
from ansible.vars.unsafe_proxy import wrap_var
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
|
@ -244,31 +244,8 @@ class StrategyBase:
|
|||
# the variable goes in the fact_cache
|
||||
host = result[1]
|
||||
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})
|
||||
|
||||
elif result[0] in ('set_host_var', 'set_host_facts'):
|
||||
|
@ -291,7 +268,8 @@ class StrategyBase:
|
|||
|
||||
if result[0] == 'set_host_var':
|
||||
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)
|
||||
elif result[0] == 'set_host_facts':
|
||||
facts = result[4]
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import StringIO
|
||||
import ast
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
|
||||
from six import string_types, text_type, binary_type
|
||||
|
@ -49,6 +52,7 @@ NON_TEMPLATED_TYPES = ( bool, Number )
|
|||
|
||||
JINJA2_OVERRIDE = '#jinja2:'
|
||||
|
||||
|
||||
def _escape_backslashes(data, jinja_env):
|
||||
"""Double backslashes within jinja2 expressions
|
||||
|
||||
|
@ -108,6 +112,7 @@ def _count_newlines_from_end(in_str):
|
|||
# Uncommon cases: zero length string and string containing only newlines
|
||||
return i
|
||||
|
||||
|
||||
class Templar:
|
||||
'''
|
||||
The main class for templating, with the main entry-point of template().
|
||||
|
@ -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.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):
|
||||
'''
|
||||
Returns filter plugins, after loading and caching them if need be
|
||||
|
@ -197,6 +208,47 @@ class Templar:
|
|||
|
||||
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):
|
||||
'''
|
||||
Sets the list of template variables this Templar instance will use
|
||||
|
@ -218,11 +270,11 @@ class Templar:
|
|||
# their constituent type.
|
||||
if hasattr(variable, '__UNSAFE__'):
|
||||
if isinstance(variable, text_type):
|
||||
return text_type(variable)
|
||||
return self._clean_data(text_type(variable))
|
||||
elif isinstance(variable, binary_type):
|
||||
return bytes(variable)
|
||||
return self._clean_data(bytes(variable))
|
||||
else:
|
||||
return variable
|
||||
return self._clean_data(variable._obj)
|
||||
|
||||
try:
|
||||
if convert_bare:
|
||||
|
@ -258,6 +310,7 @@ class Templar:
|
|||
# FIXME: if the safe_eval raised an error, should we do something with it?
|
||||
pass
|
||||
|
||||
#return self._clean_data(result)
|
||||
return result
|
||||
|
||||
elif isinstance(variable, (list, tuple)):
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
# http://code.activestate.com/recipes/496741-object-proxying/
|
||||
# Author: Tomer Filiba
|
||||
|
||||
__all__ = ['UnsafeProxy', 'wrap_var']
|
||||
|
||||
class UnsafeProxy(object):
|
||||
__slots__ = ["_obj", "__weakref__"]
|
||||
def __init__(self, obj):
|
||||
|
@ -149,3 +151,25 @@ class UnsafeProxy(object):
|
|||
ins = object.__new__(theclass)
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue