def test_string_lines_start_end_index(self, data):
     """
     Test line_number_to_line.
     """
     lines = StringLines(data['string'])
     self.assertEqual(lines.line_number_to_line(data['line_number']),
                      data['line'])
Example #2
0
 def test_string_lines_start_end_index(self, data):
     """
     Test StringLines index_to_line_start_index and index_to_line_end_index.
     """
     lines = StringLines(data['string'])
     self.assertEqual(lines.index_to_line_start_index(data['index']), data['line_start_index'])
     self.assertEqual(lines.index_to_line_end_index(data['index']), data['line_end_index'])
Example #3
0
 def test_string_lines_start_end_index(self, data):
     """
     Test StringLines index_to_line_start_index and index_to_line_end_index.
     """
     lines = StringLines(data['string'])
     self.assertEqual(lines.index_to_line_start_index(data['index']), data['line_start_index'])
     self.assertEqual(lines.index_to_line_end_index(data['index']), data['line_end_index'])
    def __init__(self, file_contents, results):
        """
        Init method.

        Arguments:
            file_contents: The contents of the Python file.
            results: A file results objects to which violations will be added.

        """
        super(BaseVisitor, self).__init__()
        self.file_contents = file_contents
        self.lines = StringLines(self.file_contents)
        self.results = results
Example #5
0
class BaseVisitor(ast.NodeVisitor):
    """
    Base class for AST NodeVisitor used for Python xss linting.

    Important: This base visitor skips all __repr__ function definitions.
    """
    def __init__(self, file_contents, results):
        """
        Init method.

        Arguments:
            file_contents: The contents of the Python file.
            results: A file results objects to which violations will be added.

        """
        super(BaseVisitor, self).__init__()
        self.file_contents = file_contents
        self.lines = StringLines(self.file_contents)
        self.results = results

    def node_to_expression(self, node):
        """
        Takes a node and translates it to an expression to be used with
        violations.

        Arguments:
            node: An AST node.

        """
        line_start_index = self.lines.line_number_to_start_index(node.lineno)
        start_index = line_start_index + node.col_offset
        if isinstance(node, ast.Str):
            # Triple quotes give col_offset of -1 on the last line of the string.
            if node.col_offset == -1:
                triple_quote_regex = re.compile("""['"]{3}""")
                end_triple_quote_match = triple_quote_regex.search(self.file_contents, line_start_index)
                open_quote_index = self.file_contents.rfind(end_triple_quote_match.group(), 0, end_triple_quote_match.start())
                if open_quote_index > 0:
                    start_index = open_quote_index
                else:
                    # If we can't find a starting quote, let's assume that what
                    # we considered the end quote is really the start quote.
                    start_index = end_triple_quote_match.start()
            string = ParseString(self.file_contents, start_index, len(self.file_contents))
            return Expression(string.start_index, string.end_index)
        else:
            return Expression(start_index)

    def visit_FunctionDef(self, node):
        """
        Skips processing of __repr__ functions, since these sometimes use '<'
        for non-HTML purposes.

        Arguments:
            node: An AST node.
        """
        if node.name != '__repr__':
            self.generic_visit(node)
Example #6
0
    def __init__(self, file_contents, results):
        """
        Init method.

        Arguments:
            file_contents: The contents of the Python file.
            results: A file results objects to which violations will be added.

        """
        super(BaseVisitor, self).__init__()
        self.file_contents = file_contents
        self.lines = StringLines(self.file_contents)
        self.results = results
    def prepare_results(self, file_string, line_comment_delim=None):
        """
        Prepares the results for output for this file.

        Arguments:
            file_string: The string of content for this file.
            line_comment_delim: A string representing the start of a line
                comment. For example "##" for Mako and "//" for JavaScript.

        """
        string_lines = StringLines(file_string)
        for violation in self.violations:
            violation.prepare_results(self.full_path, string_lines)
        if line_comment_delim is not None:
            self._filter_commented_code(line_comment_delim)
Example #8
0
    def __init__(self, ruleset, results, *args, **kwargs):
        super(HtmlInterpolateExpression, self).__init__(*args, **kwargs)
        self.string_lines = StringLines(kwargs['template'])
        self.ruleset = ruleset
        self.results = results
        self.validated = False
        self.interpolated_string_var = None

        trans_expr = self.expression_inner
        # extracting interpolated variable string name
        expr_list = trans_expr.split(' ')
        if len(expr_list) < 2:
            _add_violations(self.results,
                            self.ruleset.django_html_interpolation_invalid_tag,
                            self)
            return
        self.interpolated_string_var = expr_list[1]
