PageRenderTime 537ms CodeModel.GetById 218ms app.highlight 194ms RepoModel.GetById 2ms app.codeStats 0ms

/emesene/e3/papylib/papyon/papyon/service/SOAPService.py

https://github.com/esmanhotto/emesene
Python | 312 lines | 286 code | 5 blank | 21 comment | 8 complexity | ddd75a52443adb7ebe509a750d20e1ad MD5 | raw file
  1# -*- coding: utf-8 -*-
  2#
  3# papyon - a python client library for Msn
  4#
  5# Copyright (C) 2005-2007 Ali Sabil <ali.sabil@gmail.com>
  6# Copyright (C) 2007 Johann Prieur <johann.prieur@gmail.com>
  7# Copyright (C) 2010 Collabora Ltd.
  8#
  9# This program is free software; you can redistribute it and/or modify
 10# it under the terms of the GNU General Public License as published by
 11# the Free Software Foundation; either version 2 of the License, or
 12# (at your option) any later version.
 13#
 14# This program is distributed in the hope that it will be useful,
 15# but WITHOUT ANY WARRANTY; without even the implied warranty of
 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17# GNU General Public License for more details.
 18#
 19# You should have received a copy of the GNU General Public License
 20# along with this program; if not, write to the Free Software
 21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 22
 23import description
 24from errors import SOAPParseError
 25from SOAPUtils import *
 26from papyon.gnet.errors import HTTPError
 27from papyon.util.async import *
 28
 29import papyon.gnet.protocol
 30import papyon.util.element_tree as ElementTree
 31import papyon.util.string_io as StringIO
 32import re
 33import logging
 34
 35__all__ = ['SOAPService', 'SOAPResponse']
 36
 37logger = logging.getLogger('papyon.service')
 38
 39def url_split(url, default_scheme='http'):
 40    from urlparse import urlsplit, urlunsplit
 41    if "://" not in url: # fix a bug in urlsplit
 42        url = default_scheme + "://" + url
 43    protocol, host, path, query, fragment = urlsplit(url)
 44    if path == "": path = "/"
 45    try:
 46        host, port = host.rsplit(":", 1)
 47        port = int(port)
 48    except:
 49        port = None
 50    resource = urlunsplit(('', '', path, query, fragment))
 51    return protocol, host, port, resource
 52
 53def compress_xml(xml_string):
 54    space_regex = [(re.compile('>\s+<'), '><'),
 55        (re.compile('>\s+'), '>'),
 56        (re.compile('\s+<'), '<')]
 57
 58    for regex, replacement in space_regex:
 59        xml_string = regex.sub(replacement, xml_string)
 60    return xml_string
 61
 62soap_template = """<?xml version='1.0' encoding='utf-8'?>
 63<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
 64    <soap:Header>
 65        %s
 66    </soap:Header>
 67    <soap:Body>
 68        %s
 69    </soap:Body>
 70</soap:Envelope>"""
 71
 72class SOAPFault(object):
 73    def __init__(self, tree):
 74        self.tree = tree
 75        self.faultcode = None
 76        self.faultstring = None
 77        self.faultactor = None
 78        self.detail = None
 79
 80        if tree is not None:
 81            self.faultcode = tree.findtext("./faultcode")
 82            self.faultstring = tree.findtext("./faultstring")
 83            self.faultactor = tree.findtext("./faultactor")
 84            self.detail = tree.find("./detail")
 85
 86    def is_fault(self):
 87        return self.tree is not None
 88
 89    def __repr__(self):
 90        return """	fault code : %s
 91	fault string : %s
 92	fault actor : %s
 93	detail : %s""" % (
 94            self.faultcode, self.faultstring, self.faultactor, self.detail)
 95
 96    def __str__(self):
 97        return self.__repr__()
 98
 99
