Skip to content

Commit

Permalink
Escape more characters in sql_quote. Taken over from PloneHotfix20200…
Browse files Browse the repository at this point in the history
…121.

Backported from master branch.
  • Loading branch information
mauritsvanrees committed Jan 31, 2020
1 parent fd562ea commit 7b659c0
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
2.13.5 (unreleased)
-------------------

- Escape more characters in ``sql_quote``. Taken over from PloneHotfix20200121.

2.13.4 (2017-02-15)
-------------------
Expand Down
39 changes: 37 additions & 2 deletions src/DocumentTemplate/DT_Var.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,15 +456,50 @@ def structured_text(v, name='(Unknown name)', md={}):
doc = DocumentWithImages()(txt)
return HTML()(doc, level, header=False)

# Searching and replacing a byte in text, or text in bytes,
# may give various errors on Python 2 or 3. So we make separate functions
REMOVE_BYTES = (b'\x00', b'\x1a', b'\r')
REMOVE_TEXT = (u'\x00', u'\x1a', u'\r')
DOUBLE_BYTES = (b"'", b'\\')
DOUBLE_TEXT = (u"'", u'\\')
ESCAPE_BYTES = (b'"',)
ESCAPE_TEXT = (u'"',)

def bytes_sql_quote(v):
# Helper function for sql_quote, handling only bytes.
# Remove bad characters.
for char in REMOVE_BYTES:
v = v.replace(char, b'')
# Double untrusted characters to make them harmless.
for char in DOUBLE_BYTES:
v = v.replace(char, char * 2)
# Backslash-escape untrusted characters to make them harmless.
for char in ESCAPE_BYTES:
v = v.replace(char, b'\\%s' % char)
return v

def text_sql_quote(v):
# Helper function for sql_quote, handling only text.
# Remove bad characters.
for char in REMOVE_TEXT:
v = v.replace(char, u'')
# Double untrusted characters to make them harmless.
for char in DOUBLE_TEXT:
v = v.replace(char, char * 2)
# Backslash-escape untrusted characters to make them harmless.
for char in ESCAPE_TEXT:
v = v.replace(char, u'\\%s' % char)
return v

def sql_quote(v, name='(Unknown name)', md={}):
"""Quote single quotes in a string by doubling them.
This is needed to securely insert values into sql
string literals in templates that generate sql.
"""
if v.find("'") >= 0: return v.replace("'", "''")
return v
if isinstance(v, bytes):
return bytes_sql_quote(v)
return text_sql_quote(v)

