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

1""" 

2ViUR utility functions regarding string processing. 

3""" 

4import re 

5import secrets 

6import string 

7import warnings 

8 

9 

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 

15 

16 :param length: The desired length of the generated string. 

17 

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

21 

22 

23# String base mapping 

24__STRING_ESCAPE_MAPPING = { 

25 "<": "&lt;", 

26 ">": "&gt;", 

27 "\"": "&quot;", 

28 "'": "&#39;", 

29 "(": "&#40;", 

30 ")": "&#41;", 

31 "=": "&#61;", 

32 "\n": " ", 

33 "\0": "", 

34} 

35 

36# Translation table for string escaping 

37__STRING_ESCAPE_TRANSTAB = str.maketrans(__STRING_ESCAPE_MAPPING) 

38 

39# Lookup-table for string unescaping 

40__STRING_UNESCAPE_MAPPING = {v: k for k, v in __STRING_ESCAPE_MAPPING.items() if v} 

41 

42 

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. 

47 

48 :param val: The value to be escaped. 

49 :param max_length: Cut-off after max_length characters. None or 0 means "unlimited". 

50 

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 

57 

58 res = str(val).strip().translate(__STRING_ESCAPE_TRANSTAB) 

59 

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] 

62 

63 return res 

64 

65 

66def unescape(val: str) -> str: 

67 """ 

68 Unquotes characters formerly escaped by `escape`. 

69 

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". 

73 

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) 

82 

83 return __STRING_UNESCAPE_MAPPING.get(find) or re_match.group(0) 

84 

85 return re.sub(r"&(\w{2,4}|#0*(\d{2}));", __escape_replace, str(val).strip()) 

86 

87 

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`. 

92 

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. 

98 

99 Example: 

100 

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)