Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/utils/string.py: 78%
26 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
1"""
2ViUR utility functions regarding string processing.
3"""
4import re
5import secrets
6import string
7import warnings
10def random(length: int = 13) -> str:
11 """
12 Return a string containing random characters of given *length*.
13 It's safe to use this string in URLs or HTML.
14 Because we use the secrets module it could be used for security purposes as well
16 :param length: The desired length of the generated string.
18 :returns: A string with random characters of the given length.
19 """
20 return "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(length))
23# String base mapping
24__STRING_ESCAPE_MAPPING = {
25 "<": "<",
26 ">": ">",
27 "\"": """,
28 "'": "'",
29 "(": "(",
30 ")": ")",
31 "=": "=",
32 "\n": " ",
33 "\0": "",
34}
36# Translation table for string escaping
37__STRING_ESCAPE_TRANSTAB = str.maketrans(__STRING_ESCAPE_MAPPING)
39# Lookup-table for string unescaping
40__STRING_UNESCAPE_MAPPING = {v: k for k, v in __STRING_ESCAPE_MAPPING.items() if v}
43def escape(val: str, max_length: int | None = 254, maxLength: int | None = None) -> str:
44 """
45 Quotes special characters from a string and removes "\\\\0".
46 It shall be used to prevent XSS injections in data.
48 :param val: The value to be escaped.
49 :param max_length: Cut-off after max_length characters. None or 0 means "unlimited".
51 :returns: The quoted string.
52 """
53 # fixme: Remove in viur-core >= 4
54 if maxLength is not None and max_length == 254: 54 ↛ 55line 54 didn't jump to line 55 because the condition on line 54 was never true
55 warnings.warn("'maxLength' is deprecated, please use 'max_length'", DeprecationWarning)
56 max_length = maxLength
58 res = str(val).strip().translate(__STRING_ESCAPE_TRANSTAB)
60 if max_length: 60 ↛ 63line 60 didn't jump to line 63 because the condition on line 60 was always true
61 return res[:max_length]
63 return res
66def unescape(val: str) -> str:
67 """
68 Unquotes characters formerly escaped by `escape`.
70 :param val: The value to be unescaped.
71 :param max_length: Optional cut-off after max_length characters. \
72 A value of None or 0 means "unlimited".
74 :returns: The unquoted string.
75 """
76 def __escape_replace(re_match):
77 # In case group 2 is matched, search for its escape sequence
78 if find := re_match.group(2):
79 find = f"&#{find};"
80 else:
81 find = re_match.group(0)
83 return __STRING_UNESCAPE_MAPPING.get(find) or re_match.group(0)
85 return re.sub(r"&(\w{2,4}|#0*(\d{2}));", __escape_replace, str(val).strip())
88def is_prefix(name: str, prefix: str, delimiter: str = ".") -> bool:
89 """
90 Utility function to check if a given name matches a prefix,
91 which defines a specialization, delimited by `delimiter`.
93 In ViUR, modules, bones, renders, etc. provide a kind or handler
94 to classify or subclassify the specific object. To securitly
95 check for a specific type, it is either required to ask for the
96 exact type or if its prefixed by a path delimited normally by
97 dots.
99 Example:
101 .. code-block:: python
102 handler = "tree.file.special"
103 utils.string.is_prefix(handler, "tree") # True
104 utils.string.is_prefix(handler, "tree.node") # False
105 utils.string.is_prefix(handler, "tree.file") # True
106 utils.string.is_prefix(handler, "tree.file.special") # True
107 """
108 return name == prefix or name.startswith(prefix + delimiter)