special_formats={
'whole-dollars': whole_dollars,
Expand Down
99 changes: 96 additions & 3 deletions src/DocumentTemplate/tests/test_DT_Var.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_newline_to_br(self):
<br />
line three<br />
<BLANKLINE>
>>> dos = text.replace('\n', '\r\n')
>>> DT_Var.newline_to_br(text) == DT_Var.newline_to_br(dos)
True
Expand Down Expand Up @@ -74,7 +74,7 @@ def test_url_quoting(self):

self.assertEquals(url_quote(unicode_value), quoted_unicode_value)
self.assertEquals(url_quote(utf8_value), quoted_utf8_value)

self.assertEquals(url_unquote(quoted_unicode_value), unicode_value)
self.assertEquals(url_unquote(quoted_utf8_value), utf8_value)

Expand All @@ -89,15 +89,108 @@ def test_url_quoting_plus(self):

self.assertEquals(url_quote_plus(unicode_value), quoted_unicode_value)
self.assertEquals(url_quote_plus(utf8_value), quoted_utf8_value)

self.assertEquals(url_unquote_plus(quoted_unicode_value), unicode_value)
self.assertEquals(url_unquote_plus(quoted_utf8_value), utf8_value)


class SqlQuoting(unittest.TestCase):

def test_bytes_sql_quote(self):
from DocumentTemplate.DT_Var import bytes_sql_quote
self.assertEqual(bytes_sql_quote(b""), b"")
self.assertEqual(bytes_sql_quote(b"a"), b"a")

self.assertEqual(bytes_sql_quote(b"Can't"), b"Can''t")
self.assertEqual(bytes_sql_quote(b"Can\'t"), b"Can''t")
self.assertEqual(bytes_sql_quote(br"Can\'t"), b"Can\\\\''t")

self.assertEqual(bytes_sql_quote(b"Can\\ I?"), b"Can\\\\ I?")
self.assertEqual(bytes_sql_quote(br"Can\ I?"), b"Can\\\\ I?")

self.assertEqual(
bytes_sql_quote(b'Just say "Hello"'), b'Just say \\"Hello\\"')

self.assertEqual(
bytes_sql_quote(b'Hello\x00World'), b'HelloWorld')
self.assertEqual(
bytes_sql_quote(b'\x00Hello\x00\x00World\x00'), b'HelloWorld')

self.assertEqual(
bytes_sql_quote(b"carriage\rreturn"), b"carriagereturn")
self.assertEqual(bytes_sql_quote(b"line\nbreak"), b"line\nbreak")
self.assertEqual(bytes_sql_quote(b"tab\t"), b"tab\t")

def test_text_sql_quote(self):
from DocumentTemplate.DT_Var import text_sql_quote
self.assertEqual(text_sql_quote(u""), u"")
self.assertEqual(text_sql_quote(u"a"), u"a")

self.assertEqual(text_sql_quote(u"Can't"), u"Can''t")
self.assertEqual(text_sql_quote(u"Can\'t"), u"Can''t")
# SyntaxError on Python 3.
# self.assertEqual(text_sql_quote(ur"Can\'t"), u"Can\\\\''t")

self.assertEqual(text_sql_quote(u"Can\\ I?"), u"Can\\\\ I?")
# SyntaxError on Python 3.
# self.assertEqual(text_sql_quote(ur"Can\ I?"), u"Can\\\\ I?")

self.assertEqual(
text_sql_quote(u'Just say "Hello"'), u'Just say \\"Hello\\"')

self.assertEqual(
text_sql_quote(u'Hello\x00World'), u'HelloWorld')
self.assertEqual(
text_sql_quote(u'\x00Hello\x00\x00World\x00'), u'HelloWorld')

self.assertEqual(
text_sql_quote(u"carriage\rreturn"), u"carriagereturn")
self.assertEqual(text_sql_quote(u"line\nbreak"), u"line\nbreak")
self.assertEqual(text_sql_quote(u"tab\t"), u"tab\t")

def test_sql_quote(self):
from DocumentTemplate.DT_Var import sql_quote
self.assertEqual(sql_quote(u""), u"")
self.assertEqual(sql_quote(u"a"), u"a")
self.assertEqual(sql_quote(b"a"), b"a")

self.assertEqual(sql_quote(u"Can't"), u"Can''t")
self.assertEqual(sql_quote(u"Can\'t"), u"Can''t")
# SyntaxError on Python 3.
# self.assertEqual(sql_quote(ur"Can\'t"), u"Can\\\\''t")

self.assertEqual(sql_quote(u"Can\\ I?"), u"Can\\\\ I?")
# SyntaxError on Python 3.
# self.assertEqual(sql_quote(ur"Can\ I?"), u"Can\\\\ I?")

self.assertEqual(
sql_quote(u'Just say "Hello"'), u'Just say \\"Hello\\"')

self.assertEqual(
sql_quote(u'Hello\x00World'), u'HelloWorld')
self.assertEqual(
sql_quote(u'\x00Hello\x00\x00World\x00'), u'HelloWorld')

self.assertEqual(
sql_quote(u'\x00Hello\x00\x00World\x00'), u'HelloWorld')

self.assertEqual(u"\xea".encode("utf-8"), b"\xc3\xaa")
self.assertEqual(sql_quote(u"\xea'"), u"\xea''")
self.assertEqual(sql_quote(b"\xc3\xaa'"), b"\xc3\xaa''")

self.assertEqual(
sql_quote(b"carriage\rreturn"), b"carriagereturn")
self.assertEqual(
sql_quote(u"carriage\rreturn"), u"carriagereturn")
self.assertEqual(sql_quote(u"line\nbreak"), u"line\nbreak")
self.assertEqual(sql_quote(u"tab\t"), u"tab\t")


def test_suite():
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite())
suite.addTest(unittest.makeSuite(TestUrlQuoting))
suite.addTest(unittest.makeSuite(SqlQuoting))
return suite

if __name__ == '__main__':
Expand Down

0 comments on commit 7b659c0

Please sign in to comment.