""" Custom xmlrpc handling for Koji """ from __future__ import absolute_import import types import defusedxml.xmlrpc as defusedxmlrpc import re import six # importing here for references in koji by defused.xmlrpc.monkey_patch() below import six.moves.xmlrpc_client as xmlrpc_client import six.moves.xmlrpc_server as xmlrpc_server # noqa: F401 # patching xmlrpc to protect against XML related attacks defusedxmlrpc.monkey_patch() # duplicate a few values that we need getparser = xmlrpc_client.getparser loads = xmlrpc_client.loads Fault = xmlrpc_client.Fault DateTime = xmlrpc_client.DateTime class ExtendedMarshaller(xmlrpc_client.Marshaller): dispatch = xmlrpc_client.Marshaller.dispatch.copy() def _dump(self, value, write): # Parent class is unfriendly to subclasses :-/ f = self.dispatch[type(value)] f(self, value, write) def dump_generator(self, value, write): dump = self._dump write("\n") for v in value: dump(v, write) write("\n") dispatch[types.GeneratorType] = dump_generator MAXI8 = 2 ** 63 - 1 MINI8 = -2 ** 63 def dump_int(self, value, write): # python2's xmlrpclib doesn't support i8 extension for marshalling, # but can unmarshall it correctly. if (value > self.MAXI8 or value < self.MINI8): raise OverflowError("long int exceeds XML-RPC limits") elif (value > xmlrpc_client.MAXINT or value < xmlrpc_client.MININT): write("") write(str(int(value))) write("\n") else: return xmlrpc_client.Marshaller.dump_int(self, value, write) dispatch[int] = dump_int def dump_re(self, value, write): return self._dump(repr(value), write) # re.Pattern is supported >= py3.7 try: dispatch[re.Pattern] = dump_re except AttributeError: dispatch[re._pattern_type] = dump_re if six.PY2: ExtendedMarshaller.dispatch[long] = ExtendedMarshaller.dump_int # noqa: F821 def dumps(params, methodname=None, methodresponse=None, encoding=None, allow_none=1, marshaller=None): """encode an xmlrpc request or response Differences from the xmlrpclib version: - allow_none is on by default - uses our ExtendedMarshaller by default - option to specify marshaller """ if isinstance(params, Fault): methodresponse = 1 elif not isinstance(params, tuple): raise TypeError('params must be a tuple or Fault instance') elif methodresponse and len(params) != 1: raise ValueError('response tuple must be a singleton') if not encoding: encoding = "utf-8" if marshaller is not None: m = marshaller(encoding, allow_none=True) else: m = ExtendedMarshaller(encoding, allow_none=True) data = m.dumps(params) if encoding != "utf-8": xmlheader = "\n" % str(encoding) else: xmlheader = "\n" # utf-8 is default # standard XML-RPC wrappings if methodname: # a method call if six.PY2 and isinstance(methodname, six.text_type): # Do we need this? methodname = methodname.encode(encoding, 'xmlcharrefreplace') parts = ( xmlheader, "\n" "", methodname, "\n", data, "\n" ) elif methodresponse: # a method response, or a fault structure parts = ( xmlheader, "\n", data, "\n" ) else: return data # return as is return ''.join(parts)