100class SOAPResponse(ElementTree.XMLResponse):
101    NS_SHORTHANDS = {"soap" : XMLNS.SOAP.ENVELOPE,
102            "xmlenc" : XMLNS.ENCRYPTION.BASE,
103            "wsse" : XMLNS.WS.SECEXT,
104            "wst" : XMLNS.WS.TRUST,
105            "wsa" : XMLNS.WS.ADDRESSING,
106            "wsp" : XMLNS.WS.POLICY,
107            "wsi" : XMLNS.WS.ISSUE,
108            "wsu" : XMLNS.WS.UTILITY,
109            "ps" : XMLNS.MICROSOFT.PASSPORT,
110            "psf" : XMLNS.MICROSOFT.PASSPORT_FAULT,
111            "ab" : XMLNS.MICROSOFT.LIVE.ADDRESSBOOK,
112            "st" : XMLNS.MICROSOFT.LIVE.STORAGE,
113            "stv1" : XMLNS.MICROSOFT.LIVE.STORAGEV1,
114            "oim" : XMLNS.MICROSOFT.LIVE.OIM,
115            "rsi" : XMLNS.MICROSOFT.LIVE.RSI,
116            "spaces" : XMLNS.MICROSOFT.LIVE.SPACES }
117
118    def __init__(self, soap_data):
119        ElementTree.XMLResponse.__init__(self, soap_data, self.NS_SHORTHANDS)
120        try:
121            self.header = self.tree.find("./soap:Header")
122            self.body = self.tree.find("./soap:Body")
123            try:
124                self.fault = SOAPFault(self.body.find("./soap:Fault"))
125            except:
126                self.fault = SOAPFault(self.tree.find("./soap:Fault"))
127        except:
128            raise SOAPParseError("invalid xml+soap data", soap_data)
129
130        if not self.is_valid():
131            raise SOAPParseError("no header, fault or body", soap_data)
132
133    def is_fault(self):
134        return self.fault.is_fault()
135
136    def is_valid(self):
137        return ((self.header is not None) or \
138                (self.fault is not None) or \
139                (self.body is not None)) \
140            and self.tree is not None
141
142    def _parse(self, data):
143        events = ("start", "end", "start-ns", "end-ns")
144        ns = []
145        data = StringIO.StringIO(data)
146        context = ElementTree.iterparse(data, events=events)
147        for event, elem in context:
148            if event == "start-ns":
149                ns.append(elem)
150            elif event == "end-ns":
151                ns.pop()
152            elif event == "start":
153                elem.set("(xmlns)", tuple(ns))
154        data.close()
155        return context.root
156
157class SOAPService(object):
158
159    def __init__(self, name, proxies=None):
160        self._name = name
161        self._service = getattr(description, self._name)
162        self._active_transports = {}
163        self._proxies = proxies or {}
164
165        # Regex to find password
166        self.password_regex = re.compile("<wsse:Password>.*?</wsse:Password>", re.S)
167
168    def _send_request(self, name, url, soap_header, soap_body, soap_action,
169            callback, errback=None, transport_headers={}, user_data=None):
170
171        scheme, host, port, resource = url_split(url)
172        http_headers = transport_headers.copy()
173        if soap_action is not None:
174            http_headers["SOAPAction"] = str(soap_action)
175        http_headers["Content-Type"] = "text/xml; charset=utf-8"
176        http_headers["Cache-Control"] = "no-cache"
177        if "Accept" not in http_headers:
178            http_headers["Accept"] = "text/*"
179        http_headers["Proxy-Connection"] = "Keep-Alive"
180        http_headers["Connection"] = "Keep-Alive"
181        http_headers["Accept-Encoding"] = "gzip"
182
183        request = compress_xml(soap_template % (soap_header, soap_body))
184
185        transport = self._get_transport(name, scheme, host, port,
186                callback, errback, user_data)
187        transport.request(resource, http_headers, request, 'POST')
188
189    def _soap_request(self, method, header_args, body_args, callback, errback,
190            user_data=None):
191        http_headers = method.transport_headers()
192        soap_action = method.soap_action()
193
194        soap_header = method.soap_header(*header_args)
195        soap_body = method.soap_body(*body_args)
196
197        method_name = method.__name__.rsplit(".", 1)[1]
198        self._send_request(method_name, self._service.url, soap_header,
199                soap_body, soap_action, callback, errback, http_headers,
200                user_data)
201
202    def _response_handler(self, transport, http_response):
203        request = self._unref_transport(transport)
204        if request is None:
205            logger.warning("No active request for HTTP response received")
206            return
207        request_id, callback, errback, user_data = request
208        method = getattr(self._service, request_id)
209
210        # decode, build and process SOAP response
211        try:
212            logger.debug("<<< Received response for %s" % request_id)
213            decoded_body = http_response.decode_body()
214            logger.debug("<<<" + unicode(decoded_body, "utf-8"))
215            soap_response = SOAPResponse(decoded_body)
216            if not soap_response.is_fault():
217                response = method.process_response(soap_response)
218                if not response:
219                    raise SOAPParseError("response wasn't found", decoded_body)
220        except Exception, err:
221            logger.exception(err)
222            logger.error("Couldn't build or process SOAP response")
223            run(errback, err)
224            return
225
226        # handle SOAP response or fault
227        if not soap_response.is_fault():
228            handler = getattr(self, "_Handle" + request_id + "Response", None)
229            if handler is not None:
230                handler(callback, errback, response, user_data)
231            else:
232                self._HandleSOAPResponse(request_id, callback, errback,
233                        response, user_data)
234        else:
235            handler = getattr(self, "_Handle" + request_id + "Fault", None)
236            if handler is not None:
237                if handler(callback, errback, soap_response, user_data):
238                    return # if handler returns true, don't call generic handler
239            self._HandleSOAPFault(request_id, callback, errback, soap_response,
240                                  user_data)
241
242    def _request_handler(self, transport, http_request):
243        #hide password from logs
244        cleaned = self.password_regex.sub("<wsse:Password>*****</wsse:Password>", unicode(http_request))
245
246        logger.debug(">>> " + unicode(cleaned))
247
248    def _error_handler(self, transport, error):
249        logger.warning("Transport Error : " + str(error))
250
251        # try to process response if we received an HTTP error (status != 2xx)
252        if isinstance(error, HTTPError):
253            self._response_handler(transport, error.response)
254            return
255
256        # transport probably died, dispose all requests on it
257        for request in self._dispose_transport(transport):
258            request_id, callback, errback, user_data = request
259            run(errback, error)
260
261    # Handlers
262    def _HandleSOAPFault(self, request_id, callback, errback,
263            soap_response, user_data):
264        logger.warning("Unhandled SOAPFault to %s" % request_id)
265
266    def _HandleSOAPResponse(self, request_id, callback, errback,
267            response, user_data):
268        logger.warning("Unhandled Response to %s" % request_id)
269
270    # Transport management
271    def _get_transport(self, request_id, scheme, host, port,
272            callback, errback, user_data):
273        key = (scheme, host, port)
274        if key in self._active_transports:
275            trans = self._active_transports[key]
276            transport = trans[0]
277            trans[1].append((request_id, callback, errback, user_data))
278        else:
279            transport = papyon.gnet.protocol.ProtocolFactory(scheme,
280                    host, port, proxies=self._proxies)
281            handler_id = [
282                transport.connect("response-received", self._response_handler),
283                transport.connect("request-sent", self._request_handler),
284                transport.connect("error", self._error_handler)]
285
286            trans = [transport, [(request_id, callback, errback, user_data)], handler_id]
287            self._active_transports[key] = trans
288        return transport
289
290    def _unref_transport(self, transport):
291        for key, trans in self._active_transports.iteritems():
292            if trans[0] == transport:
293                response = trans[1].pop(0)
294
295                if len(trans[1]) != 0:
296                    return response
297
298                for handle in trans[2]:
299                    transport.disconnect(handle)
300                del self._active_transports[key]
301                return response
302        return None
303
304    def _dispose_transport(self, transport):
305        for key, trans in self._active_transports.iteritems():
306            if trans[0] == transport:
307                response = trans[1]
308                for handle in trans[2]:
309                    transport.disconnect(handle)
310                del self._active_transports[key]
311                return response
312        return []