"))', 'rule': None},
{'template': 'test.append(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'HtmlUtils.append($el, someHtml)', 'rule': None},
{'template': 'test.append("fail on concat" + test.render().el)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_append},
{'template': 'test.append("fail on concat" + testEl)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_append},
{'template': 'test.append(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_append},
)
def test_jquery_append(self, data):
"""
Test check_javascript_file_is_safe with JQuery append()
"""
linter = _build_javascript_linter()
results = FileResults('')
linter.check_javascript_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
@data(
{'template': 'test.prepend( test.render().el )', 'rule': None},
{'template': 'test.prepend(test.render().el)', 'rule': None},
{'template': 'test.prepend(test.render().$el)', 'rule': None},
{'template': 'test.prepend(testEl)', 'rule': None},
{'template': 'test.prepend($test)', 'rule': None},
{'template': 'test.prepend("text")', 'rule': None},
{'template': 'test.prepend( $( "
" ) )', 'rule': None},
{'template': 'test.prepend($("
"))', 'rule': None},
{'template': 'test.prepend($("
"))', 'rule': None},
{'template': 'test.prepend(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'HtmlUtils.prepend($el, someHtml)', 'rule': None},
{'template': 'test.prepend("broken string)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_prepend},
{'template': 'test.prepend("fail on concat" + test.render().el)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_prepend},
{'template': 'test.prepend("fail on concat" + testEl)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_prepend},
{'template': 'test.prepend(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_prepend},
)
def test_jquery_prepend(self, data):
"""
Test check_javascript_file_is_safe with JQuery prepend()
"""
linter = _build_javascript_linter()
results = FileResults('')
linter.check_javascript_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
@data(
{'template': 'test.unwrap(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.wrap(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.wrapAll(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.wrapInner(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.after(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.before(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.replaceAll(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.replaceWith(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'test.replaceWith(edx.HtmlUtils.HTML(htmlString).toString())', 'rule': None},
{'template': 'test.unwrap(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.wrap(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.wrapAll(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.wrapInner(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.after(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.before(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.replaceAll(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
{'template': 'test.replaceWith(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insertion},
)
def test_jquery_insertion(self, data):
"""
Test check_javascript_file_is_safe with JQuery insertion functions
other than append(), prepend() and html() that take content as an
argument (e.g. before(), after()).
"""
linter = _build_javascript_linter()
results = FileResults('')
linter.check_javascript_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
@data(
{'template': ' element.parentNode.appendTo(target);', 'rule': None},
{'template': ' test.render().el.appendTo(target);', 'rule': None},
{'template': ' test.render().$el.appendTo(target);', 'rule': None},
{'template': ' test.$element.appendTo(target);', 'rule': None},
{'template': ' test.testEl.appendTo(target);', 'rule': None},
{'template': '$element.appendTo(target);', 'rule': None},
{'template': 'el.appendTo(target);', 'rule': None},
{'template': 'testEl.appendTo(target);', 'rule': None},
{'template': 'testEl.prependTo(target);', 'rule': None},
{'template': 'testEl.insertAfter(target);', 'rule': None},
{'template': 'testEl.insertBefore(target);', 'rule': None},
{'template': 'anycall().appendTo(target)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insert_into_target},
{'template': 'anything.appendTo(target)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insert_into_target},
{'template': 'anything.prependTo(target)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insert_into_target},
{'template': 'anything.insertAfter(target)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insert_into_target},
{'template': 'anything.insertBefore(target)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_insert_into_target},
)
def test_jquery_insert_to_target(self, data):
"""
Test check_javascript_file_is_safe with JQuery insert to target
functions that take a target as an argument, like appendTo() and
prependTo().
"""
linter = _build_javascript_linter()
results = FileResults('')
linter.check_javascript_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
@data(
{'template': 'test.html()', 'rule': None},
{'template': 'test.html( )', 'rule': None},
{'template': "test.html( '' )", 'rule': None},
{'template': "test.html('')", 'rule': None},
{'template': 'test.html("")', 'rule': None},
{'template': 'test.html(HtmlUtils.ensureHtml(htmlSnippet).toString())', 'rule': None},
{'template': 'HtmlUtils.setHtml($el, someHtml)', 'rule': None},
{'template': 'test.html("any string")', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_html},
{'template': 'test.html("broken string)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_html},
{'template': 'test.html("檌檒濦")', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_html},
{'template': 'test.html(anything)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_jquery_html},
)
def test_jquery_html(self, data):
"""
Test check_javascript_file_is_safe with JQuery html()
"""
linter = _build_javascript_linter()
results = FileResults('')
linter.check_javascript_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
@data(
{'template': '_.escape(message)', 'rule': None},
{'template': 'anything.escape(message)', 'rule': None},
{'template': 'anythingescape(message)', 'rule': None},
{'template': '$escape(message)', 'rule': None},
{'template': '_escape(message)', 'rule': None},
{'template': 'escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
{'template': '(escape(message))', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
{'template': ' escape(message))', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
{'template': 'window.escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
{'template': '(window.escape(message)', 'rule': JAVASCRIPT_LINTER_RULESET.javascript_escape},
)
def test_javascript_escape(self, data):
"""
Test check_javascript_file_is_safe with interpolate()
"""
linter = _build_javascript_linter()
results = FileResults('')
linter.check_javascript_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
@ddt
class TestPythonLinter(TestLinter):
"""
Test PythonLinter
"""
@data(
{'template': 'm = "Plain text " + message + "plain text"', 'rule': None},
{'template': 'm = "檌檒濦 " + message + "plain text"', 'rule': None},
{'template': ' # m = "
" + commentedOutMessage + "
"', 'rule': None},
{'template': 'm = "
" + message + "
"', 'rule': [PYTHON_LINTER_RULESET.python_concat_html, PYTHON_LINTER_RULESET.python_concat_html]},
{'template': 'm = "
" + message + "
"', 'rule': [PYTHON_LINTER_RULESET.python_concat_html, PYTHON_LINTER_RULESET.python_concat_html]},
{'template': 'm = "
" + message + " broken string', 'rule': PYTHON_LINTER_RULESET.python_parse_error},
)
def test_concat_with_html(self, data):
"""
Test check_python_file_is_safe with concatenating strings and HTML
"""
linter = PythonLinter()
results = FileResults('')
linter.check_python_file_is_safe(data['template'], results)
self._validate_data_rules(data, results)
def test_check_python_expression_display_name(self):
"""
Test _check_python_file_is_safe with display_name_with_default_escaped
fails.
"""
linter = PythonLinter()
results = FileResults('')
python_file = textwrap.dedent("""
context = {
'display_name': self.display_name_with_default_escaped,
}
""")
linter.check_python_file_is_safe(python_file, results)
assert len(results.violations) == 1
assert results.violations[0].rule == PYTHON_LINTER_RULESET.python_deprecated_display_name
def test_check_custom_escaping(self):
"""
Test _check_python_file_is_safe fails when custom escapins is used.
"""
linter = PythonLinter()
results = FileResults('')
python_file = textwrap.dedent("""
msg = mmlans.replace('<', '<')
""")
linter.check_python_file_is_safe(python_file, results)
assert len(results.violations) == 1
assert results.violations[0].rule == PYTHON_LINTER_RULESET.python_custom_escape
@data(
{
'python':
textwrap.dedent("""
msg = Text("Mixed {span_start}text{span_end}").format(
span_start=HTML(""),
span_end=HTML(""),
)
"""),
'rule': None
},
{
'python':
textwrap.dedent("""
msg = "Mixed {span_start}text{span_end}".format(
span_start=HTML(""),
span_end=HTML(""),
)
"""),
'rule': PYTHON_LINTER_RULESET.python_requires_html_or_text
},
{
'python':
textwrap.dedent("""
msg = "Mixed {span_start}{text}{span_end}".format(
span_start=HTML(""),
text=Text("This should still break."),
span_end=HTML(""),
)
"""),
'rule': PYTHON_LINTER_RULESET.python_requires_html_or_text
},
{
'python':
textwrap.dedent("""
msg = Text("Mixed {link_start}text{link_end}".format(
link_start=HTML("").format(url),
link_end=HTML(""),
))
"""),
'rule': [PYTHON_LINTER_RULESET.python_close_before_format, PYTHON_LINTER_RULESET.python_requires_html_or_text]
},
{
'python':
textwrap.dedent("""
msg = Text("Mixed {link_start}text{link_end}").format(
link_start=HTML("".format(url)),
link_end=HTML(""),
)
"""),
'rule': PYTHON_LINTER_RULESET.python_close_before_format
},
{
'python':
textwrap.dedent("""
msg = Text("Mixed {link_start}text{link_end}".format(
link_start=HTML("".format(HTML(''))),
link_end=HTML(""),
))
"""),
'rule':
[
PYTHON_LINTER_RULESET.python_close_before_format,
PYTHON_LINTER_RULESET.python_requires_html_or_text,
PYTHON_LINTER_RULESET.python_close_before_format,
PYTHON_LINTER_RULESET.python_requires_html_or_text
]
},
{
'python':
textwrap.dedent("""
msg = "Mixed {span_start}text{span_end}".format(
span_start="",
span_end="",
)
"""),
'rule': [PYTHON_LINTER_RULESET.python_wrap_html, PYTHON_LINTER_RULESET.python_wrap_html]
},
{
'python':
textwrap.dedent("""
msg = Text(_("String with multiple lines "
"{link_start}unenroll{link_end} "
"and final line")).format(
link_start=HTML(
''
).format(
# Line comment with ' to throw off parser
course_id=course_overview.id
),
link_end=HTML(''),
)
"""),
'rule': None
},
{
'python': "msg = ''",
'rule': None
},
{
'python': "msg = HTML('')",
'rule': None
},
{
'python': r"""msg = ''.format(message)""",
'rule': PYTHON_LINTER_RULESET.python_wrap_html
},
{
'python': r"""r'regex with {} and named group(?P\d+)?$'.format(test)""",
'rule': None
},
{
'python': r"""msg = ''.format(self.title)
"""),
'rule': None
},
{
'python': r"""msg = '%s
' % message""",
'rule': PYTHON_LINTER_RULESET.python_interpolate_html
},
{
'python': "msg = HTML('
'",
'rule': PYTHON_LINTER_RULESET.python_parse_error
},
)
def test_check_python_with_text_and_html(self, data):
"""
Test _check_python_file_is_safe tests for proper use of Text() and
Html().
"""
linter = PythonLinter()
results = FileResults('')
file_content = textwrap.dedent(data['python'])
linter.check_python_file_is_safe(file_content, results)
self._validate_data_rules(data, results)
def test_check_python_with_text_and_html_mixed(self):
"""
Test _check_python_file_is_safe tests for proper use of Text() and
Html() for a Python file with a mix of rules.
"""
linter = PythonLinter()
results = FileResults('')
file_content = textwrap.dedent("""
msg1 = '
".format(url)),
link_end="",
)
msg3 = '
{response}
''').format(response=response_text)
""",
'rule': PYTHON_LINTER_RULESET.python_wrap_html,
'start_line': 2,
},
{
'python':
"""
def function(self):
'''
Function comment.
'''
response_str = textwrap.dedent('''
{response}
''').format(response=response_text)
""",
'rule': PYTHON_LINTER_RULESET.python_wrap_html,
'start_line': 6,
},
{
'python':
"""
def function(self):
'''
Function comment.
'''
response_str = '''
{response}
'''.format(response=response_text)
""",
'rule': PYTHON_LINTER_RULESET.python_wrap_html,
'start_line': 6,
},
{
'python':
"""
def function(self):
'''
Function comment.
'''
response_str = textwrap.dedent('''
\"\"\" Do we care about a nested triple quote? \"\"\"
{response}
''').format(response=response_text)
""",
'rule': PYTHON_LINTER_RULESET.python_wrap_html,
'start_line': 6,
},
)
def test_check_python_with_triple_quotes(self, data):
"""
Test _check_python_file_is_safe with triple quotes.
"""
linter = PythonLinter()
results = FileResults('')
file_content = textwrap.dedent(data['python'])
linter.check_python_file_is_safe(file_content, results)
self._validate_data_rules(data, results)
assert results.violations[0].start_line == data['start_line']
@ddt
class TestMakoTemplateLinter(TestLinter):
"""
Test MakoTemplateLinter
"""
@data(
{'directory': 'lms/templates', 'expected': True},
{'directory': 'lms/templates/support', 'expected': True},
{'directory': 'lms/templates/support', 'expected': True},
{'directory': 'test_root/staticfiles/templates', 'expected': False},
{'directory': './test_root/staticfiles/templates', 'expected': False},
{'directory': './some/random/path', 'expected': False},
)
def test_is_valid_directory(self, data):
"""
Test _is_valid_directory correctly determines mako directories
"""
linter = _build_mako_linter()
linter._skip_mako_dirs = ('test_root',)
assert linter._is_valid_directory(data['directory']) == data['expected']
@data(
{
'template': '\n <%page expression_filter="h"/>',
'rule': None
},
{
'template':
'\n <%page args="section_data" expression_filter="h" /> ',
'rule': None
},
{
'template': '\n ## <%page expression_filter="h"/>',
'rule': MAKO_LINTER_RULESET.mako_missing_default
},
{
'template':
'\n <%page expression_filter="h" /> '
'\n <%page args="section_data"/>',
'rule': MAKO_LINTER_RULESET.mako_multiple_page_tags
},
{
'template':
'\n <%page expression_filter="h" /> '
'\n ## <%page args="section_data"/>',
'rule': None
},
{
'template': '\n <%page args="section_data" /> ',
'rule': MAKO_LINTER_RULESET.mako_missing_default
},
{
'template':
'\n <%page args="section_data"/>
',
'rule': MAKO_LINTER_RULESET.mako_missing_default
},
{
'template': '\n',
'rule': MAKO_LINTER_RULESET.mako_missing_default
},
)
def test_check_page_default(self, data):
"""
Test _check_mako_file_is_safe with different page defaults
"""
linter = _build_mako_linter()
results = FileResults('')
linter._check_mako_file_is_safe(data['template'], results)
num_violations = 0 if data['rule'] is None else 1
assert len(results.violations) == num_violations
if num_violations > 0:
assert results.violations[0].rule == data['rule']
@data(
{'expression': '${x}', 'rule': None},
{'expression': '${{unbalanced}', 'rule': MAKO_LINTER_RULESET.mako_unparseable_expression},
{'expression': '${x | n}', 'rule': MAKO_LINTER_RULESET.mako_invalid_html_filter},
{'expression': '${x | n, decode.utf8}', 'rule': None},
{'expression': '${x | h}', 'rule': MAKO_LINTER_RULESET.mako_unwanted_html_filter},
{'expression': ' ## ${commented_out | h}', 'rule': None},
{'expression': '${x | n, dump_js_escaped_json}', 'rule': MAKO_LINTER_RULESET.mako_invalid_html_filter},
)
def test_check_mako_expressions_in_html(self, data):
"""
Test _check_mako_file_is_safe in html context provides appropriate violations
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
{expression}
""".format(expression=data['expression']))
linter._check_mako_file_is_safe(mako_template, results)
self._validate_data_rules(data, results)
def test_check_mako_expression_display_name(self):
"""
Test _check_mako_file_is_safe with display_name_with_default_escaped
fails.
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
${course.display_name_with_default_escaped}
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 1
assert results.violations[0].rule == MAKO_LINTER_RULESET.python_deprecated_display_name
@data(
{
# Python blocks between <% ... %> use the same Python linting as
# Mako expressions between ${ ... }. This single test verifies
# that these blocks are linted. The individual linting rules are
# tested in the Mako expression tests that follow.
'expression':
textwrap.dedent("""
<%
a_link_start = '
' + _("your course summary page") + ''
a_link = a_link_start + lms_link_for_about_page + a_link_end
text = _("Introductions, prerequisites, FAQs that are used on %s (formatted in HTML)") % a_link
%>
"""),
'rule': [MAKO_LINTER_RULESET.python_wrap_html, MAKO_LINTER_RULESET.python_concat_html, MAKO_LINTER_RULESET.python_wrap_html]
},
{
'expression':
textwrap.dedent("""
${"Mixed {span_start}text{span_end}".format(
span_start=HTML("
"),
span_end=HTML(""),
)}
"""),
'rule': MAKO_LINTER_RULESET.python_requires_html_or_text
},
{
'expression':
textwrap.dedent("""
${Text("Mixed {span_start}text{span_end}").format(
span_start=HTML("
"),
span_end=HTML(""),
)}
"""),
'rule': None
},
{
'expression':
textwrap.dedent("""
${"Mixed {span_start}{text}{span_end}".format(
span_start=HTML("
"),
text=Text("This should still break."),
span_end=HTML(""),
)}
"""),
'rule': MAKO_LINTER_RULESET.python_requires_html_or_text
},
{
'expression':
textwrap.dedent("""
${Text("Mixed {link_start}text{link_end}".format(
link_start=HTML("
").format(url),
link_end=HTML(""),
))}
"""),
'rule': [MAKO_LINTER_RULESET.python_close_before_format, MAKO_LINTER_RULESET.python_requires_html_or_text]
},
{
'expression':
textwrap.dedent("""
${Text("Mixed {link_start}text{link_end}").format(
link_start=HTML("
".format(url)),
link_end=HTML(""),
)}
"""),
'rule': MAKO_LINTER_RULESET.python_close_before_format
},
{
'expression':
textwrap.dedent("""
${"Mixed {span_start}text{span_end}".format(
span_start="
",
span_end="",
)}
"""),
'rule': [MAKO_LINTER_RULESET.python_wrap_html, MAKO_LINTER_RULESET.python_wrap_html]
},
{
'expression':
textwrap.dedent("""
${Text(_("String with multiple lines "
"{link_start}unenroll{link_end} "
"and final line")).format(
link_start=HTML(
'
'
).format(
course_id=course_overview.id
),
link_end=HTML(''),
)}
"""),
'rule': None
},
{
'expression': "${'
'}",
'rule': MAKO_LINTER_RULESET.python_wrap_html
},
{
'expression': "${'Embedded HTML
'}",
'rule': MAKO_LINTER_RULESET.python_wrap_html
},
{
'expression': "${ HTML('
') }",
'rule': None
},
{
'expression': "${HTML(render_entry(map['entries'], child))}",
'rule': None
},
{
'expression': "${ '
' + 'some other text' }",
'rule': [MAKO_LINTER_RULESET.python_concat_html, MAKO_LINTER_RULESET.python_wrap_html]
},
{
'expression': "${ HTML('
missing closing parentheses.' }",
'rule': MAKO_LINTER_RULESET.python_parse_error
},
{
'expression': "${'Rock & Roll'}",
'rule': MAKO_LINTER_RULESET.mako_html_entities
},
{
'expression': "${'Rock & Roll'}",
'rule': MAKO_LINTER_RULESET.mako_html_entities
},
)
def test_check_mako_with_text_and_html(self, data):
"""
Test _check_mako_file_is_safe tests for proper use of Text() and Html().
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
{expression}
""".format(expression=data['expression']))
linter._check_mako_file_is_safe(mako_template, results)
self._validate_data_rules(data, results)
def test_check_mako_entity_with_no_default(self):
"""
Test _check_mako_file_is_safe does not fail on entities when
safe-by-default is not set.
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = "${'Rock & Roll'}"
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 1
assert results.violations[0].rule == MAKO_LINTER_RULESET.mako_missing_default
def test_check_mako_expression_default_disabled(self):
"""
Test _check_mako_file_is_safe with disable pragma for safe-by-default
works to designate that this is not a Mako file
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
# This is anything but a Mako file.
# pragma can appear anywhere in file
# xss-lint: disable=mako-missing-default
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 1
assert results.violations[0].is_disabled
def test_check_mako_expression_disabled(self):
"""
Test _check_mako_file_is_safe with disable pragma results in no
violation
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
## xss-lint: disable=mako-unwanted-html-filter
${x | h}
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 1
assert results.violations[0].is_disabled
@data(
{'template': '{% extends "wiki/base.html" %}'},
{'template': '{{ message }}'},
{'template': '{# comment #}'},
)
def test_check_mako_on_django_template(self, data):
"""
Test _check_mako_file_is_safe with disable pragma results in no
violation
"""
linter = _build_mako_linter()
results = FileResults('')
linter._check_mako_file_is_safe(data['template'], results)
assert len(results.violations) == 0
def test_check_mako_expressions_in_html_with_escape_filter(self):
"""
Test _check_mako_file_is_safe results in no violations,
when strip_all_tags_but_br filter is applied in html context
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
${x | n, strip_all_tags_but_br}
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 0
def test_check_mako_expressions_in_html_without_default(self):
"""
Test _check_mako_file_is_safe in html context without the page level
default h filter suppresses expression level violation
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
${x | h}
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 1
assert results.violations[0].rule == MAKO_LINTER_RULESET.mako_missing_default
@data(
{'expression': '${x}', 'rule': MAKO_LINTER_RULESET.mako_invalid_js_filter},
{'expression': '${{unbalanced}', 'rule': MAKO_LINTER_RULESET.mako_unparseable_expression},
{'expression': '${x | n}', 'rule': MAKO_LINTER_RULESET.mako_invalid_js_filter},
{'expression': '${x | h}', 'rule': MAKO_LINTER_RULESET.mako_invalid_js_filter},
{'expression': '${x | n, dump_js_escaped_json}', 'rule': None},
{'expression': '${x | n, decode.utf8}', 'rule': None},
)
def test_check_mako_expressions_in_javascript(self, data):
"""
Test _check_mako_file_is_safe in JavaScript script context provides
appropriate violations
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
## switch to JavaScript context
## switch back to HTML context
${{x}}
""".format(expression=data['expression']))
linter._check_mako_file_is_safe(mako_template, results)
self._validate_data_rules(data, results)
@data(
{'expression': '${x}', 'rule': MAKO_LINTER_RULESET.mako_invalid_js_filter},
{'expression': '"${x | n, js_escaped_string}"', 'rule': None},
)
def test_check_mako_expressions_in_require_module(self, data):
"""
Test _check_mako_file_is_safe in JavaScript require context provides
appropriate violations
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
## switch to JavaScript context (after next line)
<%static:require_module module_name="${{x}}" class_name="TestFactory">
{expression}
%static:require_module>
## switch back to HTML context
${{x}}
""".format(expression=data['expression']))
linter._check_mako_file_is_safe(mako_template, results)
self._validate_data_rules(data, results)
@data(
{'expression': '${x}', 'rule': MAKO_LINTER_RULESET.mako_invalid_js_filter},
{'expression': '"${x | n, js_escaped_string}"', 'rule': None},
)
def test_check_mako_expressions_in_require_js(self, data):
"""
Test _check_mako_file_is_safe in JavaScript require js context provides
appropriate violations
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
# switch to JavaScript context
<%block name="requirejs">
{expression}
%block>
## switch back to HTML context
${{x}}
""".format(expression=data['expression']))
linter._check_mako_file_is_safe(mako_template, results)
self._validate_data_rules(data, results)
@data(
{'media_type': 'text/javascript', 'rule': None},
{'media_type': 'text/ecmascript', 'rule': None},
{'media_type': 'application/ecmascript', 'rule': None},
{'media_type': 'application/javascript', 'rule': None},
{'media_type': 'text/x-mathjax-config', 'rule': None},
{'media_type': 'json/xblock-args', 'rule': None},
{'media_type': 'text/template', 'rule': MAKO_LINTER_RULESET.mako_invalid_html_filter},
{'media_type': 'unknown/type', 'rule': MAKO_LINTER_RULESET.mako_unknown_context},
)
def test_check_mako_expressions_in_script_type(self, data):
"""
Test _check_mako_file_is_safe in script tag with different media types
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
# switch to JavaScript context
## switch back to HTML context
${{x}}
""").format(data['media_type'])
linter._check_mako_file_is_safe(mako_template, results)
self._validate_data_rules(data, results)
def test_check_mako_expressions_in_mixed_contexts(self):
"""
Test _check_mako_file_is_safe in mixed contexts provides
appropriate violations
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
${x | h}
${x | h}
<%static:require_module module_name="${x}" class_name="TestFactory">
${x | h}
%static:require_module>
${x | h}
<%static:studiofrontend page="${x}">
${x | h}
%static:studiofrontend>
${x | h}
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 7
assert results.violations[0].rule == MAKO_LINTER_RULESET.mako_unwanted_html_filter
assert results.violations[1].rule == MAKO_LINTER_RULESET.mako_invalid_js_filter
assert results.violations[2].rule == MAKO_LINTER_RULESET.mako_unwanted_html_filter
assert results.violations[3].rule == MAKO_LINTER_RULESET.mako_invalid_js_filter
assert results.violations[4].rule == MAKO_LINTER_RULESET.mako_unwanted_html_filter
assert results.violations[5].rule == MAKO_LINTER_RULESET.mako_invalid_js_filter
assert results.violations[6].rule == MAKO_LINTER_RULESET.mako_unwanted_html_filter
def test_check_mako_expressions_javascript_strings(self):
"""
Test _check_mako_file_is_safe javascript string specific rules.
- mako_js_missing_quotes
- mako_js_html_string
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 3
assert results.violations[0].rule == MAKO_LINTER_RULESET.mako_js_missing_quotes
assert results.violations[1].rule == MAKO_LINTER_RULESET.mako_js_html_string
assert results.violations[2].rule == MAKO_LINTER_RULESET.mako_js_html_string
def test_check_javascript_in_mako_javascript_context(self):
"""
Test _check_mako_file_is_safe with JavaScript error in JavaScript
context.
"""
linter = _build_mako_linter()
results = FileResults('')
mako_template = textwrap.dedent("""
<%page expression_filter="h"/>
""")
linter._check_mako_file_is_safe(mako_template, results)
assert len(results.violations) == 1
assert results.violations[0].rule == MAKO_LINTER_RULESET.javascript_concat_html
assert results.violations[0].start_line == 4
@data(
{'template': "\n${x | n}", 'parseable': True},
{
'template': textwrap.dedent(
"""
${(
'tabbed-multi-line-expression'
) | n}
"""),
'parseable': True
},
{'template': "${{unparseable}", 'parseable': False},
)
def test_expression_detailed_results(self, data):
"""
Test _check_mako_file_is_safe provides detailed results, including line
numbers, columns, and line
"""
linter = _build_mako_linter()
results = FileResults('')
linter._check_mako_file_is_safe(data['template'], results)
assert len(results.violations) == 2
assert results.violations[0].rule == MAKO_LINTER_RULESET.mako_missing_default
violation = results.violations[1]
lines = list(data['template'].splitlines())
assert ('${' in lines[(violation.start_line - 1)])
assert lines[(violation.start_line - 1)].startswith('${', (violation.start_column - 1))
if data['parseable']:
assert ('}' in lines[(violation.end_line - 1)])
assert lines[(violation.end_line - 1)].startswith('}', ((violation.end_column - len('}')) - 1))
else:
assert violation.start_line == violation.end_line
assert violation.end_column == '?'
assert len(violation.lines) == ((violation.end_line - violation.start_line) + 1)
for line_index in range(0, len(violation.lines)):
assert violation.lines[line_index] == lines[((line_index + violation.start_line) - 1)]
@data(
{'template': "${x}"},
{'template': "\n ${x}"},
{'template': "${x} "},
{'template': "${{test-balanced-delims}} "},
{'template': "${'{unbalanced in string'}"},
{'template': "${'unbalanced in string}'}"},
{'template': "${(\n 'tabbed-multi-line-expression'\n )}"},
)
def test_find_mako_expressions(self, data):
"""
Test _find_mako_expressions for parseable expressions
"""
linter = _build_mako_linter()
expressions = linter._find_mako_expressions(data['template'])
assert len(expressions) == 1
start_index = expressions[0].start_index
end_index = expressions[0].end_index
assert data['template'][start_index:end_index] == data['template'].strip()
assert expressions[0].expression == data['template'].strip()
@data(
{'template': " ${{unparseable} ${}", 'start_index': 1},
{'template': " ${'unparseable} ${}", 'start_index': 1},
)
def test_find_unparseable_mako_expressions(self, data):
"""
Test _find_mako_expressions for unparseable expressions
"""
linter = _build_mako_linter()
expressions = linter._find_mako_expressions(data['template'])
assert (2 <= len(expressions))
assert expressions[0].start_index == data['start_index']
assert expressions[0].expression is None
@data(
{
'template': '${""}',
'result': {'start_index': 2, 'end_index': 4, 'quote_length': 1}
},
{
'template': "${''}",
'result': {'start_index': 2, 'end_index': 4, 'quote_length': 1}
},
{
'template': "${'Hello'}",
'result': {'start_index': 2, 'end_index': 9, 'quote_length': 1}
},
{
'template': '${""" triple """}',
'result': {'start_index': 2, 'end_index': 16, 'quote_length': 3}
},
{
'template': r""" ${" \" \\"} """,
'result': {'start_index': 3, 'end_index': 11, 'quote_length': 1}
},
{
'template': "${'broken string}",
'result': {'start_index': 2, 'end_index': None, 'quote_length': None}
},
)
def test_parse_string(self, data):
"""
Test _parse_string helper
"""
linter = _build_mako_linter()
parse_string = ParseString(data['template'], data['result']['start_index'], len(data['template']))
string_dict = {
'start_index': parse_string.start_index,
'end_index': parse_string.end_index,
'quote_length': parse_string.quote_length,
}
self.assertDictEqual(string_dict, data['result'])
if parse_string.end_index is not None:
assert data['template'][parse_string.start_index:parse_string.end_index] == parse_string.string
start_inner_index = parse_string.start_index + parse_string.quote_length
end_inner_index = parse_string.end_index - parse_string.quote_length
assert data['template'][start_inner_index:end_inner_index] == parse_string.string_inner
@ddt
class TestDjangoTemplateLinter(TestLinter):
"""
Test DjangoTemplateLinter
"""
@data(
{'expression': '{% trans "whatever" as tmsg %}{{tmsg|force_escape}}',
'rule': None},
{'expression': '{% trans "whatever" as tmsgx %}{{tmsg|force_escape}}',
'rule': DJANGO_LINTER_RULESET.django_trans_escape_variable_mismatch},
{'expression': '{% trans "whatever" as tmsgx %}{{tmsgx|force_escap}}',
'rule': DJANGO_LINTER_RULESET.django_trans_invalid_escape_filter},
{'expression': '{% trans "whatever" as tmsg %}',
'rule': DJANGO_LINTER_RULESET.django_trans_missing_escape},
{'expression': '{% trans "whatever" %}',
'rule': DJANGO_LINTER_RULESET.django_trans_missing_escape},
{'expression': '{% trans "{span_start} whatever {span_end}" as tmsg %}',
'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing},
{'expression': '{% trans "{span_start} whatever {span_end}" as tmsg %}{{tmsg|force_filter}}',
'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing},
{'expression': """
{% trans "{span_start} whatever {span_end}" as tmsg %}
{% interpolate_html tmsg user_name=user_data.name start_span='
'|safe end_span=''|safe %}
""", 'rule': None},
{'expression': """
{% trans "{span_start} whatever {span_end}" as tmsg %}
{% interpolate_html %}
""", 'rule': [DJANGO_LINTER_RULESET.django_html_interpolation_missing, DJANGO_LINTER_RULESET.django_html_interpolation_invalid_tag]},
{'expression': """
{% trans "{span_start} whatever {span_end}" as tmsg %}
{% interpolate_html t start_span='
'|safe end_span=''|safe %}
""", 'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing},
{'expression': """
{% trans "{span_start} whatever {span_end}" as tmsg %}
{% interpolate_html tmsg start_span='
'|unknown end_span=''|safe %}
""", 'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing_safe_filter},
{'expression': """
{% trans "
whatever " as tmsg %}
""", 'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing},
{'expression': """
{% trans "
whatever " %}
""", 'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing},
{'expression': """
{% filter force_escape %}
{% blocktrans %}
Some translation string
{% endblocktrans %}
{% endfilter %}
""", 'rule': None},
{'expression': """
{% filter force_escape
{% blocktrans %}
Some translation string
{% endblocktrans %}
{% endfilter %}
""", 'rule': DJANGO_LINTER_RULESET.django_blocktrans_escape_filter_parse_error},
{'expression': """
{% filter someother_filter %}
{% blocktrans %}
Some translation strubg
{% endblocktrans %}
{% endfilter %}
""", 'rule': DJANGO_LINTER_RULESET.django_blocktrans_missing_escape_filter},
{'expression': """
{% filter force_escape xyz %}
{% blocktrans %}
Some translation string
{% endblocktrans %}
{% endfilter %}
""", 'rule': DJANGO_LINTER_RULESET.django_blocktrans_missing_escape_filter},
{'expression': """
{% blocktrans %}
Some translation string
{% endblocktrans %}
""", 'rule': DJANGO_LINTER_RULESET.django_blocktrans_missing_escape_filter},
{'expression': """
{% blocktrans %}
Some translation
whatever
{% endblocktrans %}
""", 'rule': DJANGO_LINTER_RULESET.django_html_interpolation_missing},
)
def test_check_django_expressions_in_html(self, data):
"""
Test _check_django_file_is_safe in html context provides appropriate violations
"""
linter = _build_django_linter()
results = FileResults('')
django_template = textwrap.dedent(
"""
{load_i18n}
{load_django_html}
{expression}
""".format(expression=data['expression'],
load_i18n='{% load i18n %}',
load_django_html='{% load django_html %}'))
linter._check_django_file_is_safe(django_template, results)
self._validate_data_rules(data, results)
def test_check_django_trans_expression_disabled(self):
"""
Test _check_django_file_is_safe with disable pragma results in no
violation
"""
linter = _build_django_linter()
results = FileResults('')
django_template = textwrap.dedent(
"""
{load_i18n}
{load_django_html}
{expression}
""".format(expression="""
{# xss-lint: disable=django-trans-missing-escape #}
{% trans 'Documentation' as tmsg%}
""",
load_i18n='{% load i18n %}',
load_django_html='{% load django_html %}'))
linter._check_django_file_is_safe(django_template, results)
assert len(results.violations) == 1
assert results.violations[0].is_disabled
def test_check_django_blocktrans_expression_disabled(self):
"""
Test _check_django_file_is_safe with disable pragma results in no
violation
"""
linter = _build_django_linter()
results = FileResults('')
django_template = textwrap.dedent(
"""
{load_i18n}
{load_django_html}
{expression}
""".format(expression="""
{# xss-lint: disable=django-blocktrans-missing-escape-filter #}
{% blocktrans %}
sometext
{% endblocktrans %}
""",
load_i18n='{% load i18n %}',
load_django_html='{% load django_html %}'))
linter._check_django_file_is_safe(django_template, results)
assert len(results.violations) == 1
assert results.violations[0].is_disabled
def test_check_django_trans_expression_commented(self):
"""
Test _check_django_file_is_safe with comment results in no
violation
"""
linter = _build_django_linter()
results = FileResults('')
django_template = textwrap.dedent(
"""
{load_i18n}
{load_django_html}
{expression}
""".format(expression="""
{# trans 'Documentation' as tmsg #}
""",
load_i18n='{% load i18n %}',
load_django_html='{% load django_html %}'))
linter._check_django_file_is_safe(django_template, results)
assert len(results.violations) == 0
def test_check_django_blocktrans_expression_commented(self):
"""
Test _check_django_file_is_safe with comment results in no
violation
"""
linter = _build_django_linter()
results = FileResults('')
django_template = textwrap.dedent(
"""
{load_i18n}
{load_django_html}
{expression}
""".format(expression="""
{% comment %}
{% blocktrans %}
{% endblocktrans %}
{% endcomment %}
""",
load_i18n='{% load i18n %}',
load_django_html='{% load django_html %}'))
linter._check_django_file_is_safe(django_template, results)
assert len(results.violations) == 0
def test_check_django_interpolate_tag_expression_commented(self):
"""
Test _check_django_file_is_safe with comment results in no
violation
"""
linter = _build_django_linter()
results = FileResults('')
django_template = textwrap.dedent(
"""
{load_i18n}
{load_django_html}
{expression}
""".format(expression="""
{% comment %}
{% interpolate_html %}
{% endcomment %}
""",
load_i18n='{% load i18n %}',
load_django_html='{% load django_html %}'))
linter._check_django_file_is_safe(django_template, results)
assert len(results.violations) == 0