summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBert JW Regeer <bertjw@regeer.org>2018-09-22 20:33:39 -0600
committerBert JW Regeer <bertjw@regeer.org>2018-12-01 19:57:36 -0700
commit5dd53c49a27bd1ebd6699b66b06d445bceedc7f5 (patch)
tree175c35a14b3cab2e70fe9d05c9ad7ba281ab79b3
parent77bb0026eebaa94845cc1a967171ca2f3e3fdfe0 (diff)
downloadwaitress-5dd53c49a27bd1ebd6699b66b06d445bceedc7f5.tar.gz
Add undquote utility function
-rw-r--r--waitress/tests/test_utilities.py33
-rw-r--r--waitress/utilities.py43
2 files changed, 76 insertions, 0 deletions
diff --git a/waitress/tests/test_utilities.py b/waitress/tests/test_utilities.py
index 95b39f3..f7691f0 100644
--- a/waitress/tests/test_utilities.py
+++ b/waitress/tests/test_utilities.py
@@ -99,3 +99,36 @@ class TestBadRequest(unittest.TestCase):
inst = self._makeOne()
self.assertEqual(inst.body, 1)
+class Test_undquote(unittest.TestCase):
+
+ def _callFUT(self, value):
+ from waitress.utilities import undquote
+ return undquote(value)
+
+ def test_empty(self):
+ self.assertEqual(self._callFUT(''), '')
+
+ def test_quoted(self):
+ self.assertEqual(self._callFUT('"test"'), 'test')
+
+ def test_unquoted(self):
+ self.assertEqual(self._callFUT('test'), 'test')
+
+ def test_quoted_backslash_quote(self):
+ self.assertEqual(self._callFUT('"\\""'), '"')
+
+ def test_quoted_htab(self):
+ self.assertEqual(self._callFUT("\"\t\""), "\t")
+
+ def test_quoted_backslash_htab(self):
+ self.assertEqual(self._callFUT("\"\\\t\""), "\t")
+
+ def test_quoted_backslash_invalid(self):
+ self.assertRaises(ValueError, self._callFUT, '"\\"')
+
+ def test_invalid_quoting(self):
+ self.assertRaises(ValueError, self._callFUT, '"test')
+
+ def test_invalid_quoting_single_quote(self):
+ self.assertRaises(ValueError, self._callFUT, '"')
+
diff --git a/waitress/utilities.py b/waitress/utilities.py
index 2c212a0..85fa138 100644
--- a/waitress/utilities.py
+++ b/waitress/utilities.py
@@ -204,3 +204,46 @@ class RequestEntityTooLarge(BadRequest):
class InternalServerError(Error):
code = 500
reason = 'Internal Server Error'
+
+
+# RFC 5234 Appendix B.1 "Core Rules":
+# VCHAR = %x21-7E
+# ; visible (printing) characters
+vchar_re = '\x21-\x7e'
+
+# RFC 7230 Section 3.2.6 "Field Value Components":
+# quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
+# qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
+# obs-text = %x80-FF
+# quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
+obs_text_re = '\x80-\xff'
+
+# The '\\' between \x5b and \x5d is needed to escape \x5d (']')
+qdtext_re = '[\t \x21\x23-\x5b\\\x5d-\x7e' + obs_text_re + ']'
+
+quoted_pair_re = r'\\' + '([\t ' + vchar_re + obs_text_re + '])'
+quoted_string_re = \
+ '"(?:(?:' + qdtext_re + ')|(?:' + quoted_pair_re + '))*"'
+
+quoted_string = re.compile(quoted_string_re)
+quoted_pair = re.compile(quoted_pair_re)
+
+
+def undquote(value):
+ if value.startswith('"') and value.endswith('"'):
+ # So it claims to be DQUOTE'ed, let's validate that
+ matches = quoted_string.match(value)
+
+ if matches and matches.end() == len(value):
+ # Remove the DQUOTE's from the value
+ value = value[1:-1]
+
+ # Remove all backslashes that are followed by a valid vchar or
+ # obs-text
+ value = quoted_pair.sub(r'\1', value)
+
+ return value
+ elif not value.startswith('"') and not value.endswith('"'):
+ return value
+
+ raise ValueError('Invalid quoting in value')