Skip to content

Commit

Permalink
Added escape methods to the project
Browse files Browse the repository at this point in the history
  • Loading branch information
joamag committed Jun 4, 2020
1 parent 195910b commit cdef6dc
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/quorum/__init__.py
Expand Up @@ -116,8 +116,8 @@
leafs, load_form, load_locale, get_locale, get_langs, set_locale, reset_locale, anotate_async,\
anotate_secure, run_thread, camel_to_underscore, camel_to_readable, underscore_to_camel, underscore_to_readable,\
generate_identifier, to_locale, nl_to_br, nl_to_br_jinja, sp_to_nbsp, sp_to_nbsp_jinja, unset, date_time,\
quote, unquote, is_content_type, parse_content_type, verify, verify_equal, verify_not_equal, verify_many,\
execute, deprecated, JSONEncoder
quote, unquote, escape, unescape, count_unescape, split_unescape, is_content_type, parse_content_type,\
verify, verify_equal, verify_not_equal, verify_many, execute, deprecated, JSONEncoder
from .validation import validate, validate_b, validate_e, safe, eq, gt, gte, lt, lte, not_null,\
not_empty, not_false, is_in, is_upper, is_lower, is_simple, is_email, is_url, is_regex, field_eq,\
field_gt, field_gte, field_lt, field_lte, string_gt, string_lt, string_eq, equals, not_past,\
Expand Down
71 changes: 71 additions & 0 deletions src/quorum/test/util.py
Expand Up @@ -492,6 +492,77 @@ def test_generate_identifier(self):
for char in identifier:
self.assertTrue(char in string.ascii_uppercase)

@quorum.secured
def test_escape(self):
result = quorum.escape("foo,bar", ",", escape = "$")
self.assertEqual(result, "foo$,bar")

result = quorum.escape("foo$,bar", ",", escape = "$")
self.assertEqual(result, "foo$$$,bar")

@quorum.secured
def test_unescape(self):
result = quorum.unescape("foo$,bar", escape = "$")
self.assertEqual(result, "foo,bar")

result = quorum.unescape("foo$$,bar", escape = "$")
self.assertEqual(result, "foo$,bar")

result = quorum.unescape("$$foo$,bar$$$$", escape = "$")
self.assertEqual(result, "$foo,bar$$")

@quorum.secured
def test_count_unescape(self):
result = quorum.count_unescape("foo:bar", ":")
self.assertEqual(result, 1)

result = quorum.count_unescape("foo:bar:hello:world", ":")
self.assertEqual(result, 3)

result = quorum.count_unescape("foo,bar,hello,world", ":")
self.assertEqual(result, 0)

result = quorum.count_unescape("foo:bar\\:hello:world", ":")
self.assertEqual(result, 2)

result = quorum.count_unescape("foo:bar\\:hello\\:world", ":")
self.assertEqual(result, 1)

result = quorum.count_unescape("foo:bar\\:hello\\\\:world", ":")
self.assertEqual(result, 2)

result = quorum.count_unescape("foo\\:bar\\:hello\\:world", ":")
self.assertEqual(result, 0)

@quorum.secured
def test_split_unescape(self):
result = quorum.split_unescape("foo bar")
self.assertEqual(result, ["foo", "bar"])

result = quorum.split_unescape("foo bar hello world", max = 2)
self.assertEqual(result, ["foo", "bar", "hello world"])

result = quorum.split_unescape("foo,bar", ",")
self.assertEqual(result, ["foo", "bar"])

result = quorum.split_unescape("foo$,bar", ",", escape = "$")
self.assertEqual(result, ["foo,bar"])

result = quorum.split_unescape("foo$$,bar", ",", escape = "$", unescape = True)
self.assertEqual(result, ["foo$", "bar"])

result = quorum.split_unescape("foo$$,bar", ",", escape = "$", unescape = False)
self.assertEqual(result, ["foo$$", "bar"])

