PageRenderTime 22ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/pulsar/utils/internet.py

https://github.com/pombredanne/pulsar
Python | 289 lines | 272 code | 10 blank | 7 comment | 18 complexity | 8ded6fddab819b07e19e30d9c70d3ec8 MD5 | raw file
  1. import sys
  2. import os
  3. import socket
  4. from functools import partial
  5. try:
  6. from select import poll, POLLIN
  7. except ImportError: # pragma nocover
  8. poll = None
  9. try:
  10. from select import select
  11. except ImportError: # pragma nocover
  12. select = False
  13. try:
  14. _SSLContext = None
  15. HAS_SNI = False
  16. ssl = None
  17. CERT_NONE = 0
  18. import ssl
  19. from ssl import wrap_socket, PROTOCOL_SSLv23, CERT_NONE
  20. from ssl import SSLContext as _SSLContext
  21. from ssl import HAS_SNI # Has SNI?
  22. except ImportError: # pragma: no cover
  23. pass
  24. from .system import platform, socketpair
  25. from .httpurl import urlsplit, parse_qsl, urlencode
  26. from .pep import native_str, ispy3k
  27. from .exceptions import SSLError
  28. BUFFER_MAX_SIZE = 256 * 1024 # 256 kb
  29. if platform.is_windows: # pragma nocover
  30. EPERM = object()
  31. from errno import WSAEINVAL as EINVAL
  32. from errno import WSAEWOULDBLOCK as EWOULDBLOCK
  33. from errno import WSAEINPROGRESS as EINPROGRESS
  34. from errno import WSAEALREADY as EALREADY
  35. from errno import WSAECONNRESET as ECONNRESET
  36. from errno import WSAEISCONN as EISCONN
  37. from errno import WSAENOTCONN as ENOTCONN
  38. from errno import WSAEINTR as EINTR
  39. from errno import WSAENOBUFS as ENOBUFS
  40. from errno import WSAEMFILE as EMFILE
  41. from errno import WSAECONNRESET as ECONNABORTED
  42. from errno import WSAEADDRINUSE as EADDRINUSE
  43. from errno import WSAEMSGSIZE as EMSGSIZE
  44. from errno import WSAENETRESET as ENETRESET
  45. from errno import WSAETIMEDOUT as ETIMEDOUT
  46. from errno import WSAECONNREFUSED as ECONNREFUSED
  47. from errno import WSAESHUTDOWN as ESHUTDOWN
  48. # No such thing as WSAENFILE, either.
  49. ENFILE = object()
  50. # Nor ENOMEM
  51. ENOMEM = object()
  52. EAGAIN = EWOULDBLOCK
  53. else:
  54. from errno import (EPERM, EINVAL, EWOULDBLOCK, EINPROGRESS, EALREADY,
  55. ECONNRESET, EISCONN, ENOTCONN, EINTR, ENOBUFS, EMFILE,
  56. ENFILE, ENOMEM, EAGAIN, ECONNABORTED, EADDRINUSE,
  57. EMSGSIZE, ENETRESET, ETIMEDOUT, ECONNREFUSED, ESHUTDOWN)
  58. ACCEPT_ERRORS = (EMFILE, ENOBUFS, ENFILE, ENOMEM, ECONNABORTED)
  59. TRY_WRITE_AGAIN = (EWOULDBLOCK, ENOBUFS, EINPROGRESS)
  60. TRY_READ_AGAIN = (EWOULDBLOCK, EAGAIN)
  61. SOCKET_INTERRUPT_ERRORS = (EINTR, ECONNRESET)
  62. def parse_address(netloc, default_port=8000):
  63. '''Parse an internet address ``netloc`` and return a tuple with
  64. ``host`` and ``port``.'''
  65. if isinstance(netloc, tuple):
  66. if len(netloc) != 2:
  67. raise ValueError('Invalid address %s' % str(netloc))
  68. return netloc
  69. #
  70. netloc = native_str(netloc)
  71. auth = None
  72. # Check if auth is available
  73. if '@' in netloc:
  74. auth, netloc = netloc.split('@')
  75. if netloc.startswith("unix:"):
  76. host = netloc.split("unix:")[1]
  77. return '%s@%s' % (auth, host) if auth else host
  78. # get host
  79. if '[' in netloc and ']' in netloc:
  80. host = netloc.split(']')[0][1:].lower()
  81. elif ':' in netloc:
  82. host = netloc.split(':')[0].lower()
  83. elif netloc == "":
  84. host = "0.0.0.0"
  85. else:
  86. host = netloc.lower()
  87. #get port
  88. netloc = netloc.split(']')[-1]
  89. if ":" in netloc:
  90. port = netloc.split(':', 1)[1]
  91. if not port.isdigit():
  92. raise ValueError("%r is not a valid port number." % port)
  93. port = int(port)
  94. else:
  95. port = default_port
  96. return ('%s@%s' % (auth, host) if auth else host, port)
  97. def parse_connection_string(connection_string, default_port=8000):
  98. """Converts the ``connection_string`` into a three elements tuple
  99. ``(scheme, host, params)`` where ``scheme`` is a string, ``host`` could
  100. be a string or a two elements tuple (for a tcp address) and ``params`` a
  101. dictionary of parameters. The ``default_port`` parameter can be used to
  102. set the port if a port is not available in the ``connection_string``.
  103. For example::
  104. >>> parse_connection_string('http://127.0.0.1:9080')
  105. ('http', ('127.0.0.1', 9080), {})
  106. and this example::
  107. >>> parse_connection_string('redis://127.0.0.1:6379?db=3&password=bla')
  108. ('redis', ('127.0.0.1', 6379), {'db': '3', 'password': 'bla'})
  109. """
  110. if '://' not in connection_string:
  111. connection_string = 'dummy://%s' % connection_string
  112. scheme, host, path, query, fragment = urlsplit(connection_string)
  113. if not scheme and not host:
  114. host, path = path, ''
  115. elif path and not query:
  116. query, path = path, ''
  117. if query:
  118. if query.find('?'):
  119. path = query
  120. else:
  121. query = query[1:]
  122. if path:
  123. raise ValueError("Address must not have a path. Found '%s'" % path)
  124. if query:
  125. params = dict(parse_qsl(query))
  126. else:
  127. params = {}
  128. if scheme == 'dummy':
  129. scheme = ''
  130. return scheme, parse_address(host, default_port), params
  131. def get_connection_string(scheme, address, params):
  132. address = ':'.join((str(b) for b in address))
  133. if params:
  134. address += '?' + urlencode(params)
  135. return scheme + '://' + address
  136. def is_socket_closed(sock):
  137. """Check if socket ``sock`` is closed."""
  138. if not sock:
  139. return True
  140. try:
  141. if not poll: # pragma nocover
  142. if not select:
  143. return False
  144. try:
  145. return bool(select([sock], [], [], 0.0)[0])
  146. except socket.error:
  147. return True
  148. # This version is better on platforms that support it.
  149. p = poll()
  150. p.register(sock, POLLIN)
  151. for (fno, ev) in p.poll(0.0):
  152. if fno == sock.fileno():
  153. # Either data is buffered (bad), or the connection is dropped.
  154. return True
  155. except Exception:
  156. return True
  157. def close_socket(sock):
  158. '''Shutdown and close the socket.'''
  159. if sock:
  160. try:
  161. sock.shutdown(socket.SHUT_RDWR)
  162. except Exception:
  163. pass
  164. try:
  165. sock.close()
  166. except Exception:
  167. pass
  168. def nice_address(address, family=None):
  169. if isinstance(address, tuple):
  170. address = ':'.join((str(s) for s in address[:2]))
  171. return '%s %s' % (family, address) if family else address
  172. def format_address(address):
  173. if isinstance(address, tuple):
  174. if len(address) == 2:
  175. return '%s:%s' % address
  176. elif len(address) == 4:
  177. return '[%s]:%s' % address[:2]
  178. else:
  179. raise ValueError('Could not format address %s' % str(address))
  180. else:
  181. return str(address)
  182. class WrapSocket: # pragma nocover
  183. def __init__(self, sock):
  184. self.sock = sock
  185. def __getstate__(self):
  186. s = self.sock
  187. return {'sock': (s.fileno(), s.family, s.type, s.proto)}
  188. def __setstate__(self, state):
  189. values = state.pop('sock')
  190. self.sock = socket.fromfd(*values)
  191. class SSLContext:
  192. def __init__(self, keyfile=None, certfile=None, cert_reqs=CERT_NONE,
  193. ca_certs=None, server_hostname=None,
  194. protocol=PROTOCOL_SSLv23):
  195. self.keyfile = keyfile
  196. self.certfile = certfile
  197. self.cert_reqs = cert_reqs
  198. self.ca_certs = ca_certs
  199. self.server_hostname = server_hostname
  200. self.protocol = protocol
  201. def wrap_socket(self, sock, server_side=False,
  202. do_handshake_on_connect=True,
  203. suppress_ragged_eofs=True, server_hostname=None):
  204. if not ssl:
  205. raise NotImplementedError
  206. server_hostname = self.server_hostname or server_hostname
  207. if not HAS_SNI: # pragma nocover
  208. server_hostname = None
  209. if _SSLContext:
  210. wrap = self._wrap3k(sock, server_hostname)
  211. else: # pragma nocover
  212. wrap = partial(wrap_socket, sock, keyfile=self.keyfile,
  213. certfile=self.certfile, ca_certs=self.ca_certs,
  214. cert_reqs=self.cert_reqs, ssl_version=self.protocol)
  215. return wrap(server_side=server_side,
  216. do_handshake_on_connect=do_handshake_on_connect,
  217. suppress_ragged_eofs=suppress_ragged_eofs)
  218. def _wrap3k(self, sock, server_hostname):
  219. context = _SSLContext(self.protocol)
  220. if self.cert_reqs:
  221. context.verify_mode = self.cert_reqs
  222. if self.ca_certs:
  223. try:
  224. context.load_verify_locations(self.ca_certs)
  225. # Py32 raises IOError
  226. # Py33 raises FileNotFoundError
  227. except Exception as e: # Re-raise as SSLError
  228. raise SSLError(e)
  229. if self.certfile:
  230. # FIXME: This block needs a test.
  231. context.load_cert_chain(self.certfile, self.keyfile)
  232. if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
  233. return partial(context.wrap_socket, sock,
  234. server_hostname=server_hostname)
  235. else: # pragma nocover
  236. return partial(context.wrap_socket, sock)
  237. def is_tls(sock):
  238. return hasattr(sock, 'keyfile')
  239. def ssl_context(context, server_side=False):
  240. if not ssl:
  241. raise NotImplementedError
  242. if server_side:
  243. assert isinstance(
  244. context, SSLContext), 'Must pass an SSLContext'
  245. else:
  246. # Client-side may pass ssl=True to use a default context.
  247. context = context or SSLContext()
  248. return context