317 lines
10 KiB
Python
317 lines
10 KiB
Python
#
|
|
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
import ast
|
|
|
|
from bandit.core import utils
|
|
|
|
|
|
class Context:
|
|
def __init__(self, context_object=None):
|
|
"""Initialize the class with a context, empty dict otherwise
|
|
|
|
:param context_object: The context object to create class from
|
|
:return: -
|
|
"""
|
|
if context_object is not None:
|
|
self._context = context_object
|
|
else:
|
|
self._context = dict()
|
|
|
|
def __repr__(self):
|
|
"""Generate representation of object for printing / interactive use
|
|
|
|
Most likely only interested in non-default properties, so we return
|
|
the string version of _context.
|
|
|
|
Example string returned:
|
|
<Context {'node': <_ast.Call object at 0x110252510>, 'function': None,
|
|
'name': 'socket', 'imports': set(['socket']), 'module': None,
|
|
'filename': 'examples/binding.py',
|
|
'call': <_ast.Call object at 0x110252510>, 'lineno': 3,
|
|
'import_aliases': {}, 'qualname': 'socket.socket'}>
|
|
|
|
:return: A string representation of the object
|
|
"""
|
|
return f"<Context {self._context}>"
|
|
|
|
@property
|
|
def call_args(self):
|
|
"""Get a list of function args
|
|
|
|
:return: A list of function args
|
|
"""
|
|
args = []
|
|
if "call" in self._context and hasattr(self._context["call"], "args"):
|
|
for arg in self._context["call"].args:
|
|
if hasattr(arg, "attr"):
|
|
args.append(arg.attr)
|
|
else:
|
|
args.append(self._get_literal_value(arg))
|
|
return args
|
|
|
|
@property
|
|
def call_args_count(self):
|
|
"""Get the number of args a function call has
|
|
|
|
:return: The number of args a function call has or None
|
|
"""
|
|
if "call" in self._context and hasattr(self._context["call"], "args"):
|
|
return len(self._context["call"].args)
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def call_function_name(self):
|
|
"""Get the name (not FQ) of a function call
|
|
|
|
:return: The name (not FQ) of a function call
|
|
"""
|
|
return self._context.get("name")
|
|
|
|
@property
|
|
def call_function_name_qual(self):
|
|
"""Get the FQ name of a function call
|
|
|
|
:return: The FQ name of a function call
|
|
"""
|
|
return self._context.get("qualname")
|
|
|
|
@property
|
|
def call_keywords(self):
|
|
"""Get a dictionary of keyword parameters
|
|
|
|
:return: A dictionary of keyword parameters for a call as strings
|
|
"""
|
|
if "call" in self._context and hasattr(
|
|
self._context["call"], "keywords"
|
|
):
|
|
return_dict = {}
|
|
for li in self._context["call"].keywords:
|
|
if hasattr(li.value, "attr"):
|
|
return_dict[li.arg] = li.value.attr
|
|
else:
|
|
return_dict[li.arg] = self._get_literal_value(li.value)
|
|
return return_dict
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def node(self):
|
|
"""Get the raw AST node associated with the context
|
|
|
|
:return: The raw AST node associated with the context
|
|
"""
|
|
return self._context.get("node")
|
|
|
|
@property
|
|
def string_val(self):
|
|
"""Get the value of a standalone unicode or string object
|
|
|
|
:return: value of a standalone unicode or string object
|
|
"""
|
|
return self._context.get("str")
|
|
|
|
@property
|
|
def bytes_val(self):
|
|
"""Get the value of a standalone bytes object (py3 only)
|
|
|
|
:return: value of a standalone bytes object
|
|
"""
|
|
return self._context.get("bytes")
|
|
|
|
@property
|
|
def string_val_as_escaped_bytes(self):
|
|
"""Get escaped value of the object.
|
|
|
|
Turn the value of a string or bytes object into byte sequence with
|
|
unknown, control, and \\ characters escaped.
|
|
|
|
This function should be used when looking for a known sequence in a
|
|
potentially badly encoded string in the code.
|
|
|
|
:return: sequence of printable ascii bytes representing original string
|
|
"""
|
|
val = self.string_val
|
|
if val is not None:
|
|
# it's any of str or unicode in py2, or str in py3
|
|
return val.encode("unicode_escape")
|
|
|
|
val = self.bytes_val
|
|
if val is not None:
|
|
return utils.escaped_bytes_representation(val)
|
|
|
|
return None
|
|
|
|
@property
|
|
def statement(self):
|
|
"""Get the raw AST for the current statement
|
|
|
|
:return: The raw AST for the current statement
|
|
"""
|
|
return self._context.get("statement")
|
|
|
|
@property
|
|
def function_def_defaults_qual(self):
|
|
"""Get a list of fully qualified default values in a function def
|
|
|
|
:return: List of defaults
|
|
"""
|
|
defaults = []
|
|
if (
|
|
"node" in self._context
|
|
and hasattr(self._context["node"], "args")
|
|
and hasattr(self._context["node"].args, "defaults")
|
|
):
|
|
for default in self._context["node"].args.defaults:
|
|
defaults.append(
|
|
utils.get_qual_attr(
|
|
default, self._context["import_aliases"]
|
|
)
|
|
)
|
|
return defaults
|
|
|
|
def _get_literal_value(self, literal):
|
|
"""Utility function to turn AST literals into native Python types
|
|
|
|
:param literal: The AST literal to convert
|
|
:return: The value of the AST literal
|
|
"""
|
|
if isinstance(literal, ast.Constant):
|
|
if isinstance(literal.value, bool):
|
|
literal_value = str(literal.value)
|
|
elif literal.value is None:
|
|
literal_value = str(literal.value)
|
|
else:
|
|
literal_value = literal.value
|
|
|
|
elif isinstance(literal, ast.List):
|
|
return_list = list()
|
|
for li in literal.elts:
|
|
return_list.append(self._get_literal_value(li))
|
|
literal_value = return_list
|
|
|
|
elif isinstance(literal, ast.Tuple):
|
|
return_tuple = tuple()
|
|
for ti in literal.elts:
|
|
return_tuple += (self._get_literal_value(ti),)
|
|
literal_value = return_tuple
|
|
|
|
elif isinstance(literal, ast.Set):
|
|
return_set = set()
|
|
for si in literal.elts:
|
|
return_set.add(self._get_literal_value(si))
|
|
literal_value = return_set
|
|
|
|
elif isinstance(literal, ast.Dict):
|
|
literal_value = dict(zip(literal.keys, literal.values))
|
|
|
|
elif isinstance(literal, ast.Name):
|
|
literal_value = literal.id
|
|
|
|
else:
|
|
literal_value = None
|
|
|
|
return literal_value
|
|
|
|
def get_call_arg_value(self, argument_name):
|
|
"""Gets the value of a named argument in a function call.
|
|
|
|
:return: named argument value
|
|
"""
|
|
kwd_values = self.call_keywords
|
|
if kwd_values is not None and argument_name in kwd_values:
|
|
return kwd_values[argument_name]
|
|
|
|
def check_call_arg_value(self, argument_name, argument_values=None):
|
|
"""Checks for a value of a named argument in a function call.
|
|
|
|
Returns none if the specified argument is not found.
|
|
:param argument_name: A string - name of the argument to look for
|
|
:param argument_values: the value, or list of values to test against
|
|
:return: Boolean True if argument found and matched, False if
|
|
found and not matched, None if argument not found at all
|
|
"""
|
|
arg_value = self.get_call_arg_value(argument_name)
|
|
if arg_value is not None:
|
|
if not isinstance(argument_values, list):
|
|
# if passed a single value, or a tuple, convert to a list
|
|
argument_values = list((argument_values,))
|
|
for val in argument_values:
|
|
if arg_value == val:
|
|
return True
|
|
return False
|
|
else:
|
|
# argument name not found, return None to allow testing for this
|
|
# eventuality
|
|
return None
|
|
|
|
def get_lineno_for_call_arg(self, argument_name):
|
|
"""Get the line number for a specific named argument
|
|
|
|
In case the call is split over multiple lines, get the correct one for
|
|
the argument.
|
|
:param argument_name: A string - name of the argument to look for
|
|
:return: Integer - the line number of the found argument, or -1
|
|
"""
|
|
if hasattr(self.node, "keywords"):
|
|
for key in self.node.keywords:
|
|
if key.arg == argument_name:
|
|
return key.value.lineno
|
|
|
|
def get_call_arg_at_position(self, position_num):
|
|
"""Returns positional argument at the specified position (if it exists)
|
|
|
|
:param position_num: The index of the argument to return the value for
|
|
:return: Value of the argument at the specified position if it exists
|
|
"""
|
|
max_args = self.call_args_count
|
|
if max_args and position_num < max_args:
|
|
arg = self._context["call"].args[position_num]
|
|
return getattr(arg, "attr", None) or self._get_literal_value(arg)
|
|
else:
|
|
return None
|
|
|
|
def is_module_being_imported(self, module):
|
|
"""Check for the specified module is currently being imported
|
|
|
|
:param module: The module name to look for
|
|
:return: True if the module is found, False otherwise
|
|
"""
|
|
return self._context.get("module") == module
|
|
|
|
def is_module_imported_exact(self, module):
|
|
"""Check if a specified module has been imported; only exact matches.
|
|
|
|
:param module: The module name to look for
|
|
:return: True if the module is found, False otherwise
|
|
"""
|
|
return module in self._context.get("imports", [])
|
|
|
|
def is_module_imported_like(self, module):
|
|
"""Check if a specified module has been imported
|
|
|
|
Check if a specified module has been imported; specified module exists
|
|
as part of any import statement.
|
|
:param module: The module name to look for
|
|
:return: True if the module is found, False otherwise
|
|
"""
|
|
if "imports" in self._context:
|
|
for imp in self._context["imports"]:
|
|
if module in imp:
|
|
return True
|
|
return False
|
|
|
|
@property
|
|
def filename(self):
|
|
return self._context.get("filename")
|
|
|
|
@property
|
|
def file_data(self):
|
|
return self._context.get("file_data")
|
|
|
|
@property
|
|
def import_aliases(self):
|
|
return self._context.get("import_aliases")
|