autobahn 0.3.1__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Python27/Lib/site-packages/autobahn/__init__.py +22 -0
- Python27/Lib/site-packages/autobahn/__init__.pyc +0 -0
- Python27/Lib/site-packages/autobahn/autobahn.py +76 -0
- Python27/Lib/site-packages/autobahn/autobahn.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/__init__.py +207 -0
- Python27/Lib/site-packages/autobahn/case/__init__.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case.py +68 -0
- Python27/Lib/site-packages/autobahn/case/case.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_1.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_2.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_3.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_4.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_5.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_6.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_7.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_8.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_1_8.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_1.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_2.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_3.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_4.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_5.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_6.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_7.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_8.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case1_2_8.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_1.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case2_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_10.py +37 -0
- Python27/Lib/site-packages/autobahn/case/case2_10.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_11.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case2_11.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_2.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case2_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_3.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case2_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_4.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case2_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_5.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case2_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_6.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case2_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_7.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case2_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_8.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case2_8.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case2_9.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case2_9.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_1.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case3_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_2.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case3_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_3.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case3_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_4.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case3_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_5.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case3_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_6.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case3_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case3_7.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case3_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_1.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_2.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_3.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_4.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_5.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case4_1_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_1.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_2.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_3.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_4.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_5.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case4_2_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_1.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_10.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_10.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_11.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_11.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_12.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_12.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_13.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_13.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_14.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_14.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_15.py +35 -0
- Python27/Lib/site-packages/autobahn/case/case5_15.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_16.py +33 -0
- Python27/Lib/site-packages/autobahn/case/case5_16.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_17.py +33 -0
- Python27/Lib/site-packages/autobahn/case/case5_17.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_18.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_18.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_19.py +56 -0
- Python27/Lib/site-packages/autobahn/case/case5_19.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_2.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_20.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_20.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_3.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case5_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_4.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case5_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_5.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case5_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_6.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case5_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_7.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case5_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_8.py +34 -0
- Python27/Lib/site-packages/autobahn/case/case5_8.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case5_9.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case5_9.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case6_1.py +30 -0
- Python27/Lib/site-packages/autobahn/case/case6_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case6_2.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case6_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case6_3.py +33 -0
- Python27/Lib/site-packages/autobahn/case/case6_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_1.py +54 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_2.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_3.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_4.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_5.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_6.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_1_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_1.py +54 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_2.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_3.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_4.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_5.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_6.py +31 -0
- Python27/Lib/site-packages/autobahn/case/case9_2_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_1.py +55 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_2.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_3.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_4.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_5.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_6.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_7.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_8.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_8.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_9.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_3_9.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_1.py +55 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_2.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_3.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_4.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_5.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_6.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_7.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_7.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_8.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_8.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_9.py +32 -0
- Python27/Lib/site-packages/autobahn/case/case9_4_9.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_1.py +58 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_2.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_3.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_4.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_5.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_6.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_5_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_1.py +58 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_1.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_2.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_2.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_3.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_3.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_4.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_4.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_5.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_5.pyc +0 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_6.py +28 -0
- Python27/Lib/site-packages/autobahn/case/case9_6_6.pyc +0 -0
- Python27/Lib/site-packages/autobahn/fuzzing.py +911 -0
- Python27/Lib/site-packages/autobahn/fuzzing.pyc +0 -0
- Python27/Lib/site-packages/autobahn/websocket.py +1505 -0
- Python27/Lib/site-packages/autobahn/websocket.pyc +0 -0
- Python27/Lib/site-packages/autobahn-0.3.1-py2.7.egg-info/PKG-INFO +10 -0
- Python27/Lib/site-packages/autobahn-0.3.1-py2.7.egg-info/SOURCES.txt +124 -0
- Python27/Lib/site-packages/autobahn-0.3.1-py2.7.egg-info/dependency_links.txt +1 -0
- Python27/Lib/site-packages/autobahn-0.3.1-py2.7.egg-info/not-zip-safe +1 -0
- Python27/Lib/site-packages/autobahn-0.3.1-py2.7.egg-info/requires.txt +1 -0
- Python27/Lib/site-packages/autobahn-0.3.1-py2.7.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,1505 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
##
|
|
3
|
+
## Copyright 2011 Tavendo GmbH
|
|
4
|
+
##
|
|
5
|
+
## Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
## you may not use this file except in compliance with the License.
|
|
7
|
+
## You may obtain a copy of the License at
|
|
8
|
+
##
|
|
9
|
+
## http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
##
|
|
11
|
+
## Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
## distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
## See the License for the specific language governing permissions and
|
|
15
|
+
## limitations under the License.
|
|
16
|
+
##
|
|
17
|
+
###############################################################################
|
|
18
|
+
|
|
19
|
+
from twisted.internet import reactor, protocol
|
|
20
|
+
from twisted.python import log
|
|
21
|
+
import binascii
|
|
22
|
+
import hashlib
|
|
23
|
+
import base64
|
|
24
|
+
import struct
|
|
25
|
+
import random
|
|
26
|
+
import urlparse
|
|
27
|
+
import os
|
|
28
|
+
import cStringIO
|
|
29
|
+
import StringIO
|
|
30
|
+
from io import BytesIO
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FrameHeader:
|
|
34
|
+
|
|
35
|
+
def __init__(self, opcode, fin, rsv, length, mask, mask_array):
|
|
36
|
+
self.opcode = opcode
|
|
37
|
+
self.fin = fin
|
|
38
|
+
self.rsv = rsv
|
|
39
|
+
self.length = length
|
|
40
|
+
self.ptr = 0
|
|
41
|
+
self.mask = mask
|
|
42
|
+
self.mask_array = mask_array
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class WebSocketProtocol(protocol.Protocol):
|
|
46
|
+
"""
|
|
47
|
+
A Twisted Protocol class for WebSockets. This class is used by both WebSocket
|
|
48
|
+
client and server protocol version. It is unusable standalone, for example
|
|
49
|
+
the WebSockets initial handshake is implemented in derived class differently
|
|
50
|
+
for clients and servers.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
## magic used during WebSocket handshake
|
|
54
|
+
##
|
|
55
|
+
WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
56
|
+
|
|
57
|
+
MESSAGE_TYPE_TEXT = 1
|
|
58
|
+
"""WebSockets text message type (UTF-8 payload)."""
|
|
59
|
+
|
|
60
|
+
MESSAGE_TYPE_BINARY = 2
|
|
61
|
+
"""WebSockets binary message type (arbitrary binary payload)."""
|
|
62
|
+
|
|
63
|
+
## WebSockets protocol state
|
|
64
|
+
##
|
|
65
|
+
STATE_CLOSED = 0
|
|
66
|
+
STATE_CONNECTING = 1
|
|
67
|
+
STATE_CLOSING = 2
|
|
68
|
+
STATE_OPEN = 3
|
|
69
|
+
|
|
70
|
+
## Streaming Send State
|
|
71
|
+
SEND_STATE_GROUND = 0
|
|
72
|
+
SEND_STATE_MESSAGE_BEGIN = 1
|
|
73
|
+
SEND_STATE_INSIDE_MESSAGE = 2
|
|
74
|
+
SEND_STATE_INSIDE_MESSAGE_FRAME = 3
|
|
75
|
+
|
|
76
|
+
## WebSockets protocol close codes
|
|
77
|
+
##
|
|
78
|
+
CLOSE_STATUS_CODE_NORMAL = 1000
|
|
79
|
+
"""Normal close of connection."""
|
|
80
|
+
|
|
81
|
+
CLOSE_STATUS_CODE_GOING_AWAY = 1001
|
|
82
|
+
"""Going away."""
|
|
83
|
+
|
|
84
|
+
CLOSE_STATUS_CODE_PROTOCOL_ERROR = 1002
|
|
85
|
+
"""Protocol error."""
|
|
86
|
+
|
|
87
|
+
CLOSE_STATUS_CODE_PAYLOAD_NOT_ACCEPTED = 1003
|
|
88
|
+
"""Payload not accepted."""
|
|
89
|
+
|
|
90
|
+
CLOSE_STATUS_CODE_FRAME_TOO_LARGE = 1004
|
|
91
|
+
"""Frame too large."""
|
|
92
|
+
|
|
93
|
+
CLOSE_STATUS_CODE_NULL = 1005 # MUST NOT be set in close frame!
|
|
94
|
+
"""No status received. (MUST NOT be used as status code when sending a close)."""
|
|
95
|
+
|
|
96
|
+
CLOSE_STATUS_CODE_CONNECTION_LOST = 1006 # MUST NOT be set in close frame!
|
|
97
|
+
"""Abnormal close of connection. (MUST NOT be used as status code when sending a close)."""
|
|
98
|
+
|
|
99
|
+
CLOSE_STATUS_CODE_TEXT_FRAME_NOT_UTF8 = 1007
|
|
100
|
+
"""Invalid UTF-8."""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def onOpen(self):
|
|
104
|
+
"""
|
|
105
|
+
Callback when initial WebSockets handshake was completed. Now you may send messages.
|
|
106
|
+
Default implementation does nothing. Override in derived class.
|
|
107
|
+
"""
|
|
108
|
+
if self.debug:
|
|
109
|
+
log.msg("WebSocketProtocol.onOpen")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def onMessageBegin(self, opcode):
|
|
113
|
+
"""
|
|
114
|
+
Callback when receiving a new message has begun. Default implementation will
|
|
115
|
+
prepare to buffer message frames. Override in derived class.
|
|
116
|
+
|
|
117
|
+
:param opcode: Opcode of message.
|
|
118
|
+
:type opcode: int
|
|
119
|
+
"""
|
|
120
|
+
self.message_opcode = opcode
|
|
121
|
+
self.message_data = []
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def onMessageFrameBegin(self, length, reserved):
|
|
125
|
+
"""
|
|
126
|
+
Callback when receiving a new message frame has begun. Default implementation will
|
|
127
|
+
prepare to buffer message frame data. Override in derived class.
|
|
128
|
+
|
|
129
|
+
:param length: Length of message frame which is received.
|
|
130
|
+
:type length: int
|
|
131
|
+
:param reserved: Reserved bits set in frame (an integer from 0 to 7).
|
|
132
|
+
:type reserved: int
|
|
133
|
+
"""
|
|
134
|
+
self.frame_length = length
|
|
135
|
+
self.frame_reserved = reserved
|
|
136
|
+
self.frame_data = []
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def onMessageFrameData(self, payload):
|
|
140
|
+
"""
|
|
141
|
+
Callback when receiving data witin message frame. Default implementation will
|
|
142
|
+
buffer data for frame. Override in derived class.
|
|
143
|
+
|
|
144
|
+
:param payload: Partial payload for message frame.
|
|
145
|
+
:type payload: str
|
|
146
|
+
"""
|
|
147
|
+
self.frame_data.append(payload)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def onMessageFrameEnd(self):
|
|
151
|
+
"""
|
|
152
|
+
Callback when a message frame has been completely received. Default implementation
|
|
153
|
+
will flatten the buffered frame data and callback onMessageFrame. Override
|
|
154
|
+
in derived class.
|
|
155
|
+
"""
|
|
156
|
+
data = bytearray().join(self.frame_data)
|
|
157
|
+
self.logRxFrame(self.current_frame.fin, self.current_frame.rsv, self.current_frame.opcode, self.current_frame.mask is not None, self.current_frame.length, self.current_frame.mask, data)
|
|
158
|
+
self.onMessageFrame(data, self.frame_reserved)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def onMessageFrame(self, payload, reserved):
|
|
162
|
+
"""
|
|
163
|
+
Callback fired when complete message frame has been received. Default implementation
|
|
164
|
+
will buffer frame for message. Override in derived class.
|
|
165
|
+
|
|
166
|
+
:param payload: Message frame payload.
|
|
167
|
+
:type payload: str
|
|
168
|
+
:param reserved: Reserved bits set in frame (an integer from 0 to 7).
|
|
169
|
+
:type reserved: int
|
|
170
|
+
"""
|
|
171
|
+
self.message_data.append(payload)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def onMessageEnd(self):
|
|
175
|
+
"""
|
|
176
|
+
Callback when a message has been completely received. Default implementation
|
|
177
|
+
will flatten the buffered frames and callback onMessage. Override
|
|
178
|
+
in derived class.
|
|
179
|
+
"""
|
|
180
|
+
data = bytearray().join(self.message_data)
|
|
181
|
+
self.onMessage(str(data), self.message_opcode == WebSocketProtocol.MESSAGE_TYPE_BINARY)
|
|
182
|
+
self.message_opcode = None
|
|
183
|
+
self.message_data = None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def onMessage(self, payload, binary):
|
|
187
|
+
"""
|
|
188
|
+
Callback when a complete message was received. Default implementation does nothing.
|
|
189
|
+
Override in derived class.
|
|
190
|
+
|
|
191
|
+
:param payload: Message payload (UTF-8 encoded text string or binary string). Can also be an empty string, when message contained no payload.
|
|
192
|
+
:type payload: str
|
|
193
|
+
:param binary: If True, payload is binary, otherwise text.
|
|
194
|
+
:type binary: bool
|
|
195
|
+
"""
|
|
196
|
+
if self.debug:
|
|
197
|
+
log.msg("WebSocketProtocol.onMessage")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def onPing(self, payload):
|
|
201
|
+
"""
|
|
202
|
+
Callback when Ping was received. Default implementation responds
|
|
203
|
+
with a Pong. Override in derived class.
|
|
204
|
+
|
|
205
|
+
:param payload: Payload of Ping, when there was any. Can be arbitrary, up to 125 octets.
|
|
206
|
+
:type payload: str
|
|
207
|
+
"""
|
|
208
|
+
if self.debug:
|
|
209
|
+
log.msg("WebSocketProtocol.onPing")
|
|
210
|
+
self.sendPong(payload)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def onPong(self, payload):
|
|
214
|
+
"""
|
|
215
|
+
Callback when Pong was received. Default implementation does nothing.
|
|
216
|
+
Override in derived class.
|
|
217
|
+
|
|
218
|
+
:param payload: Payload of Pong, when there was any. Can be arbitrary, up to 125 octets.
|
|
219
|
+
"""
|
|
220
|
+
if self.debug:
|
|
221
|
+
log.msg("WebSocketProtocol.onPong")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def onClose(self, code, reason):
|
|
225
|
+
"""
|
|
226
|
+
Callback when Close was received. The default implementation answers by
|
|
227
|
+
sending a normal Close when no Close was sent before. Otherwise it drops
|
|
228
|
+
the connection. Override in derived class.
|
|
229
|
+
|
|
230
|
+
:param code: None or close status code, if there was one (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*).
|
|
231
|
+
:type code: int
|
|
232
|
+
:param reason: None or close reason (when present, a status code MUST have been also be present).
|
|
233
|
+
:type reason: str
|
|
234
|
+
"""
|
|
235
|
+
if self.debug:
|
|
236
|
+
log.msg("WebSocketProtocol.onClose")
|
|
237
|
+
if self.closeAlreadySent:
|
|
238
|
+
self.transport.loseConnection()
|
|
239
|
+
else:
|
|
240
|
+
self.sendClose(code = WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def __init__(self):
|
|
244
|
+
self.state = WebSocketProtocol.STATE_CLOSED
|
|
245
|
+
self.send_state = WebSocketProtocol.SEND_STATE_GROUND
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def getState(self):
|
|
249
|
+
return self.state
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def failConnection(self):
|
|
253
|
+
self.failedByMe = True
|
|
254
|
+
self.state = WebSocketProtocol.STATE_CLOSED
|
|
255
|
+
self.transport.loseConnection()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def protocolViolation(self, reason):
|
|
259
|
+
if self.debug:
|
|
260
|
+
log.msg("Failing connection on protocol violation : %s" % reason)
|
|
261
|
+
self.failConnection()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def connectionMade(self):
|
|
265
|
+
"""
|
|
266
|
+
This is called by Twisted framework when a new TCP connection has been established
|
|
267
|
+
and handed over to a Protocol instance (an instance of this class).
|
|
268
|
+
"""
|
|
269
|
+
self.debug = self.factory.debug
|
|
270
|
+
self.transport.setTcpNoDelay(True)
|
|
271
|
+
self.peer = self.transport.getPeer()
|
|
272
|
+
self.peerstr = "%s:%d" % (self.peer.host, self.peer.port)
|
|
273
|
+
self.state = WebSocketProtocol.STATE_CONNECTING
|
|
274
|
+
self.send_state = WebSocketProtocol.SEND_STATE_GROUND
|
|
275
|
+
self.data = ""
|
|
276
|
+
self.closeAlreadySent = False
|
|
277
|
+
self.failedByMe = False
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def connectionLost(self, reason):
|
|
281
|
+
"""
|
|
282
|
+
This is called by Twisted framework when a TCP connection was lost.
|
|
283
|
+
"""
|
|
284
|
+
self.state = WebSocketProtocol.STATE_CLOSED
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def logRxOctets(self, data):
|
|
288
|
+
if self.debug:
|
|
289
|
+
d = str(buffer(data))
|
|
290
|
+
log.msg("RX Octets from %s : %s" % (self.peerstr, binascii.b2a_hex(d)))
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def logTxOctets(self, data, sync):
|
|
294
|
+
if self.debug:
|
|
295
|
+
d = str(buffer(data))
|
|
296
|
+
log.msg("TX Octets to %s : %s" % (self.peerstr, binascii.b2a_hex(d)))
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def logRxFrame(self, fin, rsv, opcode, masked, payload_len, mask, payload):
|
|
300
|
+
if self.debug:
|
|
301
|
+
d = str(buffer(payload))
|
|
302
|
+
if mask:
|
|
303
|
+
mmask = binascii.b2a_hex(mask)
|
|
304
|
+
else:
|
|
305
|
+
mmask = str(mask)
|
|
306
|
+
log.msg("RX Frame from %s : fin = %s, rsv = %s, opcode = %s, masked = %s, payload_len = %s, mask = %s, payload = %s" % (self.peerstr, str(fin), str(rsv), str(opcode), str(masked), str(payload_len), mmask, binascii.b2a_hex(d)))
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def logTxFrame(self, opcode, payload, fin, rsv, mask, payload_len, chopsize, sync):
|
|
310
|
+
if self.debug:
|
|
311
|
+
d = str(buffer(payload))
|
|
312
|
+
if mask:
|
|
313
|
+
mmask = binascii.b2a_hex(mask)
|
|
314
|
+
else:
|
|
315
|
+
mmask = str(mask)
|
|
316
|
+
log.msg("TX Frame to %s : fin = %s, rsv = %s, opcode = %s, mask = %s, payload_len = %s, chopsize = %s, sync = %s, payload = %s" % (self.peerstr, str(fin), str(rsv), str(opcode), mmask, str(payload_len), str(chopsize), str(sync), binascii.b2a_hex(d)))
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def dataReceived(self, data):
|
|
320
|
+
"""
|
|
321
|
+
This is called by Twisted framework upon receiving data on TCP connection.
|
|
322
|
+
"""
|
|
323
|
+
self.logRxOctets(data)
|
|
324
|
+
|
|
325
|
+
self.data += data
|
|
326
|
+
self.consumeData()
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def consumeData(self):
|
|
330
|
+
|
|
331
|
+
buffered_len = len(self.data)
|
|
332
|
+
|
|
333
|
+
## WebSocket is open (handshake was completed) or close was sent
|
|
334
|
+
##
|
|
335
|
+
if self.state in [WebSocketProtocol.STATE_OPEN, WebSocketProtocol.STATE_CLOSING]:
|
|
336
|
+
|
|
337
|
+
while self.processData():
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
## WebSocket needs handshake
|
|
341
|
+
##
|
|
342
|
+
elif self.state == WebSocketProtocol.STATE_CONNECTING:
|
|
343
|
+
|
|
344
|
+
## the implementation of processHandshake() in derived
|
|
345
|
+
## class needs to perform client or server handshake
|
|
346
|
+
## from other party here ..
|
|
347
|
+
##
|
|
348
|
+
self.processHandshake()
|
|
349
|
+
|
|
350
|
+
## we failed the connection .. don't process any more data!
|
|
351
|
+
##
|
|
352
|
+
elif self.state == WebSocketProtocol.STATE_CLOSED:
|
|
353
|
+
log.msg("received data in STATE_CLOSED")
|
|
354
|
+
|
|
355
|
+
## should not arrive here (invalid state)
|
|
356
|
+
##
|
|
357
|
+
else:
|
|
358
|
+
raise Exception("invalid state")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def processHandshake(self):
|
|
362
|
+
"""
|
|
363
|
+
Process WebSockets handshake.
|
|
364
|
+
"""
|
|
365
|
+
raise Exception("must implement handshake (client or server) in derived class")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def syncSocket(self):
|
|
369
|
+
|
|
370
|
+
## FIXME: find suitable replacement for this code, which appears to break
|
|
371
|
+
## sometimes ..
|
|
372
|
+
##
|
|
373
|
+
## From the web: "You should never call reactor.doSelect. This isn't portable across
|
|
374
|
+
## reactors, and it could easily break the reactor by re-entering it where it isn't
|
|
375
|
+
## expecting to be re-entered."
|
|
376
|
+
##
|
|
377
|
+
try:
|
|
378
|
+
reactor.doIteration(0)
|
|
379
|
+
return True
|
|
380
|
+
except:
|
|
381
|
+
return False # socket has already gone away ..
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def registerProducer(self, producer, streaming):
|
|
385
|
+
self.transport.registerProducer(producer, streaming)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def sendData(self, raw, sync = False, chopsize = None):
|
|
389
|
+
"""
|
|
390
|
+
Wrapper for self.transport.write which allows to give a chopsize.
|
|
391
|
+
When asked to chop up writing to TCP stream, we write only chopsize octets
|
|
392
|
+
and then give up control to select() in underlying reactor so that bytes
|
|
393
|
+
get onto wire immediately. Note that this is different from and unrelated
|
|
394
|
+
to WebSockets data message fragmentation. Note that this is also different
|
|
395
|
+
from the TcpNoDelay option which can be set on the socket.
|
|
396
|
+
"""
|
|
397
|
+
if chopsize and chopsize > 0:
|
|
398
|
+
i = 0
|
|
399
|
+
n = len(raw)
|
|
400
|
+
done = False
|
|
401
|
+
while not done:
|
|
402
|
+
j = i + chopsize
|
|
403
|
+
if j >= n:
|
|
404
|
+
done = True
|
|
405
|
+
j = n
|
|
406
|
+
self.logTxOctets(raw[i:j], True)
|
|
407
|
+
self.transport.write(raw[i:j])
|
|
408
|
+
|
|
409
|
+
## This is where the "magic" happens. We give up control to the
|
|
410
|
+
## Twisted reactor, which calls into the OS Select(), which will
|
|
411
|
+
## then send out outstanding data. I suspect this Twisted call is
|
|
412
|
+
## probably not "intended" to be called by Twisted users, but it is
|
|
413
|
+
## the only "way" I found to work to attain the intended result.
|
|
414
|
+
##
|
|
415
|
+
self.syncSocket()
|
|
416
|
+
|
|
417
|
+
i += chopsize
|
|
418
|
+
else:
|
|
419
|
+
self.logTxOctets(raw, sync)
|
|
420
|
+
self.transport.write(raw)
|
|
421
|
+
if sync:
|
|
422
|
+
self.syncSocket()
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def processData(self):
|
|
426
|
+
"""
|
|
427
|
+
After WebSockets handshake has been completed, this procedure will do all
|
|
428
|
+
subsequent processing of incoming bytes.
|
|
429
|
+
"""
|
|
430
|
+
buffered_len = len(self.data)
|
|
431
|
+
|
|
432
|
+
## outside a frame, that is we are awaiting data which starts a new frame
|
|
433
|
+
##
|
|
434
|
+
if self.current_frame is None:
|
|
435
|
+
|
|
436
|
+
#buffered_len = len(self.data)
|
|
437
|
+
|
|
438
|
+
## need minimum of 2 octets to for new frame
|
|
439
|
+
##
|
|
440
|
+
if buffered_len >= 2:
|
|
441
|
+
|
|
442
|
+
## FIN, RSV, OPCODE
|
|
443
|
+
##
|
|
444
|
+
b = ord(self.data[0])
|
|
445
|
+
frame_fin = (b & 0x80) != 0
|
|
446
|
+
frame_rsv = (b & 0x70) >> 4
|
|
447
|
+
frame_opcode = b & 0x0f
|
|
448
|
+
|
|
449
|
+
## MASK, PAYLOAD LEN 1
|
|
450
|
+
##
|
|
451
|
+
b = ord(self.data[1])
|
|
452
|
+
frame_masked = (b & 0x80) != 0
|
|
453
|
+
frame_payload_len1 = b & 0x7f
|
|
454
|
+
|
|
455
|
+
## MUST be 0 when no extension defining
|
|
456
|
+
## the semantics of RSV has been negotiated
|
|
457
|
+
##
|
|
458
|
+
if frame_rsv != 0:
|
|
459
|
+
self.protocolViolation("RSV != 0 and no extension negotiated")
|
|
460
|
+
return False
|
|
461
|
+
|
|
462
|
+
## all client-to-server frames MUST be masked
|
|
463
|
+
##
|
|
464
|
+
if self.isServer and not frame_masked:
|
|
465
|
+
self.protocolViolation("unmasked client to server frame")
|
|
466
|
+
return False
|
|
467
|
+
|
|
468
|
+
## check frame
|
|
469
|
+
##
|
|
470
|
+
if frame_opcode > 7: # control frame (have MSB in opcode set)
|
|
471
|
+
|
|
472
|
+
## control frames MUST NOT be fragmented
|
|
473
|
+
##
|
|
474
|
+
if not frame_fin:
|
|
475
|
+
self.protocolViolation("fragmented control frame")
|
|
476
|
+
return False
|
|
477
|
+
|
|
478
|
+
## control frames MUST have payload 125 octets or less
|
|
479
|
+
##
|
|
480
|
+
if frame_payload_len1 > 125:
|
|
481
|
+
self.protocolViolation("control frame with payload length > 125 octets")
|
|
482
|
+
return False
|
|
483
|
+
|
|
484
|
+
## check for reserved control frame opcodes
|
|
485
|
+
##
|
|
486
|
+
if frame_opcode not in [8, 9, 10]:
|
|
487
|
+
self.protocolViolation("control frame using reserved opcode %d" % frame_opcode)
|
|
488
|
+
return False
|
|
489
|
+
|
|
490
|
+
## close frame : if there is a body, the first two bytes of the body MUST be a 2-byte
|
|
491
|
+
## unsigned integer (in network byte order) representing a status code
|
|
492
|
+
##
|
|
493
|
+
if frame_opcode == 8 and frame_payload_len1 == 1:
|
|
494
|
+
self.protocolViolation("received close control frame with payload len 1")
|
|
495
|
+
return False
|
|
496
|
+
|
|
497
|
+
else: # data frame
|
|
498
|
+
|
|
499
|
+
## check for reserved data frame opcodes
|
|
500
|
+
##
|
|
501
|
+
if frame_opcode not in [0, 1, 2]:
|
|
502
|
+
self.protocolViolation("data frame using reserved opcode %d" % frame_opcode)
|
|
503
|
+
return False
|
|
504
|
+
|
|
505
|
+
## check opcode vs message fragmentation state 1/2
|
|
506
|
+
##
|
|
507
|
+
if not self.inside_message and frame_opcode == 0:
|
|
508
|
+
self.protocolViolation("received continuation data frame outside fragmented message")
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
## check opcode vs message fragmentation state 2/2
|
|
512
|
+
##
|
|
513
|
+
if self.inside_message and frame_opcode != 0:
|
|
514
|
+
self.protocolViolation("received non-continuation data frame while inside fragmented message")
|
|
515
|
+
return False
|
|
516
|
+
|
|
517
|
+
## compute complete header length
|
|
518
|
+
##
|
|
519
|
+
if frame_masked:
|
|
520
|
+
mask_len = 4
|
|
521
|
+
else:
|
|
522
|
+
mask_len = 0
|
|
523
|
+
|
|
524
|
+
if frame_payload_len1 < 126:
|
|
525
|
+
frame_header_len = 2 + mask_len
|
|
526
|
+
elif frame_payload_len1 == 126:
|
|
527
|
+
frame_header_len = 2 + 2 + mask_len
|
|
528
|
+
elif frame_payload_len1 == 127:
|
|
529
|
+
frame_header_len = 2 + 8 + mask_len
|
|
530
|
+
else:
|
|
531
|
+
raise Exception("logic error")
|
|
532
|
+
|
|
533
|
+
## only proceed when we have enough data buffered for complete
|
|
534
|
+
## frame header (which includes extended payload len + mask)
|
|
535
|
+
##
|
|
536
|
+
if buffered_len >= frame_header_len:
|
|
537
|
+
|
|
538
|
+
i = 2
|
|
539
|
+
|
|
540
|
+
## extract extended payload length
|
|
541
|
+
##
|
|
542
|
+
if frame_payload_len1 == 126:
|
|
543
|
+
frame_payload_len = struct.unpack("!H", self.data[i:i+2])[0]
|
|
544
|
+
i += 2
|
|
545
|
+
elif frame_payload_len1 == 127:
|
|
546
|
+
frame_payload_len = struct.unpack("!Q", self.data[i:i+8])[0]
|
|
547
|
+
if frame_payload_len > 0x7FFFFFFFFFFFFFFF: # 2**63
|
|
548
|
+
self.protocolViolation("invalid data frame length (>2^63)")
|
|
549
|
+
return False
|
|
550
|
+
i += 8
|
|
551
|
+
else:
|
|
552
|
+
frame_payload_len = frame_payload_len1
|
|
553
|
+
|
|
554
|
+
## when payload is masked, extract frame mask
|
|
555
|
+
##
|
|
556
|
+
frame_mask = None
|
|
557
|
+
frame_mask_array = []
|
|
558
|
+
if frame_masked:
|
|
559
|
+
frame_mask = self.data[i:i+4]
|
|
560
|
+
for j in range(0, 4):
|
|
561
|
+
frame_mask_array.append(ord(frame_mask[j]))
|
|
562
|
+
i += 4
|
|
563
|
+
|
|
564
|
+
## remember rest (payload of current frame after header and everything thereafter)
|
|
565
|
+
##
|
|
566
|
+
self.data = self.data[i:]
|
|
567
|
+
|
|
568
|
+
## ok, got complete frame header
|
|
569
|
+
##
|
|
570
|
+
self.current_frame = FrameHeader(frame_opcode, frame_fin, frame_rsv, frame_payload_len, frame_mask, frame_mask_array)
|
|
571
|
+
|
|
572
|
+
## process begin on new frame
|
|
573
|
+
##
|
|
574
|
+
self.onFrameBegin()
|
|
575
|
+
|
|
576
|
+
## reprocess when frame has no payload or and buffered data left
|
|
577
|
+
##
|
|
578
|
+
return frame_payload_len == 0 or len(self.data) > 0
|
|
579
|
+
|
|
580
|
+
else:
|
|
581
|
+
return False # need more data
|
|
582
|
+
else:
|
|
583
|
+
return False # need more data
|
|
584
|
+
|
|
585
|
+
## inside a started frame
|
|
586
|
+
##
|
|
587
|
+
else:
|
|
588
|
+
|
|
589
|
+
## cut out rest of frame payload
|
|
590
|
+
##
|
|
591
|
+
rest = self.current_frame.length - self.current_frame.ptr
|
|
592
|
+
if buffered_len >= rest:
|
|
593
|
+
payload = bytearray(self.data[:rest])
|
|
594
|
+
length = rest
|
|
595
|
+
self.data = self.data[rest:]
|
|
596
|
+
else:
|
|
597
|
+
payload = bytearray(self.data)
|
|
598
|
+
length = buffered_len
|
|
599
|
+
self.data = ""
|
|
600
|
+
|
|
601
|
+
if length > 0:
|
|
602
|
+
## unmask payload
|
|
603
|
+
##
|
|
604
|
+
if self.current_frame.mask:
|
|
605
|
+
for k in xrange(0, length):
|
|
606
|
+
payload[k] ^= self.current_frame.mask_array[(k + self.current_frame.ptr) % 4]
|
|
607
|
+
|
|
608
|
+
## process frame data
|
|
609
|
+
##
|
|
610
|
+
self.onFrameData(payload)
|
|
611
|
+
|
|
612
|
+
## advance payload pointer and fire frame end handler when frame payload is complete
|
|
613
|
+
##
|
|
614
|
+
self.current_frame.ptr += length
|
|
615
|
+
if self.current_frame.ptr == self.current_frame.length:
|
|
616
|
+
self.onFrameEnd()
|
|
617
|
+
|
|
618
|
+
## reprocess when no error occurred and buffered data left
|
|
619
|
+
##
|
|
620
|
+
return len(self.data) > 0
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def onFrameBegin(self):
|
|
624
|
+
if self.current_frame.opcode > 7:
|
|
625
|
+
self.control_frame_data = bytearray()
|
|
626
|
+
else:
|
|
627
|
+
if not self.inside_message:
|
|
628
|
+
self.inside_message = True
|
|
629
|
+
self.onMessageBegin(self.current_frame.opcode)
|
|
630
|
+
self.onMessageFrameBegin(self.current_frame.length, self.current_frame.rsv)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def onFrameData(self, payload):
|
|
634
|
+
if self.current_frame.opcode > 7:
|
|
635
|
+
self.control_frame_data.extend(payload)
|
|
636
|
+
else:
|
|
637
|
+
self.onMessageFrameData(payload)
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def onFrameEnd(self):
|
|
641
|
+
if self.current_frame.opcode > 7:
|
|
642
|
+
self.processControlFrame()
|
|
643
|
+
else:
|
|
644
|
+
self.onMessageFrameEnd()
|
|
645
|
+
if self.current_frame.fin:
|
|
646
|
+
self.onMessageEnd()
|
|
647
|
+
self.inside_message = False
|
|
648
|
+
self.current_frame = None
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def processControlFrame(self):
|
|
652
|
+
payload = str(self.control_frame_data)
|
|
653
|
+
self.control_frame_data = None
|
|
654
|
+
|
|
655
|
+
self.logRxFrame(self.current_frame.fin, self.current_frame.rsv, self.current_frame.opcode, self.current_frame.mask is not None, self.current_frame.length, self.current_frame.mask, payload)
|
|
656
|
+
|
|
657
|
+
## CLOSE frame
|
|
658
|
+
##
|
|
659
|
+
if self.current_frame.opcode == 8:
|
|
660
|
+
|
|
661
|
+
code = None
|
|
662
|
+
reason = None
|
|
663
|
+
|
|
664
|
+
plen = len(payload)
|
|
665
|
+
if plen > 0:
|
|
666
|
+
|
|
667
|
+
## If there is a body, the first two bytes of the body MUST be a 2-byte
|
|
668
|
+
## unsigned integer (in network byte order) representing a status code
|
|
669
|
+
##
|
|
670
|
+
code = struct.unpack("!H", payload[0:2])[0]
|
|
671
|
+
|
|
672
|
+
## Following the 2-byte integer the body MAY contain UTF-8
|
|
673
|
+
## encoded data with value /reason/, the interpretation of which is not
|
|
674
|
+
## defined by this specification.
|
|
675
|
+
##
|
|
676
|
+
if plen > 2:
|
|
677
|
+
try:
|
|
678
|
+
reason = unicode(payload[2:], 'utf8')
|
|
679
|
+
except UnicodeDecodeError:
|
|
680
|
+
self.protocolViolation("received non-UTF-8 payload as close frame reason")
|
|
681
|
+
return False
|
|
682
|
+
|
|
683
|
+
self.onClose(code, reason)
|
|
684
|
+
|
|
685
|
+
## PING frame
|
|
686
|
+
##
|
|
687
|
+
elif self.current_frame.opcode == 9:
|
|
688
|
+
self.onPing(payload)
|
|
689
|
+
|
|
690
|
+
## PONG frame
|
|
691
|
+
##
|
|
692
|
+
elif self.current_frame.opcode == 10:
|
|
693
|
+
self.onPong(payload)
|
|
694
|
+
|
|
695
|
+
else:
|
|
696
|
+
raise Exception("logic error")
|
|
697
|
+
|
|
698
|
+
return True
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def sendFrame(self, opcode, payload = "", fin = True, rsv = 0, mask = None, payload_len = None, chopsize = None, sync = False):
|
|
702
|
+
"""
|
|
703
|
+
Send out frame. Normally only used internally via sendMessage(), sendPing(), sendPong() and sendClose().
|
|
704
|
+
|
|
705
|
+
This method deliberately allows to send invalid frames (that is frames invalid
|
|
706
|
+
per-se, or frames invalid because of protocol state). Other than in fuzzing servers,
|
|
707
|
+
calling methods will ensure that no invalid frames are sent.
|
|
708
|
+
|
|
709
|
+
In addition, this method supports explicit specification of payload length.
|
|
710
|
+
When payload_len is given, it will always write that many octets to the stream.
|
|
711
|
+
It'll wrap within payload, resending parts of that when more octets were requested
|
|
712
|
+
The use case is again for fuzzing server which want to sent increasing amounts
|
|
713
|
+
of payload data to peers without having to construct potentially large messges
|
|
714
|
+
themselfes.
|
|
715
|
+
"""
|
|
716
|
+
if payload_len:
|
|
717
|
+
if payload_len < 1 or len(payload) < 1:
|
|
718
|
+
raise Exception("cannot construct repeated payload with length %d from payload of length %d" % (payload_len, len(payload)))
|
|
719
|
+
l = payload_len
|
|
720
|
+
pl = ''.join([payload for k in range(payload_len / len(payload))]) + payload[:payload_len % len(payload)]
|
|
721
|
+
else:
|
|
722
|
+
l = len(payload)
|
|
723
|
+
pl = payload
|
|
724
|
+
|
|
725
|
+
## first byte
|
|
726
|
+
##
|
|
727
|
+
b0 = 0
|
|
728
|
+
if fin:
|
|
729
|
+
b0 |= (1 << 7)
|
|
730
|
+
b0 |= (rsv % 8) << 4
|
|
731
|
+
b0 |= opcode % 128
|
|
732
|
+
|
|
733
|
+
## second byte, payload len bytes and mask
|
|
734
|
+
##
|
|
735
|
+
b1 = 0
|
|
736
|
+
el = ""
|
|
737
|
+
if mask or not self.isServer:
|
|
738
|
+
b1 |= 1 << 7
|
|
739
|
+
if mask:
|
|
740
|
+
mv = struct.pack("!I", mask)
|
|
741
|
+
else:
|
|
742
|
+
mv = struct.pack("!I", random.getrandbits(32))
|
|
743
|
+
|
|
744
|
+
frame_mask = []
|
|
745
|
+
for j in range(0, 4):
|
|
746
|
+
frame_mask.append(ord(mv[j]))
|
|
747
|
+
|
|
748
|
+
## mask frame payload
|
|
749
|
+
##
|
|
750
|
+
pl_ba = bytearray(pl)
|
|
751
|
+
for k in xrange(0, l):
|
|
752
|
+
pl_ba[k] ^= frame_mask[k % 4]
|
|
753
|
+
plm = str(pl_ba)
|
|
754
|
+
|
|
755
|
+
else:
|
|
756
|
+
mv = ""
|
|
757
|
+
plm = pl
|
|
758
|
+
|
|
759
|
+
if l <= 125:
|
|
760
|
+
b1 |= l
|
|
761
|
+
elif l <= 0xFFFF:
|
|
762
|
+
b1 |= 126
|
|
763
|
+
el = struct.pack("!H", l)
|
|
764
|
+
elif l <= 0x7FFFFFFFFFFFFFFF:
|
|
765
|
+
b1 |= 127
|
|
766
|
+
el = struct.pack("!Q", l)
|
|
767
|
+
else:
|
|
768
|
+
raise Exception("invalid payload length")
|
|
769
|
+
|
|
770
|
+
raw = ''.join([chr(b0), chr(b1), el, mv, plm])
|
|
771
|
+
|
|
772
|
+
## log frame TX
|
|
773
|
+
##
|
|
774
|
+
if mv != "":
|
|
775
|
+
mmv = binascii.b2a_hex(mv)
|
|
776
|
+
else:
|
|
777
|
+
mmv = None
|
|
778
|
+
self.logTxFrame(opcode, payload, fin, rsv, mmv, payload_len, chopsize, sync)
|
|
779
|
+
|
|
780
|
+
## send frame octets
|
|
781
|
+
##
|
|
782
|
+
self.sendData(raw, sync, chopsize)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def sendPing(self, payload = None):
|
|
786
|
+
"""
|
|
787
|
+
Send out Ping to peer. A peer is expected to Pong back the payload a soon
|
|
788
|
+
as "practical". When more than 1 Ping is outstanding at a peer, the peer may
|
|
789
|
+
elect to respond only to the last Ping.
|
|
790
|
+
|
|
791
|
+
:param payload: An optional, arbitrary payload of length < 126 octets.
|
|
792
|
+
:type payload: str
|
|
793
|
+
"""
|
|
794
|
+
if payload:
|
|
795
|
+
l = len(payload)
|
|
796
|
+
if l > 125:
|
|
797
|
+
raise Exception("invalid payload for PING (payload length must be <= 125, was %d)" % l)
|
|
798
|
+
self.sendFrame(opcode = 9, payload = payload)
|
|
799
|
+
else:
|
|
800
|
+
self.sendFrame(opcode = 9)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def sendPong(self, payload = None):
|
|
804
|
+
"""
|
|
805
|
+
Send out Pong to peer. A Pong frame MAY be sent unsolicited.
|
|
806
|
+
This serves as a unidirectional heartbeat. A response to an unsolicited pong is "not expected".
|
|
807
|
+
|
|
808
|
+
:param payload: An optional, arbitrary payload of length < 126 octets.
|
|
809
|
+
:type payload: str
|
|
810
|
+
"""
|
|
811
|
+
if payload:
|
|
812
|
+
l = len(payload)
|
|
813
|
+
if l > 125:
|
|
814
|
+
raise Exception("invalid payload for PONG (payload length must be <= 125, was %d)" % l)
|
|
815
|
+
self.sendFrame(opcode = 10, payload = payload)
|
|
816
|
+
else:
|
|
817
|
+
self.sendFrame(opcode = 10)
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def sendClose(self, code = None, reason = None):
|
|
821
|
+
"""Send close to WebSockets peer.
|
|
822
|
+
|
|
823
|
+
:param code: An optional close status code (:class:`WebSocketProtocol`.CLOSE_STATUS_CODE_*).
|
|
824
|
+
:type code: int
|
|
825
|
+
:param reason: An optional close reason (when present, a status code MUST also be present).
|
|
826
|
+
:type reason: str
|
|
827
|
+
"""
|
|
828
|
+
plen = 0
|
|
829
|
+
payload = ""
|
|
830
|
+
|
|
831
|
+
if code is not None:
|
|
832
|
+
|
|
833
|
+
if (not (code >= 3000 and code <= 3999)) and \
|
|
834
|
+
(not (code >= 4000 and code <= 4999)) and \
|
|
835
|
+
code not in [WebSocketProtocol.CLOSE_STATUS_CODE_NORMAL,
|
|
836
|
+
WebSocketProtocol.CLOSE_STATUS_CODE_GOING_AWAY,
|
|
837
|
+
WebSocketProtocol.CLOSE_STATUS_CODE_PROTOCOL_ERROR,
|
|
838
|
+
WebSocketProtocol.CLOSE_STATUS_CODE_PAYLOAD_NOT_ACCEPTED,
|
|
839
|
+
WebSocketProtocol.CLOSE_STATUS_CODE_FRAME_TOO_LARGE,
|
|
840
|
+
WebSocketProtocol.CLOSE_STATUS_CODE_TEXT_FRAME_NOT_UTF8]:
|
|
841
|
+
raise Exception("invalid status code %d for close frame" % code)
|
|
842
|
+
|
|
843
|
+
payload = struct.pack("!H", code)
|
|
844
|
+
plen = 2
|
|
845
|
+
|
|
846
|
+
if reason is not None:
|
|
847
|
+
reason = reason.encode("UTF-8")
|
|
848
|
+
plen += len(reason)
|
|
849
|
+
else:
|
|
850
|
+
reason = ""
|
|
851
|
+
|
|
852
|
+
if plen > 125:
|
|
853
|
+
raise Exception("close frame payload larger than 125 octets")
|
|
854
|
+
|
|
855
|
+
payload += reason
|
|
856
|
+
|
|
857
|
+
else:
|
|
858
|
+
if reason is not None and reason != "":
|
|
859
|
+
raise Exception("status reason '%s' without status code in close frame" % reason)
|
|
860
|
+
|
|
861
|
+
self.sendFrame(opcode = 8, payload = payload)
|
|
862
|
+
self.closeAlreadySent = True
|
|
863
|
+
|
|
864
|
+
|
|
865
|
+
def beginMessage(self, opcode = MESSAGE_TYPE_TEXT):
|
|
866
|
+
"""
|
|
867
|
+
Begin sending new message.
|
|
868
|
+
|
|
869
|
+
:param opcode: Message type, normally either WebSocketProtocol.MESSAGE_TYPE_TEXT (default) or WebSocketProtocol.MESSAGE_TYPE_BINARY.
|
|
870
|
+
"""
|
|
871
|
+
## check if sending state is valid for this method
|
|
872
|
+
##
|
|
873
|
+
if self.send_state != WebSocketProtocol.SEND_STATE_GROUND:
|
|
874
|
+
raise Exception("WebSocketProtocol.beginMessage invalid in current sending state")
|
|
875
|
+
|
|
876
|
+
if opcode not in [1, 2]:
|
|
877
|
+
raise Exception("use of reserved opcode %d" % opcode)
|
|
878
|
+
|
|
879
|
+
## move into "begin message" state and remember opcode for later (when sending first frame)
|
|
880
|
+
##
|
|
881
|
+
self.send_state = WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN
|
|
882
|
+
self.send_message_opcode = opcode
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def beginMessageFrame(self, length, reserved = 0, mask = None):
|
|
886
|
+
"""
|
|
887
|
+
Begin sending new message frame.
|
|
888
|
+
|
|
889
|
+
:param length: Length of frame which is started. Must be >= 0 and <= 2^63.
|
|
890
|
+
:type length: int
|
|
891
|
+
:param reserved: Reserved bits for frame (an integer from 0 to 7). Note that reserved != 0 is only legal when an extension has been negoiated which defines semantics.
|
|
892
|
+
:type reserved: int
|
|
893
|
+
:param mask: Optional frame mask. When given, this is used. When None and the peer is a client, a mask will be internally generated. For servers None is default.
|
|
894
|
+
:type mask: str
|
|
895
|
+
"""
|
|
896
|
+
## check if sending state is valid for this method
|
|
897
|
+
##
|
|
898
|
+
if self.send_state not in [WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN, WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE]:
|
|
899
|
+
raise Exception("WebSocketProtocol.beginMessageFrame invalid in current sending state")
|
|
900
|
+
|
|
901
|
+
if (not type(length) in [int, long]) or length < 0 or length > 0x7FFFFFFFFFFFFFFF: # 2**63
|
|
902
|
+
raise Exception("invalid value for message frame length")
|
|
903
|
+
|
|
904
|
+
if type(reserved) is not int or reserved < 0 or reserved > 7:
|
|
905
|
+
raise Exception("invalid value for reserved bits")
|
|
906
|
+
|
|
907
|
+
self.send_message_frame_length = length
|
|
908
|
+
self.send_message_frame_octets_sent = 0
|
|
909
|
+
|
|
910
|
+
if mask:
|
|
911
|
+
if type(mask) is not str:
|
|
912
|
+
raise Exception("mask must be a (byte) string")
|
|
913
|
+
if len(mask) != 4:
|
|
914
|
+
raise Exception("mask must have length 4")
|
|
915
|
+
self.send_message_frame_mask = mask
|
|
916
|
+
elif not self.isServer:
|
|
917
|
+
self.send_message_frame_mask = random.getrandbits(32)
|
|
918
|
+
else:
|
|
919
|
+
self.send_message_frame_mask = None
|
|
920
|
+
|
|
921
|
+
## first byte
|
|
922
|
+
##
|
|
923
|
+
b0 = (reserved % 8) << 4 # FIN = false .. since with streaming, we don't know when message ends
|
|
924
|
+
|
|
925
|
+
if self.send_state == WebSocketProtocol.SEND_STATE_MESSAGE_BEGIN:
|
|
926
|
+
self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE
|
|
927
|
+
b0 |= self.send_message_opcode % 128
|
|
928
|
+
else:
|
|
929
|
+
pass # message continuation frame
|
|
930
|
+
|
|
931
|
+
## second byte, payload len bytes and mask
|
|
932
|
+
##
|
|
933
|
+
b1 = 0
|
|
934
|
+
el = ""
|
|
935
|
+
if self.send_message_frame_mask:
|
|
936
|
+
b1 |= 1 << 7
|
|
937
|
+
mv = struct.pack("!I", self.send_message_frame_mask)
|
|
938
|
+
self.send_message_frame_mask_array = []
|
|
939
|
+
for j in range(0, 4):
|
|
940
|
+
self.send_message_frame_mask_array.append(ord(mv[j]))
|
|
941
|
+
else:
|
|
942
|
+
mv = ""
|
|
943
|
+
|
|
944
|
+
if length <= 125:
|
|
945
|
+
b1 |= length
|
|
946
|
+
elif length <= 0xFFFF:
|
|
947
|
+
b1 |= 126
|
|
948
|
+
el = struct.pack("!H", length)
|
|
949
|
+
elif length <= 0x7FFFFFFFFFFFFFFF:
|
|
950
|
+
b1 |= 127
|
|
951
|
+
el = struct.pack("!Q", length)
|
|
952
|
+
else:
|
|
953
|
+
raise Exception("invalid payload length")
|
|
954
|
+
|
|
955
|
+
## write message frame header
|
|
956
|
+
##
|
|
957
|
+
header = ''.join([chr(b0), chr(b1), el, mv])
|
|
958
|
+
self.transport.write(header)
|
|
959
|
+
|
|
960
|
+
## now we are inside message frame ..
|
|
961
|
+
##
|
|
962
|
+
self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def sendMessageFrameData(self, payload):
|
|
966
|
+
"""
|
|
967
|
+
Send out data when within message frame (message was begun, frame was begun).
|
|
968
|
+
Note that the frame is automatically ended when enough data has been sent
|
|
969
|
+
that is, there is no endMessageFrame, since you have begun the frame specifying
|
|
970
|
+
the frame length, which implicitly defined the frame end. This is different from
|
|
971
|
+
messages, which you begin and end, since a message can contain an unlimited number
|
|
972
|
+
of frames.
|
|
973
|
+
|
|
974
|
+
:param payload: Data to send.
|
|
975
|
+
:returns: int -- When frame still incomplete, returns outstanding octets, when frame complete, returns <= 0, when < 0, the amount of unconsumed data in payload argument.
|
|
976
|
+
"""
|
|
977
|
+
## check if sending state is valid for this method
|
|
978
|
+
##
|
|
979
|
+
if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE_FRAME:
|
|
980
|
+
raise Exception("WebSocketProtocol.sendMessageFrameData invalid in current sending state")
|
|
981
|
+
|
|
982
|
+
rl = len(payload)
|
|
983
|
+
if self.send_message_frame_octets_sent + rl > self.send_message_frame_length:
|
|
984
|
+
l = self.send_message_frame_length - self.send_message_frame_octets_sent
|
|
985
|
+
rest = -(rl - l)
|
|
986
|
+
pl_ba = bytearray(payload[:l])
|
|
987
|
+
else:
|
|
988
|
+
l = rl
|
|
989
|
+
rest = self.send_message_frame_length - self.send_message_frame_octets_sent - l
|
|
990
|
+
pl_ba = bytearray(payload)
|
|
991
|
+
|
|
992
|
+
## mask frame payload
|
|
993
|
+
##
|
|
994
|
+
if self.send_message_frame_mask:
|
|
995
|
+
w = self.send_message_frame_octets_sent % 4
|
|
996
|
+
for k in xrange(0, l):
|
|
997
|
+
pl_ba[k] ^= self.send_message_frame_mask_array[(w + k) % 4]
|
|
998
|
+
## WARNING. This is silly: if you do it instead like the uncommended line, the memory footprint
|
|
999
|
+
## will run away ...
|
|
1000
|
+
# pl_ba[k] ^= self.send_message_frame_mask_array[(self.send_message_frame_octets_sent + k) % 4]
|
|
1001
|
+
|
|
1002
|
+
## send frame payload
|
|
1003
|
+
##
|
|
1004
|
+
self.transport.write(str(pl_ba))
|
|
1005
|
+
|
|
1006
|
+
pl_ba = None
|
|
1007
|
+
|
|
1008
|
+
#if sync:
|
|
1009
|
+
# self.syncSocket()
|
|
1010
|
+
|
|
1011
|
+
## advance frame payload pointer and check if frame payload was completely sent
|
|
1012
|
+
##
|
|
1013
|
+
self.send_message_frame_octets_sent += l
|
|
1014
|
+
|
|
1015
|
+
## if we are done with frame, move back into "inside message" state
|
|
1016
|
+
##
|
|
1017
|
+
if self.send_message_frame_octets_sent >= self.send_message_frame_length:
|
|
1018
|
+
self.send_state = WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE
|
|
1019
|
+
|
|
1020
|
+
## when =0 : frame was completed exactly
|
|
1021
|
+
## when >0 : frame is still uncomplete and that much amount is still left to complete the frame
|
|
1022
|
+
## when <0 : frame was completed and there was this much unconsumed data in payload argument
|
|
1023
|
+
##
|
|
1024
|
+
return rest
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def endMessage(self):
|
|
1028
|
+
"""
|
|
1029
|
+
End a previously begun message. No more frames may be sent (for that message). You have to
|
|
1030
|
+
begin a new message before sending again.
|
|
1031
|
+
"""
|
|
1032
|
+
## check if sending state is valid for this method
|
|
1033
|
+
##
|
|
1034
|
+
if self.send_state != WebSocketProtocol.SEND_STATE_INSIDE_MESSAGE:
|
|
1035
|
+
raise Exception("WebSocketProtocol.endMessage invalid in current sending state [%d]" % self.send_state)
|
|
1036
|
+
self.sendFrame(opcode = 0, fin = True)
|
|
1037
|
+
self.send_state = WebSocketProtocol.SEND_STATE_GROUND
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def sendMessageFrame(self, payload, reserved = 0, mask = None, sync = False):
|
|
1041
|
+
"""
|
|
1042
|
+
When a message has begun, send a complete message frame in one go.
|
|
1043
|
+
"""
|
|
1044
|
+
self.beginMessageFrame(len(payload), reserved, mask)
|
|
1045
|
+
self.sendMessageFrameData(payload, sync)
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def sendMessage(self, payload, binary = False, payload_frag_size = None, sync = False):
|
|
1049
|
+
"""
|
|
1050
|
+
Send out a message in one go.
|
|
1051
|
+
|
|
1052
|
+
You can send text or binary message, and optionally
|
|
1053
|
+
specifiy a payload fragment size. When the latter is given, the payload will
|
|
1054
|
+
be split up into frames with payload <= the payload_frag_size given.
|
|
1055
|
+
"""
|
|
1056
|
+
## (initial) frame opcode
|
|
1057
|
+
##
|
|
1058
|
+
if binary:
|
|
1059
|
+
opcode = 2
|
|
1060
|
+
else:
|
|
1061
|
+
opcode = 1
|
|
1062
|
+
|
|
1063
|
+
## send unfragmented
|
|
1064
|
+
##
|
|
1065
|
+
if payload_frag_size is None or len(payload) <= payload_frag_size:
|
|
1066
|
+
self.sendFrame(opcode = opcode, payload = payload, sync = sync)
|
|
1067
|
+
|
|
1068
|
+
## send data message in fragments
|
|
1069
|
+
##
|
|
1070
|
+
else:
|
|
1071
|
+
if payload_frag_size < 1:
|
|
1072
|
+
raise Exception("payload fragment size must be at least 1 (was %d)" % payload_frag_size)
|
|
1073
|
+
i = 0
|
|
1074
|
+
n = len(payload)
|
|
1075
|
+
done = False
|
|
1076
|
+
first = True
|
|
1077
|
+
while not done:
|
|
1078
|
+
j = i + payload_frag_size
|
|
1079
|
+
if j > n:
|
|
1080
|
+
done = True
|
|
1081
|
+
j = n
|
|
1082
|
+
if first:
|
|
1083
|
+
self.sendFrame(opcode = opcode, payload = payload[i:j], fin = done, sync = sync)
|
|
1084
|
+
first = False
|
|
1085
|
+
else:
|
|
1086
|
+
self.sendFrame(opcode = 0, payload = payload[i:j], fin = done, sync = sync)
|
|
1087
|
+
i += payload_frag_size
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
class HttpException():
|
|
1091
|
+
def __init__(self, code, reason):
|
|
1092
|
+
self.code = code
|
|
1093
|
+
self.reason = reason
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
class WebSocketServerProtocol(WebSocketProtocol):
|
|
1097
|
+
"""
|
|
1098
|
+
A Twisted protocol for WebSockets servers.
|
|
1099
|
+
"""
|
|
1100
|
+
|
|
1101
|
+
def onConnect(self, host, path, params, origin, protocols):
|
|
1102
|
+
"""
|
|
1103
|
+
Callback when new WebSocket client connection is established.
|
|
1104
|
+
Throw HttpException when you don't want to accept WebSocket connection.
|
|
1105
|
+
Return accepted protocol from list of protocols provided by client or None.
|
|
1106
|
+
|
|
1107
|
+
Override in derived class.
|
|
1108
|
+
"""
|
|
1109
|
+
return None
|
|
1110
|
+
|
|
1111
|
+
def connectionMade(self):
|
|
1112
|
+
WebSocketProtocol.connectionMade(self)
|
|
1113
|
+
if self.debug:
|
|
1114
|
+
log.msg("WebSocketServiceConnection.connectionMade")
|
|
1115
|
+
log.msg("connection accepted from %s" % self.peerstr)
|
|
1116
|
+
self.http_request = None
|
|
1117
|
+
self.http_headers = {}
|
|
1118
|
+
self.isServer = True
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def connectionLost(self, reason):
|
|
1122
|
+
WebSocketProtocol.connectionLost(self, reason)
|
|
1123
|
+
if self.debug:
|
|
1124
|
+
log.msg("WebSocketServiceConnection.connectionLost")
|
|
1125
|
+
log.msg("connection from %s lost" % self.peerstr)
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
def processHandshake(self):
|
|
1129
|
+
"""
|
|
1130
|
+
Process WebSockets handshake.
|
|
1131
|
+
"""
|
|
1132
|
+
## only proceed when we have fully received the HTTP request line and all headers
|
|
1133
|
+
##
|
|
1134
|
+
end_of_header = self.data.find("\x0d\x0a\x0d\x0a")
|
|
1135
|
+
if end_of_header >= 0:
|
|
1136
|
+
|
|
1137
|
+
## extract HTTP headers
|
|
1138
|
+
##
|
|
1139
|
+
## FIXME: properly handle headers split accross multiple lines
|
|
1140
|
+
##
|
|
1141
|
+
raw = self.data[:end_of_header].splitlines()
|
|
1142
|
+
self.http_request = raw[0].strip()
|
|
1143
|
+
for h in raw[1:]:
|
|
1144
|
+
i = h.find(":")
|
|
1145
|
+
if i > 0:
|
|
1146
|
+
key = h[:i].strip()
|
|
1147
|
+
value = h[i+1:].strip()
|
|
1148
|
+
self.http_headers[key] = value
|
|
1149
|
+
|
|
1150
|
+
## remember rest (after HTTP headers, if any)
|
|
1151
|
+
##
|
|
1152
|
+
self.data = self.data[end_of_header + 4:]
|
|
1153
|
+
|
|
1154
|
+
## self.http_request & self.http_headers are now set
|
|
1155
|
+
## => validate WebSocket handshake
|
|
1156
|
+
##
|
|
1157
|
+
|
|
1158
|
+
if self.debug:
|
|
1159
|
+
log.msg("received request line in handshake : %s" % str(self.http_request))
|
|
1160
|
+
log.msg("received headers in handshake : %s" % str(self.http_headers))
|
|
1161
|
+
|
|
1162
|
+
## HTTP Request line : METHOD, VERSION
|
|
1163
|
+
##
|
|
1164
|
+
rl = self.http_request.split(" ")
|
|
1165
|
+
if len(rl) != 3:
|
|
1166
|
+
return self.sendHttpBadRequest("bad HTTP request line '%s'" % self.http_request)
|
|
1167
|
+
if rl[0] != "GET":
|
|
1168
|
+
return self.sendHttpBadRequest("illegal HTTP method '%s'" % rl[0])
|
|
1169
|
+
vs = rl[2].split("/")
|
|
1170
|
+
if len(vs) != 2 or vs[0] != "HTTP" or vs[1] not in ["1.1"]:
|
|
1171
|
+
return self.sendHttpBadRequest("bad HTTP version '%s'" % rl[2])
|
|
1172
|
+
|
|
1173
|
+
## HTTP Request line : REQUEST-URI
|
|
1174
|
+
##
|
|
1175
|
+
## FIXME: checking
|
|
1176
|
+
##
|
|
1177
|
+
self.http_request_uri = rl[1]
|
|
1178
|
+
(scheme, loc, path, params, query, fragment) = urlparse.urlparse(self.http_request_uri)
|
|
1179
|
+
if fragment != "":
|
|
1180
|
+
return self.sendHttpBadRequest("HTTP Request URI with fragment identifier")
|
|
1181
|
+
self.http_request_path = path
|
|
1182
|
+
self.http_request_params = urlparse.parse_qs(query)
|
|
1183
|
+
|
|
1184
|
+
## Host
|
|
1185
|
+
##
|
|
1186
|
+
## FIXME: checking
|
|
1187
|
+
##
|
|
1188
|
+
if not self.http_headers.has_key("Host"):
|
|
1189
|
+
return self.sendHttpBadRequest("HTTP Host header missing")
|
|
1190
|
+
self.http_request_host = self.http_headers["Host"].strip()
|
|
1191
|
+
|
|
1192
|
+
## Upgrade
|
|
1193
|
+
##
|
|
1194
|
+
if not self.http_headers.has_key("Upgrade"):
|
|
1195
|
+
return self.sendHttpBadRequest("HTTP Upgrade header missing")
|
|
1196
|
+
if self.http_headers["Upgrade"] != "websocket":
|
|
1197
|
+
return self.sendHttpBadRequest("HTTP Upgrade header different from 'websocket'")
|
|
1198
|
+
|
|
1199
|
+
## Connection
|
|
1200
|
+
##
|
|
1201
|
+
if not self.http_headers.has_key("Connection"):
|
|
1202
|
+
return self.sendHttpBadRequest("HTTP Connection header missing")
|
|
1203
|
+
connectionUpgrade = False
|
|
1204
|
+
for c in self.http_headers["Connection"].split(","):
|
|
1205
|
+
if c.strip() == "Upgrade":
|
|
1206
|
+
connectionUpgrade = True
|
|
1207
|
+
break
|
|
1208
|
+
if not connectionUpgrade:
|
|
1209
|
+
return self.sendHttpBadRequest("HTTP Connection header does not include 'Upgrade' value")
|
|
1210
|
+
|
|
1211
|
+
## Sec-WebSocket-Version
|
|
1212
|
+
##
|
|
1213
|
+
if not self.http_headers.has_key("Sec-WebSocket-Version"):
|
|
1214
|
+
return self.sendHttpBadRequest("HTTP Sec-WebSocket-Version header missing")
|
|
1215
|
+
try:
|
|
1216
|
+
version = int(self.http_headers["Sec-WebSocket-Version"])
|
|
1217
|
+
if version < 8:
|
|
1218
|
+
return self.sendHttpBadRequest("Sec-WebSocket-Version %d not supported (only >= 8)" % version)
|
|
1219
|
+
else:
|
|
1220
|
+
self.websocket_version = version
|
|
1221
|
+
except:
|
|
1222
|
+
return self.sendHttpBadRequest("could not parse HTTP Sec-WebSocket-Version header '%s'" % self.http_headers["Sec-WebSocket-Version"])
|
|
1223
|
+
|
|
1224
|
+
## Sec-WebSocket-Protocol
|
|
1225
|
+
##
|
|
1226
|
+
##
|
|
1227
|
+
if self.http_headers.has_key("Sec-WebSocket-Protocol"):
|
|
1228
|
+
protocols = self.http_headers["Sec-WebSocket-Protocol"].split(",")
|
|
1229
|
+
# check for duplicates in protocol header
|
|
1230
|
+
pp = {}
|
|
1231
|
+
for p in protocols:
|
|
1232
|
+
if pp.has_key(p):
|
|
1233
|
+
return self.sendHttpBadRequest("duplicate protocol '%s' specified in HTTP Sec-WebSocket-Protocol header" % p)
|
|
1234
|
+
else:
|
|
1235
|
+
pp[p] = 1
|
|
1236
|
+
# ok, no duplicates, save list in order the client sent it
|
|
1237
|
+
self.websocket_protocols = protocols
|
|
1238
|
+
else:
|
|
1239
|
+
self.websocket_protocols = []
|
|
1240
|
+
|
|
1241
|
+
## Sec-WebSocket-Origin
|
|
1242
|
+
## http://tools.ietf.org/html/draft-ietf-websec-origin-02
|
|
1243
|
+
##
|
|
1244
|
+
## FIXME: checking
|
|
1245
|
+
##
|
|
1246
|
+
if self.http_headers.has_key("Sec-WebSocket-Origin"):
|
|
1247
|
+
origin = self.http_headers["Sec-WebSocket-Origin"].strip()
|
|
1248
|
+
if origin == "null":
|
|
1249
|
+
self.websocket_origin = None
|
|
1250
|
+
else:
|
|
1251
|
+
self.websocket_origin = origin
|
|
1252
|
+
else:
|
|
1253
|
+
self.websocket_origin = None
|
|
1254
|
+
|
|
1255
|
+
## Sec-WebSocket-Extensions
|
|
1256
|
+
##
|
|
1257
|
+
if self.http_headers.has_key("Sec-WebSocket-Extensions"):
|
|
1258
|
+
pass
|
|
1259
|
+
|
|
1260
|
+
## Sec-WebSocket-Key
|
|
1261
|
+
## http://tools.ietf.org/html/rfc4648#section-4
|
|
1262
|
+
##
|
|
1263
|
+
if not self.http_headers.has_key("Sec-WebSocket-Key"):
|
|
1264
|
+
return self.sendHttpBadRequest("HTTP Sec-WebSocket-Version header missing")
|
|
1265
|
+
key = self.http_headers["Sec-WebSocket-Key"].strip()
|
|
1266
|
+
if len(key) != 24: # 16 bytes => (ceil(128/24)*24)/6 == 24
|
|
1267
|
+
return self.sendHttpBadRequest("bad Sec-WebSocket-Key (length must be 24 ASCII chars) '%s'" % key)
|
|
1268
|
+
if key[-2:] != "==": # 24 - ceil(128/6) == 2
|
|
1269
|
+
return self.sendHttpBadRequest("bad Sec-WebSocket-Key (invalid base64 encoding) '%s'" % key)
|
|
1270
|
+
for c in key[:-2]:
|
|
1271
|
+
if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/":
|
|
1272
|
+
return self.sendHttpBadRequest("bad character '%s' in Sec-WebSocket-Key (invalid base64 encoding) '%s'" (c, key))
|
|
1273
|
+
|
|
1274
|
+
## WebSocket handshake validated
|
|
1275
|
+
## => produce response
|
|
1276
|
+
|
|
1277
|
+
## Now fire onConnect() on derived class, to give that class a chance to accept or deny
|
|
1278
|
+
## the connection. onConnect() may throw, in which case the connection is denied, or it
|
|
1279
|
+
## may return a protocol from the protocols provided by client or None.
|
|
1280
|
+
##
|
|
1281
|
+
try:
|
|
1282
|
+
protocol = self.onConnect(self.http_request_host, self.http_request_path, self.http_request_params, self.websocket_origin, self.websocket_protocols)
|
|
1283
|
+
if protocol and not (protocol in self.websocket_protocols):
|
|
1284
|
+
raise Exception("protocol accepted must be from the list client sent or null")
|
|
1285
|
+
except HttpException, e:
|
|
1286
|
+
return self.sendHttpRequestFailure(e.code, e.reason)
|
|
1287
|
+
|
|
1288
|
+
## compute Sec-WebSocket-Accept
|
|
1289
|
+
##
|
|
1290
|
+
sha1 = hashlib.sha1()
|
|
1291
|
+
sha1.update(key + WebSocketProtocol.WS_MAGIC)
|
|
1292
|
+
sec_websocket_accept = base64.b64encode(sha1.digest())
|
|
1293
|
+
|
|
1294
|
+
## send response to complete WebSocket handshake
|
|
1295
|
+
##
|
|
1296
|
+
response = "HTTP/1.1 101 Switching Protocols\x0d\x0a"
|
|
1297
|
+
response += "Upgrade: websocket\x0d\x0a"
|
|
1298
|
+
response += "Connection: Upgrade\x0d\x0a"
|
|
1299
|
+
response += "Sec-WebSocket-Accept: %s\x0d\x0a" % sec_websocket_accept
|
|
1300
|
+
if protocol:
|
|
1301
|
+
response += "Sec-WebSocket-Protocol: %s\x0d\x0a" % protocol
|
|
1302
|
+
response += "\x0d\x0a"
|
|
1303
|
+
|
|
1304
|
+
if self.debug:
|
|
1305
|
+
log.msg("send handshake : %s" % response)
|
|
1306
|
+
self.sendData(response)
|
|
1307
|
+
|
|
1308
|
+
## move into OPEN state
|
|
1309
|
+
##
|
|
1310
|
+
self.state = WebSocketProtocol.STATE_OPEN
|
|
1311
|
+
self.current_frame = None
|
|
1312
|
+
self.inside_message = False
|
|
1313
|
+
|
|
1314
|
+
## fire handler on derived class
|
|
1315
|
+
##
|
|
1316
|
+
self.onOpen()
|
|
1317
|
+
|
|
1318
|
+
## process rest, if any
|
|
1319
|
+
##
|
|
1320
|
+
if len(self.data) > 0:
|
|
1321
|
+
self.consumeData()
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
def sendHttpBadRequest(self, reason):
|
|
1325
|
+
"""
|
|
1326
|
+
When problems/errors happen during WebSockets handshake, send HTTP
|
|
1327
|
+
Bad Request Error and terminate.
|
|
1328
|
+
"""
|
|
1329
|
+
self.sendHttpRequestFailure(400, reason)
|
|
1330
|
+
|
|
1331
|
+
|
|
1332
|
+
def sendHttpRequestFailure(self, code, reason):
|
|
1333
|
+
"""
|
|
1334
|
+
Send out HTTP error.
|
|
1335
|
+
"""
|
|
1336
|
+
response = "HTTP/1.1 %d %s\x0d\x0a" % (code, reason)
|
|
1337
|
+
response += "\x0d\x0a"
|
|
1338
|
+
if self.debug:
|
|
1339
|
+
log.msg("send handshake failure : %s" % response)
|
|
1340
|
+
self.sendData(response)
|
|
1341
|
+
self.transport.loseConnection()
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
class WebSocketServerFactory(protocol.ServerFactory):
|
|
1345
|
+
"""
|
|
1346
|
+
A Twisted factory for WebSockets server protocols.
|
|
1347
|
+
"""
|
|
1348
|
+
|
|
1349
|
+
protocol = WebSocketServerProtocol
|
|
1350
|
+
|
|
1351
|
+
def __init__(self, debug = False):
|
|
1352
|
+
self.debug = debug
|
|
1353
|
+
|
|
1354
|
+
def startFactory(self):
|
|
1355
|
+
pass
|
|
1356
|
+
|
|
1357
|
+
def stopFactory(self):
|
|
1358
|
+
pass
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
class WebSocketClientProtocol(WebSocketProtocol):
|
|
1362
|
+
"""
|
|
1363
|
+
Client protocol for WebSockets.
|
|
1364
|
+
"""
|
|
1365
|
+
|
|
1366
|
+
def __init__(self, debug = False):
|
|
1367
|
+
self.debug = debug
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
def connectionMade(self):
|
|
1371
|
+
WebSocketProtocol.connectionMade(self)
|
|
1372
|
+
if self.debug:
|
|
1373
|
+
log.msg("connection to %s established" % self.peerstr)
|
|
1374
|
+
self.http_request = None
|
|
1375
|
+
self.http_headers = {}
|
|
1376
|
+
self.isServer = False
|
|
1377
|
+
self.startHandshake()
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
def connectionLost(self, reason):
|
|
1381
|
+
WebSocketProtocol.connectionLost(self, reason)
|
|
1382
|
+
if self.debug:
|
|
1383
|
+
log.msg("connection to %s lost" % self.peerstr)
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
def startHandshake(self):
|
|
1387
|
+
self.websocket_key = base64.b64encode(os.urandom(16))
|
|
1388
|
+
|
|
1389
|
+
request = "GET %s HTTP/1.1\x0d\x0a" % self.factory.path
|
|
1390
|
+
request += "Host: localhost:9000\x0d\x0a"
|
|
1391
|
+
request += "Upgrade: websocket\x0d\x0a"
|
|
1392
|
+
request += "Connection: Upgrade\x0d\x0a"
|
|
1393
|
+
request += "Sec-WebSocket-Key: %s\x0d\x0a" % self.websocket_key
|
|
1394
|
+
request += "Sec-WebSocket-Version: 8\x0d\x0a"
|
|
1395
|
+
request += "\x0d\x0a"
|
|
1396
|
+
|
|
1397
|
+
if self.debug:
|
|
1398
|
+
log.msg(request)
|
|
1399
|
+
|
|
1400
|
+
self.sendData(request)
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
def processHandshake(self):
|
|
1404
|
+
"""
|
|
1405
|
+
Process WebSockets handshake.
|
|
1406
|
+
"""
|
|
1407
|
+
## only proceed when we have fully received the HTTP request line and all headers
|
|
1408
|
+
##
|
|
1409
|
+
end_of_header = self.data.find("\x0d\x0a\x0d\x0a")
|
|
1410
|
+
if end_of_header >= 0:
|
|
1411
|
+
|
|
1412
|
+
## extract HTTP headers
|
|
1413
|
+
##
|
|
1414
|
+
## FIXME: properly handle headers split accross multiple lines
|
|
1415
|
+
##
|
|
1416
|
+
raw = self.data[:end_of_header].splitlines()
|
|
1417
|
+
self.http_request = raw[0].strip()
|
|
1418
|
+
for h in raw[1:]:
|
|
1419
|
+
i = h.find(":")
|
|
1420
|
+
if i > 0:
|
|
1421
|
+
key = h[:i].strip()
|
|
1422
|
+
value = h[i+1:].strip()
|
|
1423
|
+
self.http_headers[key] = value
|
|
1424
|
+
|
|
1425
|
+
## remember rest (after HTTP headers, if any)
|
|
1426
|
+
##
|
|
1427
|
+
self.data = self.data[end_of_header + 4:]
|
|
1428
|
+
|
|
1429
|
+
## self.http_request & self.http_headers are now set
|
|
1430
|
+
## => validate WebSocket handshake
|
|
1431
|
+
##
|
|
1432
|
+
|
|
1433
|
+
if self.debug:
|
|
1434
|
+
log.msg("received request line in handshake : %s" % str(self.http_request))
|
|
1435
|
+
log.msg("received headers in handshake : %s" % str(self.http_headers))
|
|
1436
|
+
|
|
1437
|
+
## Response Line
|
|
1438
|
+
##
|
|
1439
|
+
if self.http_request != "HTTP/1.1 101 Switching Protocols":
|
|
1440
|
+
pass
|
|
1441
|
+
|
|
1442
|
+
## Upgrade
|
|
1443
|
+
##
|
|
1444
|
+
if not self.http_headers.has_key("Upgrade"):
|
|
1445
|
+
return self.sendHttpBadRequest("HTTP Upgrade header missing")
|
|
1446
|
+
if self.http_headers["Upgrade"] != "websocket":
|
|
1447
|
+
return self.sendHttpBadRequest("HTTP Upgrade header different from 'websocket'")
|
|
1448
|
+
|
|
1449
|
+
## Connection
|
|
1450
|
+
##
|
|
1451
|
+
if not self.http_headers.has_key("Connection"):
|
|
1452
|
+
return self.sendHttpBadRequest("HTTP Connection header missing")
|
|
1453
|
+
connectionUpgrade = False
|
|
1454
|
+
for c in self.http_headers["Connection"].split(","):
|
|
1455
|
+
if c.strip() == "Upgrade":
|
|
1456
|
+
connectionUpgrade = True
|
|
1457
|
+
break
|
|
1458
|
+
if not connectionUpgrade:
|
|
1459
|
+
return self.sendHttpBadRequest("HTTP Connection header does not include 'Upgrade' value")
|
|
1460
|
+
|
|
1461
|
+
## compute Sec-WebSocket-Accept
|
|
1462
|
+
##
|
|
1463
|
+
if not self.http_headers.has_key("Sec-WebSocket-Accept"):
|
|
1464
|
+
return self.sendHttpBadRequest("HTTP Sec-WebSocket-Accept header missing")
|
|
1465
|
+
else:
|
|
1466
|
+
sha1 = hashlib.sha1()
|
|
1467
|
+
sha1.update(self.websocket_key + WebSocketProtocol.WS_MAGIC)
|
|
1468
|
+
sec_websocket_accept = base64.b64encode(sha1.digest())
|
|
1469
|
+
|
|
1470
|
+
if self.http_headers["Sec-WebSocket-Accept"] != sec_websocket_accept:
|
|
1471
|
+
pass
|
|
1472
|
+
|
|
1473
|
+
## move into OPEN state
|
|
1474
|
+
##
|
|
1475
|
+
self.state = WebSocketProtocol.STATE_OPEN
|
|
1476
|
+
self.current_frame = None
|
|
1477
|
+
self.inside_message = False
|
|
1478
|
+
|
|
1479
|
+
## fire handler on derived class
|
|
1480
|
+
##
|
|
1481
|
+
self.onOpen()
|
|
1482
|
+
|
|
1483
|
+
## process rest, if any
|
|
1484
|
+
##
|
|
1485
|
+
if len(self.data) > 0:
|
|
1486
|
+
self.consumeData()
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
class WebSocketClientFactory(protocol.ClientFactory):
|
|
1490
|
+
"""
|
|
1491
|
+
Client factory for WebSockets.
|
|
1492
|
+
"""
|
|
1493
|
+
|
|
1494
|
+
protocol = WebSocketClientProtocol
|
|
1495
|
+
|
|
1496
|
+
def __init__(self, debug = False):
|
|
1497
|
+
self.debug = debug
|
|
1498
|
+
self.path = "/"
|
|
1499
|
+
random.seed()
|
|
1500
|
+
|
|
1501
|
+
def clientConnectionFailed(self, connector, reason):
|
|
1502
|
+
reactor.stop()
|
|
1503
|
+
|
|
1504
|
+
def clientConnectionLost(self, connector, reason):
|
|
1505
|
+
reactor.stop()
|