Source code for galaxy.tools.parameters.sanitize
"""
Tool Parameter specific sanitizing.
"""
import logging
import string
import galaxy.util
log = logging.getLogger(__name__)
[docs]class ToolParameterSanitizer:
"""
Handles tool parameter specific sanitizing.
>>> from galaxy.util import XML
>>> sanitizer = ToolParameterSanitizer.from_element(XML(
... '''
... <sanitizer invalid_char="">
... <valid initial="string.ascii_letters"/>
... </sanitizer>
... '''))
>>> sanitizer.sanitize_param(''.join(sorted(c for c in string.printable))) == ''.join(sorted(c for c in string.ascii_letters))
True
>>> slash = chr(92)
>>> sanitizer = ToolParameterSanitizer.from_element(XML(
... '''
... <sanitizer>
... <valid initial="none">
... <add preset="string.printable"/>
... <remove value="""/>
... <remove value="%s"/>
... </valid>
... <mapping initial="none">
... <add source=""" target="%s""/>
... <add source="%s" target="%s%s"/>
... </mapping>
... </sanitizer>
... ''' % (slash, slash, slash, slash, slash)))
>>> text = '%s"$rm&#!' % slash
>>> [c for c in sanitizer.sanitize_param(text)] == [slash, slash, slash, '"', '$', 'r', 'm', '&', '#', '!']
True
"""
VALID_PRESET = {"default": (f"{string.ascii_letters + string.digits} -=_.()/+*^,:?!"), "none": ""}
MAPPING_PRESET = {"default": galaxy.util.mapped_chars, "none": {}}
DEFAULT_INVALID_CHAR = "X"
# class methods
[docs] @classmethod
def from_element(cls, elem):
"""Loads the proper filter by the type attribute of elem"""
# TODO: Add ability to generically specify a method to use for sanitizing input via specification in tool XML
rval = ToolParameterSanitizer()
rval._invalid_char = elem.get("invalid_char", cls.DEFAULT_INVALID_CHAR)
rval.sanitize = galaxy.util.string_as_bool(elem.get("sanitize", "True"))
for valid_elem in elem.findall("valid"):
rval._valid_chars = rval.get_valid_by_name(valid_elem.get("initial", "default"))
for action_elem in valid_elem:
preset = rval.get_valid_by_name(action_elem.get("preset", "none"))
valid_value = list(action_elem.get("value", []))
if action_elem.tag.lower() == "add":
for val in preset + valid_value:
if val not in rval._valid_chars:
rval._valid_chars.append(val)
elif action_elem.tag.lower() == "remove":
for val in preset + valid_value:
while val in rval._valid_chars:
rval._valid_chars.remove(val)
else:
log.debug(f"Invalid action tag in valid: {action_elem.tag}")
for mapping_elem in elem.findall("mapping"):
rval._mapped_chars = rval.get_mapping_by_name(mapping_elem.get("initial", "default"))
for action_elem in mapping_elem:
map_source = action_elem.get("source", None)
map_target = action_elem.get("target", None)
preset = rval.get_mapping_by_name(action_elem.get("preset", "none"))
if action_elem.tag.lower() == "add":
rval._mapped_chars.update(preset)
if None not in [map_source, map_target]:
rval._mapped_chars[map_source] = map_target
elif action_elem.tag.lower() == "remove":
for map_key in preset.keys():
if map_key in rval._mapped_chars:
del rval._mapped_chars[map_key]
if map_source is not None and map_key in rval._mapped_chars:
del rval._mapped_chars[map_key]
else:
log.debug(f"Invalid action tag in mapping: {action_elem.tag}")
return rval
[docs] @classmethod
def get_valid_by_name(cls, name):
rval = []
for split_name in name.split(","):
# Remove ';' (if present) and everything after it
split_name = split_name.split(";", 1)[0]
split_name = split_name.strip()
value = []
if split_name.startswith("string."):
string_constant = split_name[7:]
if string_constant in ("letters", "lowercase", "uppercase"):
split_name = f"string.ascii_{string_constant}"
try:
value = eval(split_name)
except NameError as e:
log.debug(f"Invalid string preset specified: {e}")
elif split_name in cls.VALID_PRESET:
value = cls.VALID_PRESET[split_name]
else:
log.debug(f"Invalid preset name specified: {split_name}")
rval.extend([val for val in value if val not in rval])
return rval
[docs] @classmethod
def get_mapping_by_name(cls, name):
rval = {}
for split_name in name.split(","):
split_name = split_name.strip()
if split_name in cls.MAPPING_PRESET:
rval.update(cls.MAPPING_PRESET[split_name])
else:
log.debug(f"Invalid preset name specified: {split_name}")
return rval
# end class methods
[docs] def __init__(self):
self._valid_chars = [] # List of valid characters
self._mapped_chars = {} # Replace a char with a any number of characters
self._invalid_char = self.DEFAULT_INVALID_CHAR # Replace invalid characters with this character
self.sanitize = True # Simply pass back the passed in value
[docs] def restore_text(self, text):
"""Restores sanitized text"""
if self.sanitize:
for key, value in self._mapped_chars.items():
text = text.replace(value, key)
return text
[docs] def sanitize_text(self, text):
"""Restricts the characters that are allowed in a text"""
if not self.sanitize:
return text
rval = []
for c in text:
if c in self._valid_chars:
rval.append(c)
elif c in self._mapped_chars:
rval.append(self._mapped_chars[c])
else:
rval.append(self._invalid_char)
return "".join(rval)
[docs] def sanitize_param(self, value):
"""Clean incoming parameters (strings or lists)"""
if not self.sanitize:
return value
if isinstance(value, str):
return self.sanitize_text(value)
elif isinstance(value, list):
return list(map(self.sanitize_text, value))
else:
raise Exception(f"Unknown parameter type ({type(value)}:{value})")