PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://github.com/RazvanB/emesene
Python | 312 lines | 286 code | 5 blank | 21 comment | 6 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. import description
  23. from errors import SOAPParseError
  24. from SOAPUtils import *
  25. from papyon.gnet.errors import HTTPError
  26. from papyon.util.async import *
  27. import papyon.gnet.protocol
  28. import papyon.util.element_tree as ElementTree
  29. import papyon.util.string_io as StringIO
  30. import re
  31. import logging
  32. __all__ = ['SOAPService', 'SOAPResponse']
  33. logger = logging.getLogger('papyon.service')
  34. def url_split(url, default_scheme='http'):
  35. from urlparse import urlsplit, urlunsplit
  36. if "://" not in url: # fix a bug in urlsplit
  37. url = default_scheme + "://" + url
  38. protocol, host, path, query, fragment = urlsplit(url)
  39. if path == "": path = "/"
  40. try:
  41. host, port = host.rsplit(":", 1)
  42. port = int(port)
  43. except:
  44. port = None
  45. resource = urlunsplit(('', '', path, query, fragment))
  46. return protocol, host, port, resource
  47. def compress_xml(xml_string):
  48. space_regex = [(re.compile('>\s+<'), '><'),
  49. (re.compile('>\s+'), '>'),
  50. (re.compile('\s+<'), '<')]
  51. for regex, replacement in space_regex:
  52. xml_string = regex.sub(replacement, xml_string)
  53. return xml_string
  54. soap_template = """<?xml version='1.0' encoding='utf-8'?>
  55. <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/">
  56. <soap:Header>
  57. %s
  58. </soap:Header>
  59. <soap:Body>
  60. %s
  61. </soap:Body>
  62. </soap:Envelope>"""
  63. class SOAPFault(object):
  64. def __init__(self, tree):
  65. self.tree = tree
  66. self.faultcode = None
  67. self.faultstring = None
  68. self.faultactor = None
  69. self.detail = None
  70. if tree is not None:
  71. self.faultcode = tree.findtext("./faultcode")
  72. self.faultstring = tree.findtext("./faultstring")
  73. self.faultactor = tree.findtext("./faultactor")
  74. self.detail = tree.find("./detail")
  75. def is_fault(self):
  76. return self.tree is not None
  77. def __repr__(self):
  78. return """ fault code : %s
  79. fault string : %s
  80. fault actor : %s
  81. detail : %s""" % (
  82. self.faultcode, self.faultstring, self.faultactor, self.detail)
  83. def __str__(self):
  84. return self.__repr__()
  85. class SOAPResponse(ElementTree.XMLResponse):
  86. NS_SHORTHANDS = {"soap" : XMLNS.SOAP.ENVELOPE,
  87. "xmlenc" : XMLNS.ENCRYPTION.BASE,
  88. "wsse" : XMLNS.WS.SECEXT,
  89. "wst" : XMLNS.WS.TRUST,
  90. "wsa" : XMLNS.WS.ADDRESSING,
  91. "wsp" : XMLNS.WS.POLICY,
  92. "wsi" : XMLNS.WS.ISSUE,
  93. "wsu" : XMLNS.WS.UTILITY,
  94. "ps" : XMLNS.MICROSOFT.PASSPORT,
  95. "psf" : XMLNS.MICROSOFT.PASSPORT_FAULT,
  96. "ab" : XMLNS.MICROSOFT.LIVE.ADDRESSBOOK,
  97. "st" : XMLNS.MICROSOFT.LIVE.STORAGE,
  98. "stv1" : XMLNS.MICROSOFT.LIVE.STORAGEV1,
  99. "oim" : XMLNS.MICROSOFT.LIVE.OIM,
  100. "rsi" : XMLNS.MICROSOFT.LIVE.RSI,
  101. "spaces" : XMLNS.MICROSOFT.LIVE.SPACES }
  102. def __init__(self, soap_data):
  103. ElementTree.XMLResponse.__init__(self, soap_data, self.NS_SHORTHANDS)
  104. try:
  105. self.header = self.tree.find("./soap:Header")
  106. self.body = self.tree.find("./soap:Body")
  107. try:
  108. self.fault = SOAPFault(self.body.find("./soap:Fault"))
  109. except:
  110. self.fault = SOAPFault(self.tree.find("./soap:Fault"))
  111. except:
  112. raise SOAPParseError("invalid xml+soap data", soap_data)
  113. if not self.is_valid():
  114. raise SOAPParseError("no header, fault or body", soap_data)
  115. def is_fault(self):
  116. return self.fault.is_fault()
  117. def is_valid(self):
  118. return ((self.header is not None) or \
  119. (self.fault is not None) or \
  120. (self.body is not None)) \
  121. and self.tree is not None
  122. def _parse(self, data):
  123. events = ("start", "end", "start-ns", "end-ns")
  124. ns = []
  125. data = StringIO.StringIO(data)
  126. context = ElementTree.iterparse(data, events=events)
  127. for event, elem in context:
  128. if event == "start-ns":
  129. ns.append(elem)
  130. elif event == "end-ns":
  131. ns.pop()
  132. elif event == "start":
  133. elem.set("(xmlns)", tuple(ns))
  134. data.close()
  135. return context.root
  136. class SOAPService(object):
  137. def __init__(self, name, proxies=None):
  138. self._name = name
  139. self._service = getattr(description, self._name)
  140. self._active_transports = {}
  141. self._proxies = proxies or {}
  142. # Regex to find password
  143. self.password_regex = re.compile("<wsse:Password>.*?</wsse:Password>", re.S)
  144. def _send_request(self, name, url, soap_header, soap_body, soap_action,
  145. callback, errback=None, transport_headers={}, user_data=None):
  146. scheme, host, port, resource = url_split(url)
  147. http_headers = transport_headers.copy()
  148. if soap_action is not None:
  149. http_headers["SOAPAction"] = str(soap_action)
  150. http_headers["Content-Type"] = "text/xml; charset=utf-8"
  151. http_headers["Cache-Control"] = "no-cache"
  152. if "Accept" not in http_headers:
  153. http_headers["Accept"] = "text/*"
  154. http_headers["Proxy-Connection"] = "Keep-Alive"
  155. http_headers["Connection"] = "Keep-Alive"
  156. http_headers["Accept-Encoding"] = "gzip"
  157. request = compress_xml(soap_template % (soap_header, soap_body))
  158. transport = self._get_transport(name, scheme, host, port,
  159. callback, errback, user_data)
  160. transport.request(resource, http_headers, request, 'POST')
  161. def _soap_request(self, method, header_args, body_args, callback, errback,
  162. user_data=None):
  163. http_headers = method.transport_headers()
  164. soap_action = method.soap_action()
  165. soap_header = method.soap_header(*header_args)
  166. soap_body = method.soap_body(*body_args)
  167. method_name = method.__name__.rsplit(".", 1)[1]
  168. self._send_request(method_name, self._service.url, soap_header,
  169. soap_body, soap_action, callback, errback, http_headers,
  170. user_data)
  171. def _response_handler(self, transport, http_response):
  172. request = self._unref_transport(transport)
  173. if request is None:
  174. logger.warning("No active request for HTTP response received")
  175. return
  176. request_id, callback, errback, user_data = request
  177. method = getattr(self._service, request_id)
  178. # decode, build and process SOAP response
  179. try:
  180. logger.debug("<<< Received response for %s" % request_id)
  181. decoded_body = http_response.decode_body()
  182. logger.debug("<<<" + unicode(decoded_body, "utf-8"))
  183. soap_response = SOAPResponse(decoded_body)
  184. if not soap_response.is_fault():
  185. response = method.process_response(soap_response)
  186. if not response:
  187. raise SOAPParseError("response wasn't found", decoded_body)
  188. except Exception, err:
  189. logger.exception(err)
  190. logger.error("Couldn't build or process SOAP response")
  191. run(errback, err)
  192. return
  193. # handle SOAP response or fault
  194. if not soap_response.is_fault():
  195. handler = getattr(self, "_Handle" + request_id + "Response", None)
  196. if handler is not None:
  197. handler(callback, errback, response, user_data)
  198. else:
  199. self._HandleSOAPResponse(request_id, callback, errback,
  200. response, user_data)
  201. else:
  202. handler = getattr(self, "_Handle" + request_id + "Fault", None)
  203. if handler is not None:
  204. if handler(callback, errback, soap_response, user_data):
  205. return # if handler returns true, don't call generic handler
  206. self._HandleSOAPFault(request_id, callback, errback, soap_response,
  207. user_data)
  208. def _request_handler(self, transport, http_request):
  209. #hide password from logs
  210. cleaned = self.password_regex.sub("<wsse:Password>*****</wsse:Password>", unicode(http_request))
  211. logger.debug(">>> " + unicode(cleaned))
  212. def _error_handler(self, transport, error):
  213. logger.warning("Transport Error : " + str(error))
  214. # try to process response if we received an HTTP error (status != 2xx)
  215. if isinstance(error, HTTPError):
  216. self._response_handler(transport, error.response)
  217. return
  218. # transport probably died, dispose all requests on it
  219. for request in self._dispose_transport(transport):
  220. request_id, callback, errback, user_data = request
  221. run(errback, error)
  222. # Handlers
  223. def _HandleSOAPFault(self, request_id, callback, errback,
  224. soap_response, user_data):
  225. logger.warning("Unhandled SOAPFault to %s" % request_id)
  226. def _HandleSOAPResponse(self, request_id, callback, errback,
  227. response, user_data):
  228. logger.warning("Unhandled Response to %s" % request_id)
  229. # Transport management
  230. def _get_transport(self, request_id, scheme, host, port,
  231. callback, errback, user_data):
  232. key = (scheme, host, port)
  233. if key in self._active_transports:
  234. trans = self._active_transports[key]
  235. transport = trans[0]
  236. trans[1].append((request_id, callback, errback, user_data))
  237. else:
  238. transport = papyon.gnet.protocol.ProtocolFactory(scheme,
  239. host, port, proxies=self._proxies)
  240. handler_id = [
  241. transport.connect("response-received", self._response_handler),
  242. transport.connect("request-sent", self._request_handler),
  243. transport.connect("error", self._error_handler)]
  244. trans = [transport, [(request_id, callback, errback, user_data)], handler_id]
  245. self._active_transports[key] = trans
  246. return transport
  247. def _unref_transport(self, transport):
  248. for key, trans in self._active_transports.iteritems():
  249. if trans[0] == transport:
  250. response = trans[1].pop(0)
  251. if len(trans[1]) != 0:
  252. return response
  253. for handle in trans[2]:
  254. transport.disconnect(handle)
  255. del self._active_transports[key]
  256. return response
  257. return None
  258. def _dispose_transport(self, transport):
  259. for key, trans in self._active_transports.iteritems():
  260. if trans[0] == transport:
  261. response = trans[1]
  262. for handle in trans[2]:
  263. transport.disconnect(handle)
  264. del self._active_transports[key]
  265. return response
  266. return []