diff -r 16d47bd81d84 -r 3f89629b9e54 src/pyams_utils/protocol/xmlrpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_utils/protocol/xmlrpc.py Thu Feb 19 00:46:48 2015 +0100 @@ -0,0 +1,144 @@ +# +# Copyright (c) 2008-2015 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +__docformat__ = 'restructuredtext' + + +# import standard library +import base64 +import http.client +import http.cookiejar +import socket +import urllib.request +import xmlrpc.client + +# import interfaces + +# import packages + + +class XMLRPCCookieAuthTransport(xmlrpc.client.Transport): + """An XML-RPC transport handling authentication via cookies""" + + _http_connection = http.client.HTTPConnection + + def __init__(self, user_agent, credentials=(), cookies=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None): + xmlrpc.client.Transport.__init__(self) + self.user_agent = user_agent + self.credentials = credentials + self.cookies = cookies + self.timeout = timeout + self.headers = headers + + def make_connection(self, host): + # This is the make_connection that runs under Python 2.7 and newer. + # The code is pulled straight from 2.7 xmlrpclib, except replacing + # HTTPConnection with self._http_connection + if self._connection and host == self._connection[0]: + return self._connection[1] + chost, self._extra_headers, _x509 = self.get_host_info(host) + self._connection = host, self._http_connection(chost, timeout=self.timeout) + return self._connection[1] + + # override the send_host hook to also send authentication info + def send_host(self, connection, host): + connection.putheader('Host', host) + if (self.cookies is not None) and (len(self.cookies) > 0): + for cookie in self.cookies: + connection.putheader('Cookie', '%s=%s' % (cookie.name, cookie.value)) + elif self.credentials: + auth = 'Basic %s' % base64.encodebytes("%s:%s" % self.credentials).strip() + connection.putheader('Authorization', auth) + + # send user agent + def send_user_agent(self, connection): + connection.putheader('User-Agent', self.user_agent) + + # send custom headers + def send_headers(self, connection, headers): + xmlrpc.client.Transport.send_headers(self, connection, headers) + for k, v in (self.headers or {}).iteritems(): + connection.putheader(k, v) + + # dummy request class for extracting cookies + class CookieRequest(urllib.request.Request): + pass + + # dummy response info headers helper + class CookieResponseHelper: + def __init__(self, response): + self.response = response + def getheaders(self, header): + return self.response.msg.getallmatchingheaders(header) + + # dummy response class for extracting cookies + class CookieResponse: + def __init__(self, response): + self.response = response + def info(self): + return XMLRPCCookieAuthTransport.CookieResponseHelper(self.response) + + def request(self, host, handler, request_body, verbose=False): + # issue XML-RPC request + connection = self.make_connection(host) + self.verbose = verbose + if verbose: + connection.set_debuglevel(1) + self.send_request(connection, handler, request_body) + self.send_host(connection, host) + self.send_user_agent(connection) + self.send_headers(connection) + self.send_content(connection, request_body) + # get response + return self.get_response(connection, host, handler) + + def get_response(self, connection, host, handler): + response = connection.getresponse() + # extract cookies from response headers + if self.cookies is not None: + crequest = XMLRPCCookieAuthTransport.CookieRequest('http://%s/' % host) + cresponse = XMLRPCCookieAuthTransport.CookieResponse(response) + self.cookies.extract_cookies(cresponse, crequest) + if response.status != 200: + raise xmlrpc.client.ProtocolError(host + handler, response.status, response.reason, response.getheaders()) + return self.parse_response(response) + + +class SecureXMLRPCCookieAuthTransport(XMLRPCCookieAuthTransport): + """Secure XML-RPC transport""" + + _http_connection = http.client.HTTPSConnection + + +def get_client(uri, credentials=(), verbose=False, allow_none=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, headers=None): + """Get an XML-RPC client which supports basic authentication""" + if uri.startswith('https:'): + transport = SecureXMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS secure transport)', credentials, + timeout=timeout, headers=headers) + else: + transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS basic transport)', credentials, + timeout=timeout, headers=headers) + return xmlrpc.client.Server(uri, transport=transport, verbose=verbose, allow_none=allow_none) + + +def get_client_with_cookies(uri, credentials=(), verbose=False, allow_none=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + headers=None, cookies=None): + """Get an XML-RPC client which supports authentication through cookies""" + if cookies is None: + cookies = http.cookiejar.CookieJar() + if uri.startswith('https:'): + transport = SecureXMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS secure cookie transport)', + credentials, cookies, timeout, headers) + else: + transport = XMLRPCCookieAuthTransport('Python XML-RPC Client/0.1 (PyAMS basic cookie transport)', + credentials, cookies, timeout, headers) + return xmlrpc.client.Server(uri, transport=transport, verbose=verbose, allow_none=allow_none)