class BaseVisitor(ast.NodeVisitor):
    """
    Base class for AST NodeVisitor used for Python xss linting.

    Important: This base visitor skips all __repr__ function definitions.
    """
    def __init__(self, file_contents, results):
        """
        Init method.

        Arguments:
            file_contents: The contents of the Python file.
            results: A file results objects to which violations will be added.

        """
        super(BaseVisitor, self).__init__()
        self.file_contents = file_contents
        self.lines = StringLines(self.file_contents)
        self.results = results

    def node_to_expression(self, node):
        """
        Takes a node and translates it to an expression to be used with
        violations.

        Arguments:
            node: An AST node.

        """
        line_start_index = self.lines.line_number_to_start_index(node.lineno)
        start_index = line_start_index + node.col_offset
        if isinstance(node, ast.Str):
            # Triple quotes give col_offset of -1 on the last line of the string.
            if node.col_offset == -1:
                triple_quote_regex = re.compile("""['"]{3}""")
                end_triple_quote_match = triple_quote_regex.search(
                    self.file_contents, line_start_index)
                open_quote_index = self.file_contents.rfind(
                    end_triple_quote_match.group(), 0,
                    end_triple_quote_match.start())
                if open_quote_index > 0:
                    start_index = open_quote_index
                else:
                    # If we can't find a starting quote, let's assume that what
                    # we considered the end quote is really the start quote.
                    start_index = end_triple_quote_match.start()
            string = ParseString(self.file_contents, start_index,
                                 len(self.file_contents))
            return Expression(string.start_index, string.end_index)
        else:
            return Expression(start_index)

    def visit_FunctionDef(self, node):
        """
        Skips processing of __repr__ functions, since these sometimes use '<'
        for non-HTML purposes.

        Arguments:
            node: An AST node.
        """
        if node.name != '__repr__':
            self.generic_visit(node)
Example #10
0
 def test_string_lines_line_numbers(self, data):
     """
     Test line_number_to_line.
     """
     lines = StringLines(data['string'])
     assert lines.line_number_to_line(data['line_number']) == data['line']
Example #11
0
class TransExpression(Expression):
    """
        The expression handling trans tag
    """
    def __init__(self, ruleset, results, *args, **kwargs):
        super(TransExpression, self).__init__(*args, **kwargs)
        self.string_lines = StringLines(kwargs['template'])
        self.ruleset = ruleset
        self.results = results

    def validate_expression(self, template_file, expressions=None):
        """
        Validates trans tag expression for missing escaping filter

        Arguments:
            template_file: The content of the Django template.
            results: Violations to be generated.

        Returns:
            None
        """
        trans_expr = self.expression_inner

        # extracting translation string message
        trans_var_name_used, trans_expr_msg = self.process_translation_string(
            trans_expr)
        if not trans_var_name_used or not trans_expr_msg:
            return

        # Checking if trans tag has interpolated variables eg {} in translations string.
        # and testing for possible interpolate_html tag used for it.
        if self.check_string_interpolation(trans_expr_msg, trans_var_name_used,
                                           expressions, template_file):
            return

        escape_expr_start_pos, escape_expr_end_pos = self.find_filter_tag(
            template_file)
        if not escape_expr_start_pos or not escape_expr_end_pos:
            return

        self.process_escape_filter_tag(
            template_file=template_file,
            escape_expr_start_pos=escape_expr_start_pos,
            escape_expr_end_pos=escape_expr_end_pos,
            trans_var_name_used=trans_var_name_used)

    def process_translation_string(self, trans_expr):
        """
        Process translation string into string and variable name used

        Arguments:
            trans_expr: Translation expression inside {% %}
        Returns:
            None
        """

        quote = re.search(r"""\s*['"].*['"]\s*""", trans_expr, re.I)
        if not quote:
            _add_violations(
                self.results,
                self.ruleset.django_trans_escape_filter_parse_error, self)
            return None, None

        trans_expr_msg = trans_expr[quote.start():quote.end()].strip()
        if _check_is_string_has_html(trans_expr_msg):
            _add_violations(self.results,
                            self.ruleset.django_html_interpolation_missing,
                            self)
            return None, None

        pos = trans_expr.find('as', quote.end())
        if pos == -1:
            _add_violations(self.results,
                            self.ruleset.django_trans_missing_escape, self)
            return None, None

        trans_var_name_used = trans_expr[pos + len('as'):].strip()
        return trans_var_name_used, trans_expr_msg

    def check_string_interpolation(self, trans_expr_msg, trans_var_name_used,
                                   expressions, template_file):
        """
        Checks if the translation string has used interpolation variable eg {variable} but not
        used interpolate_html tag to escape them

        Arguments:
            trans_expr_msg: Translation string in quotes
            trans_var_name_used: Translation variable used
            expressions: List of expressions found during django file processing
            template_file: django template file
        Returns:
            True: In case it finds interpolated variables
            False: No interpolation variables found
        """

        if _check_is_string_has_variables(trans_expr_msg):
            interpolate_tag, html_interpolated = _is_html_interpolated(
                trans_var_name_used, expressions)

            if not html_interpolated:
                _add_violations(self.results,
                                self.ruleset.django_html_interpolation_missing,
                                self)
            if interpolate_tag:
                interpolate_tag.validate_expression(template_file, expressions)
            return True
        return

    def find_filter_tag(self, template_file):
        """
        Finds if there is force_filter tag applied

        Arguments:
            template_file: django template file
        Returns:
            (None, None): In case there is a violations
            (start, end): Found filter tag start and end position
        """

        trans_expr_lineno = self.string_lines.index_to_line_number(
            self.start_index)
        escape_expr_start_pos = template_file.find('{{', self.end_index)
        if escape_expr_start_pos == -1:
            _add_violations(self.results,
                            self.ruleset.django_trans_missing_escape, self)
            return None, None

        # {{ found but should be on the same line as trans tag
        trans_expr_filter_lineno = self.string_lines.index_to_line_number(
            escape_expr_start_pos)
        if trans_expr_filter_lineno != trans_expr_lineno:
            _add_violations(self.results,
                            self.ruleset.django_trans_missing_escape, self)
            return None, None

        escape_expr_end_pos = template_file.find('}}', escape_expr_start_pos)
        # couldn't find matching }}
        if escape_expr_end_pos == -1:
            _add_violations(self.results,
                            self.ruleset.django_trans_missing_escape, self)
            return None, None

        # }} should be also on the same line
        trans_expr_filter_lineno = self.string_lines.index_to_line_number(
            escape_expr_end_pos)
        if trans_expr_filter_lineno != trans_expr_lineno:
            _add_violations(self.results,
                            self.ruleset.django_trans_missing_escape, self)
            return None, None

        return escape_expr_start_pos, escape_expr_end_pos

    def process_escape_filter_tag(self, **kwargs):
        """
        Checks if the escape filter and process it for violations

        Arguments:
            kwargs:  Having force_filter expression start, end, trans expression variable
            used and templates
        Returns:
            None: If found any violations
        """

        template_file = kwargs['template_file']
        escape_expr_start_pos = kwargs['escape_expr_start_pos']
        escape_expr_end_pos = kwargs['escape_expr_end_pos']
        trans_var_name_used = kwargs['trans_var_name_used']

        escape_expr = template_file[escape_expr_start_pos +
                                    len('{{'):escape_expr_end_pos].strip(' ')

        # check escape expression has the right variable and its escaped properly
        # with force_escape filter
        if '|' not in escape_expr or len(escape_expr.split('|')) != 2:
            _add_violations(self.results,
                            self.ruleset.django_trans_invalid_escape_filter,
                            self)
            return

        escape_expr_var_used, escape_filter = \
            escape_expr.split('|')[0].strip(' '), escape_expr.split('|')[1].strip(' ')
        if trans_var_name_used != escape_expr_var_used:
            _add_violations(self.results,
                            self.ruleset.django_trans_escape_variable_mismatch,
                            self)
            return

        if escape_filter != 'force_escape':
            _add_violations(self.results,
                            self.ruleset.django_trans_invalid_escape_filter,
                            self)
            return
Example #12
0
 def __init__(self, ruleset, results, *args, **kwargs):
     super(BlockTransExpression, self).__init__(*args, **kwargs)
     self.string_lines = StringLines(kwargs['template'])
     self.ruleset = ruleset
     self.results = results
Example #13
0
 def test_string_lines_start_end_index(self, data):
     """
     Test line_number_to_line.
     """
     lines = StringLines(data['string'])
     self.assertEqual(lines.line_number_to_line(data['line_number']), data['line'])