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'])
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
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)
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)
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)
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']
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
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