result = quorum.split_unescape("foo$", ",", escape = "$", unescape = True)
self.assertEqual(result, ["foo$"])

result = quorum.split_unescape("foo\\\\\\:bar", ":", unescape = True)
self.assertEqual(result, ["foo\\:bar"])

result = quorum.split_unescape("foo\\\\:bar", ":", unescape = True)
self.assertEqual(result, ["foo\\", "bar"])

@quorum.secured
def test_is_content_type(self):
result = quorum.is_content_type("text/plain", "text/plain")
Expand Down
126 changes: 126 additions & 0 deletions src/quorum/util.py
Expand Up @@ -1173,6 +1173,132 @@ def unquote(value, *args, **kwargs):
if is_bytes: value = value.decode("utf-8")
return value

def escape(value, char, escape = "\\"):
"""
Escapes the provided string value according to the requested
target character and escape value. Meaning that all the characters
are going to be replaced by the escape plus character sequence.
:type value: String
:param value: The string that is going to have the target characters
escaped according to the escape character.
:type char: String
:param char: The character that is going to be "target" of escaping.
:type escape: String
:param escape: The character to be used for escaping (normally`\`).
:rtype: String
:return: The final string with the target character properly escaped.
"""

return value.replace(escape, escape + escape).replace(char, escape + char)

def unescape(value, escape = "\\"):
"""
Unescapes the provided string value using the provided escape
character as the reference for the unescape operation.
This is considered to be a very expensive operation and so it
should be used carefully.
:type value: String
:param value: The string value that is going to be unescape.
:rtype: String
:return: The final unescaped value.
"""

result = []
iterator = iter(value)
for char in iterator:
if char == escape:
try:
result.append(next(iterator))
except StopIteration:
result.append(escape)
else:
result.append(char)
return "".join(result)

def count_unescape(value, sub, escape = "\\"):
"""
Runs the sub string count operation on an escaped string
so that it takes into account the escaped values avoiding
them for the count operation.
:type value: String
:param value: The base string value to have the number of
occurrences of a sub string counted.
:type sub: String
:param sub: The sub string to be evaluated for occurrences,
this value should be constrained to strings of single character.
:type escape: String
:param escape: The "special" escape character that will allow the
delimiter to be also present in the choices selection.
:rtype: int
:return: The final count of occurrences of the sub string
taking into account the proper escaping of the string.
"""

count = 0
iterator = iter(value)
for char in iterator:
if char == escape:
try:
next(iterator)
except StopIteration:
pass
elif char == sub:
count += 1
return count

def split_unescape(value, delimiter = " ", max = -1, escape = "\\", unescape = True):
"""
Splits the provided string around the delimiter character that
has been provided and allows proper escaping of it using the
provided escape character.
This is considered to be a very expensive operation when compared
to the simple split operation and so it should be used carefully.
:type value: String
:param value: The string value that is going to be split around
the proper delimiter value taking into account the escaping.
:type delimiter: String
:param delimiter: The delimiter character to be used in the split
operation.
:type max: int
:param max: The maximum number of split operations that are going
to be performed by this operation.
:type escape: String
:param escape: The "special" escape character that will allow the
delimiter to be also present in the choices selection.
:type unescape: bool
:param unescape: If the final resulting string should be already
unescaped (normalized).
:rtype: List
:return: The final list containing the multiple string parts separated
by the delimiter character and respecting the escape sequences.
"""

result = []
current = []
iterator = iter(value)
count = 0
for char in iterator:
if char == escape:
try:
if not unescape: current.append(escape)
current.append(next(iterator))
except StopIteration:
if unescape: current.append(escape)
elif char == delimiter and not count == max:
result.append("".join(current))
current = []
count += 1
else:
current.append(char)
result.append("".join(current))
return result

def is_content_type(data, target):
"""
Verifies if the any of the provided mime types (target) is
Expand Down

0 comments on commit cdef6dc

Please sign in to comment.