Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
ff2e6dc1
Commit
ff2e6dc1
authored
Apr 11, 2016
by
Robert Raposa
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #12082 from edx/robrap/linter-mako
TNL-4324: Enhance linter for HTML() and Text()
parents
498ab887
96fe8afb
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
461 additions
and
165 deletions
+461
-165
scripts/safe_template_linter.py
+237
-52
scripts/tests/test_safe_template_linter.py
+224
-113
No files found.
scripts/safe_template_linter.py
View file @
ff2e6dc1
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
A linting tool to check if templates are safe
A linting tool to check if templates are safe
"""
"""
from
__future__
import
print_function
from
__future__
import
print_function
import
argparse
from
enum
import
Enum
from
enum
import
Enum
import
os
import
os
import
re
import
re
...
@@ -205,8 +206,8 @@ class Rules(Enum):
...
@@ -205,8 +206,8 @@ class Rules(Enum):
'mako-multiple-page-tags'
,
'mako-multiple-page-tags'
,
'A Mako template can only have one <
%
page> tag.'
'A Mako template can only have one <
%
page> tag.'
)
)
mako_unparsable_expression
=
(
mako_unpars
e
able_expression
=
(
'mako-unparsable-expression'
,
'mako-unpars
e
able-expression'
,
'The expression could not be properly parsed.'
'The expression could not be properly parsed.'
)
)
mako_unwanted_html_filter
=
(
mako_unwanted_html_filter
=
(
...
@@ -217,15 +218,30 @@ class Rules(Enum):
...
@@ -217,15 +218,30 @@ class Rules(Enum):
'mako-invalid-html-filter'
,
'mako-invalid-html-filter'
,
'The expression is using an invalid filter in an HTML context.'
'The expression is using an invalid filter in an HTML context.'
)
)
mako_invalid_js_filter
=
(
'mako-invalid-js-filter'
,
'The expression is using an invalid filter in a JavaScript context.'
)
mako_deprecated_display_name
=
(
mako_deprecated_display_name
=
(
'mako-deprecated-display-name'
,
'mako-deprecated-display-name'
,
'Replace deprecated display_name_with_default_escaped with display_name_with_default.'
'Replace deprecated display_name_with_default_escaped with display_name_with_default.'
)
)
mako_invalid_js_filter
=
(
mako_html_requires_text
=
(
'mako-invalid-js-filter'
,
'mako-html-requires-text'
,
'The expression is using an invalid filter in a JavaScript context.'
'You must begin with Text() if you use HTML() during interpolation.'
)
mako_close_before_format
=
(
'mako-close-before-format'
,
'You must close any call to Text() or HTML() before calling format().'
)
mako_text_redundant
=
(
'mako-text-redundant'
,
'Using Text() function without HTML() is unnecessary.'
)
mako_html_alone
=
(
'mako-html-alone'
,
"Only use HTML() alone with properly escaped HTML(), and make sure it is really alone."
)
)
underscore_not_escaped
=
(
underscore_not_escaped
=
(
'underscore-not-escaped'
,
'underscore-not-escaped'
,
'Expressions should be escaped using <
%-
expression
%
>.'
'Expressions should be escaped using <
%-
expression
%
>.'
...
@@ -421,7 +437,7 @@ class ExpressionRuleViolation(RuleViolation):
...
@@ -421,7 +437,7 @@ class ExpressionRuleViolation(RuleViolation):
line_number
,
line_number
,
column
,
column
,
rule_id
,
rule_id
,
self
.
lines
[
line_number
-
self
.
start_line
-
1
]
.
encode
(
encoding
=
'utf-8'
)
self
.
lines
[
line_number
-
self
.
start_line
]
.
encode
(
encoding
=
'utf-8'
)
),
file
=
out
)
),
file
=
out
)
...
@@ -630,13 +646,14 @@ class MakoTemplateLinter(object):
...
@@ -630,13 +646,14 @@ class MakoTemplateLinter(object):
for
expression
in
expressions
:
for
expression
in
expressions
:
if
expression
[
'expression'
]
is
None
:
if
expression
[
'expression'
]
is
None
:
results
.
violations
.
append
(
ExpressionRuleViolation
(
results
.
violations
.
append
(
ExpressionRuleViolation
(
Rules
.
mako_unparsable_expression
,
expression
Rules
.
mako_unpars
e
able_expression
,
expression
))
))
continue
continue
context
=
self
.
_get_context
(
contexts
,
expression
[
'start_index'
])
context
=
self
.
_get_context
(
contexts
,
expression
[
'start_index'
])
self
.
_check_filters
(
mako_template
,
expression
,
context
,
has_page_default
,
results
)
self
.
_check_filters
(
mako_template
,
expression
,
context
,
has_page_default
,
results
)
self
.
_check_deprecated_display_name
(
expression
,
results
)
self
.
_check_deprecated_display_name
(
expression
,
results
)
self
.
_check_html_and_text
(
expression
,
results
)
def
_check_deprecated_display_name
(
self
,
expression
,
results
):
def
_check_deprecated_display_name
(
self
,
expression
,
results
):
"""
"""
...
@@ -654,6 +671,52 @@ class MakoTemplateLinter(object):
...
@@ -654,6 +671,52 @@ class MakoTemplateLinter(object):
Rules
.
mako_deprecated_display_name
,
expression
Rules
.
mako_deprecated_display_name
,
expression
))
))
def
_check_html_and_text
(
self
,
expression
,
results
):
"""
Checks rules related to proper use of HTML() and Text().
Rule 1: If HTML() is called, the expression must begin with Text(), or
Rule 2: If HTML() is called alone, it must be the only call.
Rule 3: Both HTML() and Text() must be closed before any call to
format().
Rule 4: Using Text() without HTML() is unnecessary.
Arguments:
expression: A dict containing the start_index, end_index, and
expression (text) of the expression.
results: A list of results into which violations will be added.
"""
# strip '${' and '}' and whitespace from ends
expression_inner
=
expression
[
'expression'
][
2
:
-
1
]
.
strip
()
if
'HTML('
in
expression_inner
:
if
expression_inner
.
startswith
(
'HTML('
):
close_paren_index
=
self
.
_find_closing_char_index
(
None
,
"("
,
")"
,
expression_inner
,
len
(
'HTML('
),
0
)
# check that the close paren is at the end of the expression.
if
close_paren_index
!=
len
(
expression_inner
)
-
1
:
results
.
violations
.
append
(
ExpressionRuleViolation
(
Rules
.
mako_html_alone
,
expression
))
elif
expression_inner
.
startswith
(
'Text('
)
is
False
:
results
.
violations
.
append
(
ExpressionRuleViolation
(
Rules
.
mako_html_requires_text
,
expression
))
else
:
if
'Text('
in
expression_inner
:
results
.
violations
.
append
(
ExpressionRuleViolation
(
Rules
.
mako_text_redundant
,
expression
))
for
match
in
re
.
finditer
(
"(HTML
\
(|Text
\
()"
,
expression_inner
):
close_paren_index
=
self
.
_find_closing_char_index
(
None
,
"("
,
")"
,
expression_inner
,
match
.
end
(),
0
)
if
0
<=
close_paren_index
:
# the argument sent to HTML() or Text()
argument
=
expression_inner
[
match
.
end
():
close_paren_index
]
if
".format("
in
argument
:
results
.
violations
.
append
(
ExpressionRuleViolation
(
Rules
.
mako_close_before_format
,
expression
))
def
_check_filters
(
self
,
mako_template
,
expression
,
context
,
has_page_default
,
results
):
def
_check_filters
(
self
,
mako_template
,
expression
,
context
,
has_page_default
,
results
):
"""
"""
Checks that the filters used in the given Mako expression are valid
Checks that the filters used in the given Mako expression are valid
...
@@ -796,9 +859,9 @@ class MakoTemplateLinter(object):
...
@@ -796,9 +859,9 @@ class MakoTemplateLinter(object):
while
True
:
while
True
:
start_index
=
mako_template
.
find
(
start_delim
,
start_index
)
start_index
=
mako_template
.
find
(
start_delim
,
start_index
)
if
(
start_index
<
0
)
:
if
start_index
<
0
:
break
break
end_index
=
self
.
_find_
balanced_end_curly
(
mako_template
,
start_index
+
len
(
start_delim
),
0
)
end_index
=
self
.
_find_
closing_char_index
(
start_delim
,
'{'
,
'}'
,
mako_template
,
start_index
+
len
(
start_delim
),
0
)
if
end_index
<
0
:
if
end_index
<
0
:
expression
=
None
expression
=
None
...
@@ -817,39 +880,117 @@ class MakoTemplateLinter(object):
...
@@ -817,39 +880,117 @@ class MakoTemplateLinter(object):
return
expressions
return
expressions
def
_find_
balanced_end_curly
(
self
,
mako_template
,
start_index
,
num_open_curlie
s
):
def
_find_
closing_char_index
(
self
,
start_delim
,
open_char
,
close_char
,
template
,
start_index
,
num_open_char
s
):
"""
"""
Finds the end index of the Mako expression's ending curly brace. Skips
Finds the index of the closing char that matches the opening char.
any additional open/closed braces that are balanced inside. Does not
take into consideration strings.
For example, this could be used to find the end of a Mako expression,
where the open and close characters would be '{' and '}'.
Arguments:
Arguments:
mako_template: The template text.
start_delim: If provided (e.g. '${' for Mako expressions), the
start_index: The start index of the Mako expression.
closing character must be found before the next start_delim.
num_open_curlies: The current number of open expressions.
open_char: The opening character to be matched (e.g '{')
close_char: The closing character to be matched (e.g '}')
template: The template to be searched.
start_index: The start index of the last open char.
num_open_chars: The current number of open chars.
Returns:
Returns:
The
end index of the expression
, or -1 if unparseable.
The
index of the closing character
, or -1 if unparseable.
"""
"""
end_curly_index
=
mako_template
.
find
(
'}'
,
start_index
)
close_char_index
=
template
.
find
(
close_char
,
start_index
)
if
end_curly
_index
<
0
:
if
close_char
_index
<
0
:
# if we can't find an end_c
urly
, let's just quit
# if we can't find an end_c
har
, let's just quit
return
end_curly_index
return
-
1
open_curly_index
=
mako_template
.
find
(
'{'
,
start_index
,
end_curly_index
)
open_char_index
=
template
.
find
(
open_char
,
start_index
,
close_char_index
)
start_quote_index
=
self
.
_find_string_start
(
template
,
start_index
,
close_char_index
)
if
(
open_curly_index
>=
0
)
and
(
open_curly_index
<
end_curly_index
):
if
0
<=
start_quote_index
:
if
mako_template
[
open_curly_index
-
1
]
==
'$'
:
string_end_index
=
self
.
_parse_string
(
template
,
start_quote_index
)[
'end_index'
]
# assume if we find "${" it is the start of the next expression
if
string_end_index
<
0
:
# and we have a parse error
return
-
1
return
-
1
else
:
else
:
return
self
.
_find_balanced_end_curly
(
mako_template
,
open_curly_index
+
1
,
num_open_curlies
+
1
)
return
self
.
_find_closing_char_index
(
start_delim
,
open_char
,
close_char
,
template
,
string_end_index
,
num_open_chars
)
if
num_open_curlies
==
0
:
if
(
open_char_index
>=
0
)
and
(
open_char_index
<
close_char_index
):
return
end_curly_index
if
start_delim
is
not
None
:
# if we find another starting delim, consider this unparseable
start_delim_index
=
template
.
find
(
start_delim
,
start_index
,
close_char_index
)
if
start_delim_index
<
open_char_index
:
return
-
1
return
self
.
_find_closing_char_index
(
start_delim
,
open_char
,
close_char
,
template
,
open_char_index
+
1
,
num_open_chars
+
1
)
if
num_open_chars
==
0
:
return
close_char_index
else
:
else
:
return
self
.
_find_balanced_end_curly
(
mako_template
,
end_curly_index
+
1
,
num_open_curlies
-
1
)
return
self
.
_find_closing_char_index
(
start_delim
,
open_char
,
close_char
,
template
,
close_char_index
+
1
,
num_open_chars
-
1
)
def
_find_string_start
(
self
,
template
,
start_index
,
end_index
):
"""
Finds the index of the end of start of a string. In other words, the
first single or double quote.
Arguments:
template: The template to be searched.
start_index: The start index to search.
end_index: The end index to search before.
num_open_chars: The current number of open expressions.
Returns:
The start index of the first single or double quote, or -1 if
no quote was found.
"""
double_quote_index
=
template
.
find
(
'"'
,
start_index
,
end_index
)
single_quote_index
=
template
.
find
(
"'"
,
start_index
,
end_index
)
if
0
<=
single_quote_index
or
0
<=
double_quote_index
:
if
0
<=
single_quote_index
and
0
<=
double_quote_index
:
return
min
(
single_quote_index
,
double_quote_index
)
else
:
return
max
(
single_quote_index
,
double_quote_index
)
return
-
1
def
_parse_string
(
self
,
template
,
start_index
):
"""
Finds the indices of a string inside a template.
Arguments:
template: The template to be searched.
start_index: The start index of the open quote.
Returns:
A dict containing the following:
start_index: The index of the first quote.
end_index: The index following the closing quote, or -1 if
unparseable
quote_length: The length of the quote. Could be 3 for a Python
triple quote.
"""
quote
=
template
[
start_index
]
if
quote
not
in
[
"'"
,
'"'
]:
raise
ValueError
(
"start_index must refer to a single or double quote."
)
triple_quote
=
quote
*
3
if
template
.
startswith
(
triple_quote
,
start_index
):
quote
=
triple_quote
result
=
{
'start_index'
:
start_index
,
'end_index'
:
-
1
,
'quote_length'
:
len
(
quote
),
}
start_index
+=
len
(
quote
)
while
True
:
quote_end_index
=
template
.
find
(
quote
,
start_index
)
backslash_index
=
template
.
find
(
"
\\
"
,
start_index
)
if
quote_end_index
<
0
:
return
result
if
0
<=
backslash_index
<
quote_end_index
:
start_index
=
backslash_index
+
2
else
:
result
[
'end_index'
]
=
quote_end_index
+
len
(
quote
)
return
result
class
UnderscoreTemplateLinter
(
object
):
class
UnderscoreTemplateLinter
(
object
):
...
@@ -1001,6 +1142,25 @@ class UnderscoreTemplateLinter(object):
...
@@ -1001,6 +1142,25 @@ class UnderscoreTemplateLinter(object):
return
expressions
return
expressions
def
_process_file
(
full_path
,
template_linters
,
options
,
out
):
"""
For each linter, lints the provided file. This means finding and printing
violations.
Arguments:
full_path: The full path of the file to lint.
template_linters: A list of linting objects.
options: A list of the options.
out: output file
"""
directory
=
os
.
path
.
dirname
(
full_path
)
file
=
os
.
path
.
basename
(
full_path
)
for
template_linter
in
template_linters
:
results
=
template_linter
.
process_file
(
directory
,
file
)
results
.
print_results
(
options
,
out
)
def
_process_current_walk
(
current_walk
,
template_linters
,
options
,
out
):
def
_process_current_walk
(
current_walk
,
template_linters
,
options
,
out
):
"""
"""
For each linter, lints all the files in the current os walk. This means
For each linter, lints all the files in the current os walk. This means
...
@@ -1016,10 +1176,8 @@ def _process_current_walk(current_walk, template_linters, options, out):
...
@@ -1016,10 +1176,8 @@ def _process_current_walk(current_walk, template_linters, options, out):
walk_directory
=
os
.
path
.
normpath
(
current_walk
[
0
])
walk_directory
=
os
.
path
.
normpath
(
current_walk
[
0
])
walk_files
=
current_walk
[
2
]
walk_files
=
current_walk
[
2
]
for
walk_file
in
walk_files
:
for
walk_file
in
walk_files
:
walk_file
=
os
.
path
.
normpath
(
walk_file
)
full_path
=
os
.
path
.
join
(
walk_directory
,
walk_file
)
for
template_linter
in
template_linters
:
_process_file
(
full_path
,
template_linters
,
options
,
out
)
results
=
template_linter
.
process_file
(
walk_directory
,
walk_file
)
results
.
print_results
(
options
,
out
)
def
_process_os_walk
(
starting_dir
,
template_linters
,
options
,
out
):
def
_process_os_walk
(
starting_dir
,
template_linters
,
options
,
out
):
...
@@ -1037,33 +1195,60 @@ def _process_os_walk(starting_dir, template_linters, options, out):
...
@@ -1037,33 +1195,60 @@ def _process_os_walk(starting_dir, template_linters, options, out):
_process_current_walk
(
current_walk
,
template_linters
,
options
,
out
)
_process_current_walk
(
current_walk
,
template_linters
,
options
,
out
)
def
_parse_arg
(
arg
,
option
):
"""
Parses an argument searching for --[option]=[OPTION_VALUE]
Arguments:
arg: The system argument
option: The specific option to be searched for (e.g. "file")
Returns:
The option value for a match, or None if arg is not for this option
"""
if
arg
.
startswith
(
'--{}='
.
format
(
option
)):
option_value
=
arg
.
split
(
'='
)[
1
]
if
option_value
.
startswith
(
"'"
)
or
option_value
.
startswith
(
'"'
):
option_value
=
option_value
[
1
:
-
1
]
return
option_value
else
:
return
None
def
main
():
def
main
():
"""
"""
Used to execute the linter. Use --help option for help.
Used to execute the linter. Use --help option for help.
Prints all
of the
violations.
Prints all violations.
"""
"""
epilog
=
'rules:
\n
'
for
rule
in
Rules
.
__members__
.
values
():
epilog
+=
" {0[0]}: {0[1]}
\n
"
.
format
(
rule
.
value
)
parser
=
argparse
.
ArgumentParser
(
formatter_class
=
argparse
.
RawDescriptionHelpFormatter
,
description
=
'Checks that templates are safe.'
,
epilog
=
epilog
)
parser
.
add_argument
(
'--quiet'
,
dest
=
'quiet'
,
action
=
'store_true'
,
help
=
'only display the filenames that contain violations'
)
parser
.
add_argument
(
'--file'
,
dest
=
'file'
,
nargs
=
1
,
default
=
None
,
help
=
'a single file to lint'
)
parser
.
add_argument
(
'--dir'
,
dest
=
'directory'
,
nargs
=
1
,
default
=
[
'.'
],
help
=
'the directory to lint (including sub-directories)'
)
#TODO: Use click
args
=
parser
.
parse_args
()
if
'--help'
in
sys
.
argv
:
print
(
"Check that templates are safe."
)
print
(
"Options:"
)
print
(
" --quiet Just display the filenames that have violations."
)
print
(
""
)
print
(
"Rules:"
)
for
rule
in
Rules
.
__members__
.
values
():
print
(
" {0[0]}: {0[1]}"
.
format
(
rule
.
value
))
return
is_quiet
=
'--quiet'
in
sys
.
argv
# TODO --file=...
options
=
{
options
=
{
'is_quiet'
:
is_
quiet
,
'is_quiet'
:
args
.
quiet
,
}
}
template_linters
=
[
MakoTemplateLinter
(),
UnderscoreTemplateLinter
()]
template_linters
=
[
MakoTemplateLinter
(),
UnderscoreTemplateLinter
()]
_process_os_walk
(
'.'
,
template_linters
,
options
,
out
=
sys
.
stdout
)
if
args
.
file
is
not
None
:
if
os
.
path
.
isfile
(
args
.
file
[
0
])
is
False
:
raise
ValueError
(
"File [{}] is not a valid file."
.
format
(
args
.
file
[
0
]))
_process_file
(
args
.
file
[
0
],
template_linters
,
options
,
out
=
sys
.
stdout
)
else
:
if
os
.
path
.
exists
(
args
.
directory
[
0
])
is
False
or
os
.
path
.
isfile
(
args
.
directory
[
0
])
is
True
:
raise
ValueError
(
"Directory [{}] is not a valid directory."
.
format
(
args
.
directory
[
0
]))
_process_os_walk
(
args
.
directory
[
0
],
template_linters
,
options
,
out
=
sys
.
stdout
)
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
...
...
scripts/tests/test_safe_template_linter.py
View file @
ff2e6dc1
...
@@ -109,7 +109,14 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -109,7 +109,14 @@ class TestMakoTemplateLinter(TestCase):
if
data
[
'violations'
]
>
0
:
if
data
[
'violations'
]
>
0
:
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
data
[
'rule'
])
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
data
[
'rule'
])
def
test_check_mako_expressions_in_html
(
self
):
@data
(
{
'expression'
:
'${x}'
,
'rule'
:
None
},
{
'expression'
:
'${{unbalanced}'
,
'rule'
:
Rules
.
mako_unparseable_expression
},
{
'expression'
:
'${x | n}'
,
'rule'
:
Rules
.
mako_invalid_html_filter
},
{
'expression'
:
'${x | h}'
,
'rule'
:
Rules
.
mako_unwanted_html_filter
},
{
'expression'
:
'${x | n, dump_js_escaped_json}'
,
'rule'
:
Rules
.
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
Test _check_mako_file_is_safe in html context provides appropriate violations
"""
"""
...
@@ -118,25 +125,12 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -118,25 +125,12 @@ class TestMakoTemplateLinter(TestCase):
mako_template
=
textwrap
.
dedent
(
"""
mako_template
=
textwrap
.
dedent
(
"""
<
%
page expression_filter="h"/>
<
%
page expression_filter="h"/>
${x}
{expression}
${'{{unbalanced-nested'}
"""
.
format
(
expression
=
data
[
'expression'
]))
${x | n}
${x | h}
${x | n, dump_js_escaped_json}
"""
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
self
.
assertEqual
(
len
(
results
.
violations
),
4
)
self
.
_validate_data_rule
(
data
,
results
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_unparsable_expression
)
start_index
=
results
.
violations
[
0
]
.
expression
[
'start_index'
]
self
.
assertEqual
(
mako_template
[
start_index
:
start_index
+
24
],
"${'{{unbalanced-nested'}"
)
self
.
assertEqual
(
results
.
violations
[
1
]
.
rule
,
Rules
.
mako_invalid_html_filter
)
self
.
assertEqual
(
results
.
violations
[
1
]
.
expression
[
'expression'
],
"${x | n}"
)
self
.
assertEqual
(
results
.
violations
[
2
]
.
rule
,
Rules
.
mako_unwanted_html_filter
)
self
.
assertEqual
(
results
.
violations
[
2
]
.
expression
[
'expression'
],
"${x | h}"
)
self
.
assertEqual
(
results
.
violations
[
3
]
.
rule
,
Rules
.
mako_invalid_html_filter
)
self
.
assertEqual
(
results
.
violations
[
3
]
.
expression
[
'expression'
],
"${x | n, dump_js_escaped_json}"
)
def
test_check_mako_expression_display_name
(
self
):
def
test_check_mako_expression_display_name
(
self
):
"""
"""
...
@@ -156,6 +150,87 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -156,6 +150,87 @@ class TestMakoTemplateLinter(TestCase):
self
.
assertEqual
(
len
(
results
.
violations
),
1
)
self
.
assertEqual
(
len
(
results
.
violations
),
1
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_deprecated_display_name
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_deprecated_display_name
)
@data
(
{
'expression'
:
textwrap
.
dedent
(
"""
${"Mixed {span_start}text{span_end}".format(
span_start=HTML("<span>"),
span_end=HTML("</span>"),
)}
"""
),
'rule'
:
Rules
.
mako_html_requires_text
},
{
'expression'
:
textwrap
.
dedent
(
"""
${Text("Mixed {span_start}text{span_end}").format(
span_start=HTML("<span>"),
span_end=HTML("</span>"),
)}
"""
),
'rule'
:
None
},
{
'expression'
:
textwrap
.
dedent
(
"""
${"Mixed {span_start}{text}{span_end}".format(
span_start=HTML("<span>"),
text=Text("This should still break."),
span_end=HTML("</span>"),
)}
"""
),
'rule'
:
Rules
.
mako_html_requires_text
},
{
'expression'
:
textwrap
.
dedent
(
"""
${Text("Mixed {link_start}text{link_end}".format(
link_start=HTML("<a href='{}'>").format(url),
link_end=HTML("</a>"),
))}
"""
),
'rule'
:
Rules
.
mako_close_before_format
},
{
'expression'
:
textwrap
.
dedent
(
"""
${Text("Mixed {link_start}text{link_end}").format(
link_start=HTML("<a href='{}'>".format(url)),
link_end=HTML("</a>"),
)}
"""
),
'rule'
:
Rules
.
mako_close_before_format
},
{
'expression'
:
"""${ Text("text") }"""
,
'rule'
:
Rules
.
mako_text_redundant
},
{
'expression'
:
"""${ HTML("<span></span>") }"""
,
'rule'
:
None
},
{
'expression'
:
"""${ HTML("<span></span>") + "some other text" }"""
,
'rule'
:
Rules
.
mako_html_alone
},
)
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
=
MakoTemplateLinter
()
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_rule
(
data
,
results
)
def
test_check_mako_expression_default_disabled
(
self
):
def
test_check_mako_expression_default_disabled
(
self
):
"""
"""
Test _check_mako_file_is_safe with disable pragma for safe-by-default
Test _check_mako_file_is_safe with disable pragma for safe-by-default
...
@@ -228,7 +303,14 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -228,7 +303,14 @@ class TestMakoTemplateLinter(TestCase):
self
.
assertEqual
(
len
(
results
.
violations
),
1
)
self
.
assertEqual
(
len
(
results
.
violations
),
1
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_missing_default
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_missing_default
)
def
test_check_mako_expressions_in_javascript
(
self
):
@data
(
{
'expression'
:
'${x}'
,
'rule'
:
Rules
.
mako_invalid_js_filter
},
{
'expression'
:
'${{unbalanced}'
,
'rule'
:
Rules
.
mako_unparseable_expression
},
{
'expression'
:
'${x | n}'
,
'rule'
:
Rules
.
mako_invalid_js_filter
},
{
'expression'
:
'${x | h}'
,
'rule'
:
Rules
.
mako_invalid_js_filter
},
{
'expression'
:
'${x | n, dump_js_escaped_json}'
,
'rule'
:
None
},
)
def
test_check_mako_expressions_in_javascript
(
self
,
data
):
"""
"""
Test _check_mako_file_is_safe in JavaScript script context provides
Test _check_mako_file_is_safe in JavaScript script context provides
appropriate violations
appropriate violations
...
@@ -239,29 +321,19 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -239,29 +321,19 @@ class TestMakoTemplateLinter(TestCase):
mako_template
=
textwrap
.
dedent
(
"""
mako_template
=
textwrap
.
dedent
(
"""
<
%
page expression_filter="h"/>
<
%
page expression_filter="h"/>
<script>
<script>
${x}
{expression}
${'{{unbalanced-nested'}
${x | n}
${x | h}
${x | n, dump_js_escaped_json}
"${x-with-quotes | n, js_escaped_string}"
</script>
</script>
"""
)
"""
.
format
(
expression
=
data
[
'expression'
])
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
self
.
assertEqual
(
len
(
results
.
violations
),
4
)
self
.
_validate_data_rule
(
data
,
results
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_invalid_js_filter
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
expression
[
'expression'
],
"${x}"
)
self
.
assertEqual
(
results
.
violations
[
1
]
.
rule
,
Rules
.
mako_unparsable_expression
)
start_index
=
results
.
violations
[
1
]
.
expression
[
'start_index'
]
self
.
assertEqual
(
mako_template
[
start_index
:
start_index
+
24
],
"${'{{unbalanced-nested'}"
)
self
.
assertEqual
(
results
.
violations
[
2
]
.
rule
,
Rules
.
mako_invalid_js_filter
)
self
.
assertEqual
(
results
.
violations
[
2
]
.
expression
[
'expression'
],
"${x | n}"
)
self
.
assertEqual
(
results
.
violations
[
3
]
.
rule
,
Rules
.
mako_invalid_js_filter
)
self
.
assertEqual
(
results
.
violations
[
3
]
.
expression
[
'expression'
],
"${x | h}"
)
def
test_check_mako_expressions_in_require_js
(
self
):
@data
(
{
'expression'
:
'${x}'
,
'rule'
:
Rules
.
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 context provides
Test _check_mako_file_is_safe in JavaScript require context provides
appropriate violations
appropriate violations
...
@@ -271,17 +343,14 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -271,17 +343,14 @@ class TestMakoTemplateLinter(TestCase):
mako_template
=
textwrap
.
dedent
(
"""
mako_template
=
textwrap
.
dedent
(
"""
<
%
page expression_filter="h"/>
<
%
page expression_filter="h"/>
<
%
static:require_module module_name="${x}" class_name="TestFactory">
<
%
static:require_module module_name="${{x}}" class_name="TestFactory">
${x}
{expression}
${x | n, js_escaped_string}
</
%
static:require_module>
</
%
static:require_module>
"""
)
"""
.
format
(
expression
=
data
[
'expression'
])
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
self
.
assertEqual
(
len
(
results
.
violations
),
1
)
self
.
_validate_data_rule
(
data
,
results
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_invalid_js_filter
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
expression
[
'expression'
],
"${x}"
)
@data
(
@data
(
{
'media_type'
:
'text/javascript'
,
'expected_violations'
:
0
},
{
'media_type'
:
'text/javascript'
,
'expected_violations'
:
0
},
...
@@ -339,7 +408,20 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -339,7 +408,20 @@ class TestMakoTemplateLinter(TestCase):
self
.
assertEqual
(
results
.
violations
[
3
]
.
rule
,
Rules
.
mako_invalid_js_filter
)
self
.
assertEqual
(
results
.
violations
[
3
]
.
rule
,
Rules
.
mako_invalid_js_filter
)
self
.
assertEqual
(
results
.
violations
[
4
]
.
rule
,
Rules
.
mako_unwanted_html_filter
)
self
.
assertEqual
(
results
.
violations
[
4
]
.
rule
,
Rules
.
mako_unwanted_html_filter
)
def
test_expression_detailed_results
(
self
):
@data
(
{
'template'
:
"
\n
${x | n}"
,
'parseable'
:
True
},
{
'template'
:
textwrap
.
dedent
(
"""
<div>${(
'tabbed-multi-line-expression'
) | n}</div>
"""
),
'parseable'
:
True
},
{
'template'
:
"${{unparseable}"
,
'parseable'
:
False
},
)
def
test_expression_detailed_results
(
self
,
data
):
"""
"""
Test _check_mako_file_is_safe provides detailed results, including line
Test _check_mako_file_is_safe provides detailed results, including line
numbers, columns, and line
numbers, columns, and line
...
@@ -347,86 +429,115 @@ class TestMakoTemplateLinter(TestCase):
...
@@ -347,86 +429,115 @@ class TestMakoTemplateLinter(TestCase):
linter
=
MakoTemplateLinter
()
linter
=
MakoTemplateLinter
()
results
=
FileResults
(
''
)
results
=
FileResults
(
''
)
mako_template
=
textwrap
.
dedent
(
"""
linter
.
_check_mako_file_is_safe
(
data
[
'template'
],
results
)
${x | n}
<div>${(
'tabbed-multi-line-expression'
) | n}</div>
${'{{unbalanced-nested' | n}
"""
)
linter
.
_check_mako_file_is_safe
(
mako_template
,
results
)
self
.
assertEqual
(
len
(
results
.
violations
),
4
)
self
.
assertEqual
(
len
(
results
.
violations
),
2
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_missing_default
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
Rules
.
mako_missing_default
)
self
.
assertEqual
(
results
.
violations
[
1
]
.
start_line
,
2
)
violation
=
results
.
violations
[
1
]
self
.
assertEqual
(
results
.
violations
[
1
]
.
start_column
,
1
)
lines
=
list
(
data
[
'template'
]
.
splitlines
())
self
.
assertEqual
(
results
.
violations
[
1
]
.
end_line
,
2
)
self
.
assertTrue
(
"${"
in
lines
[
violation
.
start_line
-
1
])
self
.
assertEqual
(
results
.
violations
[
1
]
.
end_column
,
8
)
self
.
assertTrue
(
lines
[
violation
.
start_line
-
1
]
.
startswith
(
"${"
,
violation
.
start_column
-
1
))
self
.
assertEqual
(
len
(
results
.
violations
[
1
]
.
lines
),
1
)
if
data
[
'parseable'
]:
self
.
assertEqual
(
results
.
violations
[
1
]
.
lines
[
0
],
"${x | n}"
)
self
.
assertTrue
(
"}"
in
lines
[
violation
.
end_line
-
1
])
self
.
assertTrue
(
lines
[
violation
.
end_line
-
1
]
.
startswith
(
"}"
,
violation
.
end_column
-
1
))
self
.
assertEqual
(
results
.
violations
[
2
]
.
start_line
,
3
)
else
:
self
.
assertEqual
(
results
.
violations
[
2
]
.
start_column
,
10
)
self
.
assertEqual
(
violation
.
start_line
,
violation
.
end_line
)
self
.
assertEqual
(
results
.
violations
[
2
]
.
end_line
,
5
)
self
.
assertEqual
(
violation
.
end_column
,
"?"
)
self
.
assertEqual
(
results
.
violations
[
2
]
.
end_column
,
10
)
self
.
assertEqual
(
len
(
violation
.
lines
),
violation
.
end_line
-
violation
.
start_line
+
1
)
self
.
assertEqual
(
len
(
results
.
violations
[
2
]
.
lines
),
3
)
for
line_index
in
range
(
0
,
len
(
violation
.
lines
)):
self
.
assertEqual
(
results
.
violations
[
2
]
.
lines
[
0
],
" <div>${("
)
self
.
assertEqual
(
violation
.
lines
[
line_index
],
lines
[
line_index
+
violation
.
start_line
-
1
])
self
.
assertEqual
(
results
.
violations
[
2
]
.
lines
[
1
],
@data
(
" 'tabbed-multi-line-expression'"
{
'template'
:
"${x}"
},
)
{
'template'
:
"
\n
${x}"
},
self
.
assertEqual
(
results
.
violations
[
2
]
.
lines
[
2
],
" ) | n}</div>"
)
{
'template'
:
"${x} "
},
{
'template'
:
"${{test-balanced-delims}} "
},
self
.
assertEqual
(
results
.
violations
[
3
]
.
start_line
,
6
)
{
'template'
:
"${'{unbalanced in string'}"
},
self
.
assertEqual
(
results
.
violations
[
3
]
.
start_column
,
1
)
{
'template'
:
"${'unbalanced in string}'}"
},
self
.
assertEqual
(
results
.
violations
[
3
]
.
end_line
,
6
)
{
'template'
:
"${(
\n
'tabbed-multi-line-expression'
\n
)}"
},
self
.
assertEqual
(
results
.
violations
[
3
]
.
end_column
,
"?"
)
)
self
.
assertEqual
(
len
(
results
.
violations
[
3
]
.
lines
),
1
)
def
test_find_mako_expressions
(
self
,
data
):
self
.
assertEqual
(
"""
results
.
violations
[
3
]
.
lines
[
0
],
Test _find_mako_expressions for parseable expressions
"${'{{unbalanced-nested' | n}"
)
def
test_find_mako_expressions
(
self
):
"""
Test _find_mako_expressions finds appropriate expressions
"""
"""
linter
=
MakoTemplateLinter
()
linter
=
MakoTemplateLinter
()
mako_template
=
textwrap
.
dedent
(
"""
expressions
=
linter
.
_find_mako_expressions
(
data
[
'template'
])
${x}
${tabbed-x}
${(
'tabbed-multi-line-expression'
)}
${'{{unbalanced-nested'}
${'{{nested}}'}
<div>no expression</div>
"""
)
expressions
=
linter
.
_find_mako_expressions
(
mako_template
)
self
.
assertEqual
(
len
(
expressions
),
1
)
start_index
=
expressions
[
0
][
'start_index'
]
end_index
=
expressions
[
0
][
'end_index'
]
self
.
assertEqual
(
data
[
'template'
][
start_index
:
end_index
+
1
],
data
[
'template'
]
.
strip
())
self
.
assertEqual
(
expressions
[
0
][
'expression'
],
data
[
'template'
]
.
strip
())
self
.
assertEqual
(
len
(
expressions
),
5
)
@data
(
self
.
_validate_expression
(
mako_template
,
expressions
[
0
],
'${x}'
)
{
'template'
:
" ${{unparseable} ${}"
,
'start_index'
:
1
},
self
.
_validate_expression
(
mako_template
,
expressions
[
1
],
'${tabbed-x}'
)
{
'template'
:
" ${'unparseable} ${}"
,
'start_index'
:
1
},
self
.
_validate_expression
(
mako_template
,
expressions
[
2
],
"${(
\n
'tabbed-multi-line-expression'
\n
)}"
)
)
def
test_find_mako_expressions
(
self
,
data
):
"""
Test _find_mako_expressions for unparseable expressions
"""
linter
=
MakoTemplateLinter
()
# won't parse unbalanced nested {}'s
expressions
=
linter
.
_find_mako_expressions
(
data
[
'template'
])
unbalanced_expression
=
"${'{{unbalanced-nested'}"
self
.
assertTrue
(
2
<=
len
(
expressions
))
self
.
assertEqual
(
expressions
[
3
][
'end_index'
],
-
1
)
self
.
assertEqual
(
expressions
[
0
][
'start_index'
],
data
[
'start_index'
])
start_index
=
expressions
[
3
][
'start_index'
]
self
.
assertIsNone
(
expressions
[
0
][
'expression'
])
self
.
assertEqual
(
mako_template
[
start_index
:
start_index
+
len
(
unbalanced_expression
)],
unbalanced_expression
)
self
.
assertEqual
(
expressions
[
3
][
'expression'
],
None
)
self
.
_validate_expression
(
mako_template
,
expressions
[
4
],
"${'{{nested}}'}"
)
@data
(
{
'template'
:
"""${""}"""
,
'start_index'
:
0
,
'end_index'
:
5
,
'expected_index'
:
2
},
{
'template'
:
"""${''}"""
,
'start_index'
:
0
,
'end_index'
:
5
,
'expected_index'
:
2
},
{
'template'
:
"""${"''"}"""
,
'start_index'
:
0
,
'end_index'
:
7
,
'expected_index'
:
2
},
{
'template'
:
"""${'""'}"""
,
'start_index'
:
0
,
'end_index'
:
7
,
'expected_index'
:
2
},
{
'template'
:
"""${'""'}"""
,
'start_index'
:
3
,
'end_index'
:
7
,
'expected_index'
:
3
},
{
'template'
:
"""${'""'}"""
,
'start_index'
:
0
,
'end_index'
:
1
,
'expected_index'
:
-
1
},
)
def
test_find_string_start
(
self
,
data
):
"""
Test _find_string_start helper
"""
linter
=
MakoTemplateLinter
()
string_start_index
=
linter
.
_find_string_start
(
data
[
'template'
],
data
[
'start_index'
],
data
[
'end_index'
])
self
.
assertEqual
(
string_start_index
,
data
[
'expected_index'
])
@data
(
{
'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
}
},
)
def
test_parse_string
(
self
,
data
):
"""
Test _parse_string helper
"""
linter
=
MakoTemplateLinter
()
result
=
linter
.
_parse_string
(
data
[
'template'
],
data
[
'result'
][
'start_index'
])
def
_validate_expression
(
self
,
template_string
,
expression
,
expected_expression
):
self
.
assertDictEqual
(
result
,
data
[
'result'
])
start_index
=
expression
[
'start_index'
]
end_index
=
expression
[
'end_index'
]
def
_validate_data_rule
(
self
,
data
,
results
):
self
.
assertEqual
(
template_string
[
start_index
:
end_index
+
1
],
expected_expression
)
if
data
[
'rule'
]
is
None
:
self
.
assertEqual
(
expression
[
'expression'
],
expected_expression
)
self
.
assertEqual
(
len
(
results
.
violations
),
0
)
else
:
self
.
assertEqual
(
len
(
results
.
violations
),
1
)
self
.
assertEqual
(
results
.
violations
[
0
]
.
rule
,
data
[
'rule'
])
@ddt
@ddt
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment