diff options
author | Bert JW Regeer <bertjw@regeer.org> | 2018-09-22 20:33:39 -0600 |
---|---|---|
committer | Bert JW Regeer <bertjw@regeer.org> | 2018-12-01 19:57:36 -0700 |
commit | 5dd53c49a27bd1ebd6699b66b06d445bceedc7f5 (patch) | |
tree | 175c35a14b3cab2e70fe9d05c9ad7ba281ab79b3 | |
parent | 77bb0026eebaa94845cc1a967171ca2f3e3fdfe0 (diff) | |
download | waitress-5dd53c49a27bd1ebd6699b66b06d445bceedc7f5.tar.gz |
Add undquote utility function
-rw-r--r-- | waitress/tests/test_utilities.py | 33 | ||||
-rw-r--r-- | waitress/utilities.py | 43 |
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') |