_handshake.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. """
  2. websocket - WebSocket client library for Python
  3. Copyright (C) 2010 Hiroki Ohtani(liris)
  4. This library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Lesser General Public
  6. License as published by the Free Software Foundation; either
  7. version 2.1 of the License, or (at your option) any later version.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public
  13. License along with this library; if not, write to the Free Software
  14. Foundation, Inc., 51 Franklin Street, Fifth Floor,
  15. Boston, MA 02110-1335 USA
  16. """
  17. import hashlib
  18. import hmac
  19. import os
  20. import six
  21. from ._cookiejar import SimpleCookieJar
  22. from ._exceptions import *
  23. from ._http import *
  24. from ._logging import *
  25. from ._socket import *
  26. if six.PY3:
  27. from base64 import encodebytes as base64encode
  28. else:
  29. from base64 import encodestring as base64encode
  30. __all__ = ["handshake_response", "handshake"]
  31. if hasattr(hmac, "compare_digest"):
  32. compare_digest = hmac.compare_digest
  33. else:
  34. def compare_digest(s1, s2):
  35. return s1 == s2
  36. # websocket supported version.
  37. VERSION = 13
  38. CookieJar = SimpleCookieJar()
  39. class handshake_response(object):
  40. def __init__(self, status, headers, subprotocol):
  41. self.status = status
  42. self.headers = headers
  43. self.subprotocol = subprotocol
  44. CookieJar.add(headers.get("set-cookie"))
  45. def handshake(sock, hostname, port, resource, **options):
  46. headers, key = _get_handshake_headers(resource, hostname, port, options)
  47. header_str = "\r\n".join(headers)
  48. send(sock, header_str)
  49. dump("request header", header_str)
  50. status, resp = _get_resp_headers(sock)
  51. success, subproto = _validate(resp, key, options.get("subprotocols"))
  52. if not success:
  53. raise WebSocketException("Invalid WebSocket Header")
  54. return handshake_response(status, resp, subproto)
  55. def _pack_hostname(hostname):
  56. # IPv6 address
  57. if ':' in hostname:
  58. return '[' + hostname + ']'
  59. return hostname
  60. def _get_handshake_headers(resource, host, port, options):
  61. headers = [
  62. "GET %s HTTP/1.1" % resource,
  63. "Upgrade: websocket",
  64. "Connection: Upgrade"
  65. ]
  66. if port == 80 or port == 443:
  67. hostport = _pack_hostname(host)
  68. else:
  69. hostport = "%s:%d" % (_pack_hostname(host), port)
  70. if "host" in options and options["host"] is not None:
  71. headers.append("Host: %s" % options["host"])
  72. else:
  73. headers.append("Host: %s" % hostport)
  74. if "origin" in options and options["origin"] is not None:
  75. headers.append("Origin: %s" % options["origin"])
  76. else:
  77. headers.append("Origin: http://%s" % hostport)
  78. key = _create_sec_websocket_key()
  79. headers.append("Sec-WebSocket-Key: %s" % key)
  80. headers.append("Sec-WebSocket-Version: %s" % VERSION)
  81. subprotocols = options.get("subprotocols")
  82. if subprotocols:
  83. headers.append("Sec-WebSocket-Protocol: %s" % ",".join(subprotocols))
  84. if "header" in options:
  85. header = options["header"]
  86. if isinstance(header, dict):
  87. header = map(": ".join, header.items())
  88. headers.extend(header)
  89. server_cookie = CookieJar.get(host)
  90. client_cookie = options.get("cookie", None)
  91. cookie = "; ".join(filter(None, [server_cookie, client_cookie]))
  92. if cookie:
  93. headers.append("Cookie: %s" % cookie)
  94. headers.append("")
  95. headers.append("")
  96. return headers, key
  97. def _get_resp_headers(sock, success_status=101):
  98. status, resp_headers, status_message = read_headers(sock)
  99. if status != success_status:
  100. raise WebSocketBadStatusException("Handshake status %d %s", status, status_message)
  101. return status, resp_headers
  102. _HEADERS_TO_CHECK = {
  103. "upgrade": "websocket",
  104. "connection": "upgrade",
  105. }
  106. def _validate(headers, key, subprotocols):
  107. subproto = None
  108. for k, v in _HEADERS_TO_CHECK.items():
  109. r = headers.get(k, None)
  110. if not r:
  111. return False, None
  112. r = r.lower()
  113. if v != r:
  114. return False, None
  115. if subprotocols:
  116. subproto = headers.get("sec-websocket-protocol", None).lower()
  117. if not subproto or subproto not in [s.lower() for s in subprotocols]:
  118. error("Invalid subprotocol: " + str(subprotocols))
  119. return False, None
  120. result = headers.get("sec-websocket-accept", None)
  121. if not result:
  122. return False, None
  123. result = result.lower()
  124. if isinstance(result, six.text_type):
  125. result = result.encode('utf-8')
  126. value = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode('utf-8')
  127. hashed = base64encode(hashlib.sha1(value).digest()).strip().lower()
  128. success = compare_digest(hashed, result)
  129. if success:
  130. return True, subproto
  131. else:
  132. return False, None
  133. def _create_sec_websocket_key():
  134. randomness = os.urandom(16)
  135. return base64encode(randomness).decode('utf-8').strip()