+import os
+import json
+
+import xmlrpc.client
+
+# See "2.2 Characters" in the XML specification:
+#
+# #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
+# avoiding
+# [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
+
+invalid_codepoints = list(range(0x0, 0x8)) + [0xB, 0xC] + list(range(0xE, 0x1F))
+# broke with f24, somehow we get a unicode
+# as an incoming string to be translated
+str_xml_escape_table = \
+ str.maketrans("".join((chr(x) for x in invalid_codepoints)),
+ "?" * len(invalid_codepoints))
+# loosely inspired from
+# http://stackoverflow.com/questions/1324067/
+# how-do-i-get-str-translate-to-work-with-unicode-strings
+unicode_xml_escape_table = \
+ {invalid: "?" for invalid in invalid_codepoints}
+
+
+def xmlrpclib_escape(s, replace=str.replace):
+ """
+ xmlrpclib does not handle invalid 7-bit control characters. This
+ function augments xmlrpclib.escape, which by default only replaces
+ '&', '<', and '>' with entities.
+ """
+
+ # This is the standard xmlrpclib.escape function
+ s = replace(s, "&", "&")
+ s = replace(s, "<", "<")
+ s = replace(s, ">", ">",)
+
+ # Replace invalid 7-bit control characters with '?'
+ if isinstance(s, str):
+ return s.translate(str_xml_escape_table)
+ else:
+ return s.translate(unicode_xml_escape_table)
+
+
+def test_xmlrpclib_escape():
+ incomings = [
+ # full ASCII
+ "".join((chr(x) for x in range(128))),
+ # likewise but as a unicode string up to 256
+ "".join((chr(x) for x in range(256))),
+ ]
+ for incoming in incomings:
+ print("==================== xmlrpclib_escape INPUT")
+ print(type(incoming), '->', incoming)
+ print("==================== xmlrpclib_escape OUTPUT")
+ print(xmlrpclib_escape(incoming))
+
+
+def xmlrpclib_dump(self, value, write):
+ """
+ xmlrpclib cannot marshal instances of subclasses of built-in
+ types. This function overrides xmlrpclib.Marshaller.__dump so that
+ any value that is an instance of one of its acceptable types is
+ marshalled as that type.
+
+ xmlrpclib also cannot handle invalid 7-bit control characters. See
+ above.
+ """
+
+ # Use our escape function
+ args = [self, value, write]
+ if isinstance(value, str):
+ args.append(xmlrpclib_escape)
+
+ try:
+ # Try for an exact match first
+ f = self.dispatch[type(value)]
+ except KeyError:
+ # Try for an isinstance() match
+ for Type, f in self.dispatch.items():
+ if isinstance(value, Type):
+ f(*args)
+ return
+ raise TypeError("cannot marshal %s objects" % type(value))
+ else:
+ f(*args)
+
+
+# You can't hide from me!
+xmlrpc.client.Marshaller._Marshaller__dump = xmlrpclib_dump
+
+# SOAP support is optional