diff -r 85c04fdaa404 Lib/test/test_xmlrpc.py --- a/Lib/test/test_xmlrpc.py Wed May 22 15:28:30 2013 +0300 +++ b/Lib/test/test_xmlrpc.py Wed May 22 21:00:22 2013 +0300 @@ -187,6 +187,26 @@ self.assertEqual(s, "abc \xc2\x95") self.assertEqual(items, [("def \xc2\x96", "ghi \xc2\x97")]) + def test_dump_invalid_string(self): + # ASCII control characters + for i in set(range(32)) - {9, 10, 13}: + self.assertRaises(ValueError, xmlrpclib.dumps, (chr(i),)) + # UTF-8 encoded surrogates, U+FFFE, and U+FFFF + for s in ('\xed\xb0\x80', '\xed\xbf\xbf', + '\xef\xbf\xbe', '\xef\xbf\xbf'): + self.assertRaises(ValueError, xmlrpclib.dumps, (s,)) + # Invalid UTF-8 + for s in ('\x80', '\xc1\xbf', '\xc2', '\xe0\x9f\xbf', '\xe0\xa0', + '\xf0\x8f\xbf\xbf', '\xf0\x90\x80', '\xf4\x90\x80\x80'): + self.assertRaises(ValueError, xmlrpclib.dumps, (s,)) + + @unittest.skipUnless(have_unicode, 'requires unicode support') + def test_dump_invalid_unicode(self): + for i in ((set(range(32)) - {9, 10, 13}) | + set(range(0xdc00, 0xc000)) | + {0xfffe, 0xffff}): + self.assertRaises(ValueError, xmlrpclib.dumps, (unichr(i),)) + class HelperTestCase(unittest.TestCase): def test_escape(self): diff -r 85c04fdaa404 Lib/xmlrpclib.py --- a/Lib/xmlrpclib.py Wed May 22 15:28:30 2013 +0300 +++ b/Lib/xmlrpclib.py Wed May 22 21:00:22 2013 +0300 @@ -142,6 +142,7 @@ import socket import errno import httplib +import re try: import gzip except ImportError: @@ -171,6 +172,20 @@ data = unicode(data, encoding) return data +if unicode: + _invalid_chars_re = re.compile(u'[\x00-\x08\x0b\x0c\x0e-\x1f' + u'\udc00-\udfff|\ufffe|\uffff]', re.S) + def _check_unicode(s, isinvalid=_invalid_chars_re.search): + if isinvalid(s): + raise ValueError('invalid string') + def _check_str(s, encoding, _check_unicode=_check_unicode): + _check_unicode(unicode(s, encoding or 'utf-8')) +else: + _invalid_chars_re = re.compile('[\x00-\x08\x0b\x0c\x0e-\x1f', re.S) + def _check_str(s, encoding, isinvalid=_invalid_chars_re.search): + if isinvalid(s): + raise ValueError('invalid string') + def escape(s, replace=string.replace): s = replace(s, "&", "&") s = replace(s, "<", "<") @@ -690,6 +705,7 @@ dispatch[FloatType] = dump_double def dump_string(self, value, write, escape=escape): + _check_str(value, self.encoding) write("") write(escape(value)) write("\n") @@ -697,6 +713,7 @@ if unicode: def dump_unicode(self, value, write, escape=escape): + _check_unicode(value) value = value.encode(self.encoding) write("") write(escape(value)) @@ -726,11 +743,13 @@ write("\n") for k, v in value.items(): write("\n") - if type(k) is not StringType: - if unicode and type(k) is UnicodeType: - k = k.encode(self.encoding) - else: - raise TypeError, "dictionary key must be string" + if type(k) is StringType: + _check_str(k, self.encoding) + elif unicode and type(k) is UnicodeType: + _check_unicode(k) + k = k.encode(self.encoding) + else: + raise TypeError, "dictionary key must be string" write("%s\n" % escape(k)) dump(v, write) write("\n")