From 237c16f0fd3998f4a2e69d9096997d10af2cf8c9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 24 Nov 2008 12:51:03 -0700 Subject: Started moving xmlrpc-functions from ipalib.util to ipalib.rpc --- ipa_server/rpcserver.py | 32 +++++++++++ ipalib/plugins/b_xmlrpc.py | 3 +- ipalib/rpc.py | 86 +++++++++++++++++++++++++++++ tests/test_ipalib/test_rpc.py | 123 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 ipa_server/rpcserver.py create mode 100644 ipalib/rpc.py create mode 100644 tests/test_ipalib/test_rpc.py diff --git a/ipa_server/rpcserver.py b/ipa_server/rpcserver.py new file mode 100644 index 00000000..1fbbf4aa --- /dev/null +++ b/ipa_server/rpcserver.py @@ -0,0 +1,32 @@ +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Dispatcher for RPC server. +""" + +from ipalib import Backend +from ipalib.rpc import xmlrpc_wrap, xmlrpc_unwrap + + +class XMLServer(Backend): + + def dispatch(self, method, params): + self.info('Received call to %r', method) + params = xml_unwrap(params) diff --git a/ipalib/plugins/b_xmlrpc.py b/ipalib/plugins/b_xmlrpc.py index b6e113a5..14f2a9be 100644 --- a/ipalib/plugins/b_xmlrpc.py +++ b/ipalib/plugins/b_xmlrpc.py @@ -47,11 +47,10 @@ class xmlrpc(Backend): # Are there any reasonably common XML-RPC client implementations # that don't support the extension? # See: http://docs.python.org/library/xmlrpclib.html - uri = self.api.env.xmlrpc_uri + uri = self.env.xmlrpc_uri if uri.startswith('https://'): return xmlrpclib.ServerProxy(uri, transport=KerbTransport(), - #verbose=self.api.env.verbose, ) return xmlrpclib.ServerProxy(uri) diff --git a/ipalib/rpc.py b/ipalib/rpc.py new file mode 100644 index 00000000..c4662f84 --- /dev/null +++ b/ipalib/rpc.py @@ -0,0 +1,86 @@ +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Core RPC functionality. +""" + +from types import NoneType +from xmlrpclib import Binary + + +def xmlrpc_wrap(value): + """ + Wrap all ``str`` in ``xmlrpclib.Binary``. + + Because ``xmlrpclib.dumps()`` will itself convert all ``unicode`` instances + into UTF-8 encoded ``str`` instances, we don't do it here. + + So in total, when encoding data for an XML-RPC request, the following + transformations occur: + + * All ``str`` instances are treated as binary data and are wrapped in + an ``xmlrpclib.Binary()`` instance. + + * Only ``unicode`` instances are treated as character data. They get + converted to UTF-8 encoded ``str`` instances (although as mentioned, + not by this function). + + Also see `xmlrpc_unwrap`. + """ + if type(value) in (list, tuple): + return tuple(xmlrpc_wrap(v) for v in value) + if type(value) is dict: + return dict( + (k, xmlrpc_wrap(v)) for (k, v) in value.iteritems() + ) + if type(value) is str: + return Binary(value) + assert type(value) in (unicode, int, float, bool, NoneType) + return value + + +def xmlrpc_unwrap(value, encoding='UTF-8'): + """ + Unwrap all ``xmlrpc.Binary``, decode all ``str`` into ``unicode``. + + When decoding data from an XML-RPC request, the following transformations + occur: + + * The binary payloads of all ``xmlrpclib.Binary`` instances are + returned as ``str`` instances. + + * All ``str`` instances are treated as UTF-8 encoded character data. + They are decoded and the resulting ``unicode`` instance is returned. + + Also see `xmlrpc_wrap`. + """ + if type(value) in (list, tuple): + return tuple(xmlrpc_unwrap(v, encoding) for v in value) + if type(value) is dict: + return dict( + (k, xmlrpc_unwrap(v, encoding)) for (k, v) in value.iteritems() + ) + if type(value) is str: + return value.decode(encoding) + if isinstance(value, Binary): + assert type(value.data) is str + return value.data + assert type(value) in (unicode, int, float, bool, NoneType) + return value diff --git a/tests/test_ipalib/test_rpc.py b/tests/test_ipalib/test_rpc.py new file mode 100644 index 00000000..14af2bf4 --- /dev/null +++ b/tests/test_ipalib/test_rpc.py @@ -0,0 +1,123 @@ +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Test the `ipalib.rpc` module. +""" + +from xmlrpclib import Binary, dumps, loads +import struct +from tests.util import raises +from ipalib import rpc + + +# A string that should have bytes 'x\00' through '\xff': +BINARY_BYTES = ''.join(struct.pack('B', d) for d in xrange(256)) +assert '\x00' in BINARY_BYTES and '\xff' in BINARY_BYTES +assert type(BINARY_BYTES) is str and len(BINARY_BYTES) == 256 + +# A UTF-8 encoded str +UTF8_BYTES = '\xd0\x9f\xd0\xb0\xd0\xb2\xd0\xb5\xd0\xbb' + +# The same UTF-8 data decoded (a unicode instance) +UNICODE_CHARS = u'\u041f\u0430\u0432\u0435\u043b' +assert UTF8_BYTES.decode('UTF-8') == UNICODE_CHARS +assert UNICODE_CHARS.encode('UTF-8') == UTF8_BYTES +assert UTF8_BYTES != UNICODE_CHARS + + +def dump_n_load(value): + (param, method) = loads( + dumps((value,)) + ) + return param[0] + + +def round_trip(value): + return rpc.xmlrpc_unwrap( + dump_n_load(rpc.xmlrpc_wrap(value)) + ) + + +def test_round_trip(): + """ + Test `ipalib.rpc.xmlrpc_wrap` and `ipalib.rpc.xmlrpc_unwrap`. + + This tests the two functions together with ``xmlrpclib.dumps()`` and + ``xmlrpclib.loads()`` in a full encode/dumps/loads/decode round trip. + """ + # We first test that our assumptions about xmlrpclib module in the Python + # standard library are correct: + assert dump_n_load(UTF8_BYTES) == UNICODE_CHARS + assert dump_n_load(UNICODE_CHARS) == UNICODE_CHARS + assert dump_n_load(Binary(BINARY_BYTES)).data == BINARY_BYTES + assert isinstance(dump_n_load(Binary(BINARY_BYTES)), Binary) + assert type(dump_n_load('hello')) is str + assert type(dump_n_load(u'hello')) is str + + # Now we test our wrap and unwrap methods in combination with dumps, loads: + # All str should come back str (because they get wrapped in + # xmlrpclib.Binary(). All unicode should come back unicode because str + # explicity get decoded by rpc.xmlrpc_unwrap() if they weren't already + # decoded by xmlrpclib.loads(). + assert round_trip(UTF8_BYTES) == UTF8_BYTES + assert round_trip(UNICODE_CHARS) == UNICODE_CHARS + assert round_trip(BINARY_BYTES) == BINARY_BYTES + assert type(round_trip('hello')) is str + assert type(round_trip(u'hello')) is unicode + assert round_trip('') == '' + assert round_trip(u'') == u'' + compound = [UTF8_BYTES, UNICODE_CHARS, BINARY_BYTES, + dict(utf8=UTF8_BYTES, chars=UNICODE_CHARS, data=BINARY_BYTES) + ] + assert round_trip(compound) == tuple(compound) + + +def test_xmlrpc_wrap(): + """ + Test the `ipalib.rpc.xmlrpc_wrap` function. + """ + f = rpc.xmlrpc_wrap + assert f([]) == tuple() + assert f({}) == dict() + b = f('hello') + assert isinstance(b, Binary) + assert b.data == 'hello' + u = f(u'hello') + assert type(u) is unicode + assert u == u'hello' + value = f([dict(one=False, two=u'hello'), None, 'hello']) + + +def test_xmlrpc_unwrap(): + """ + Test the `ipalib.rpc.xmlrpc_unwrap` function. + """ + f = rpc.xmlrpc_unwrap + assert f([]) == tuple() + assert f({}) == dict() + value = f(Binary(UTF8_BYTES)) + assert type(value) is str + assert value == UTF8_BYTES + assert f(UTF8_BYTES) == UNICODE_CHARS + assert f(UNICODE_CHARS) == UNICODE_CHARS + value = f([True, Binary('hello'), dict(one=1, two=UTF8_BYTES, three=None)]) + assert value == (True, 'hello', dict(one=1, two=UNICODE_CHARS, three=None)) + assert type(value[1]) is str + assert type(value[2]['two']) is unicode -- cgit