I also want to report another bug, we currently don't have websocket support, I think these days, websocket became very important, especially online remote desktop service like this, without websocket support we cannot control or handshake with remote desktop server:
https://dpetechnet.cloudapp.net/Home.aspx
Test:
http://websocket.org/echo.html
I tried to implement websocket by editing ProxyTool.py but I cannot make it work, if AFProxy tries to handshake a insecure websocket connect, it will simply fail, only secure websocket works, AFProxy can handshare with websocket server, but cannot send and receive packets:
Code:
from http.server import SimpleHTTPRequestHandler
import struct
from base64 import b64encode
from hashlib import sha1
from email.message import Message
from io import StringIO
import errno, socket #for socket exceptions
import threading
class ProxyRequestHandler(BaseHTTPRequestHandler):
"""RequestHandler with do_CONNECT method defined
"""
server_version = "%s/%s" % (_name, __version__)
# do_CONNECT() will set self.ssltunnel to override this
ssltunnel = False
# Override default value 'HTTP/1.0'
protocol_version = 'HTTP/1.1'
_ws_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
_opcode_continu = 0x0
_opcode_text = 0x1
_opcode_binary = 0x2
_opcode_close = 0x8
_opcode_ping = 0x9
_opcode_pong = 0xa
mutex = threading.Lock()
def on_ws_message(self, message):
if message is None:
message = b''
# echo message back to client
#print(dir(self))
#print(self.headers)
#self.wfile.write(message)
self.wfile.write(bytes(message.encode('utf-8')))
self.log_message('websocket received "%s"',str(message))
#self.close_connection = 1
def on_ws_connected(self):
self.log_message('%s','websocket connected')
def on_ws_closed(self):
self.log_message('%s','websocket closed')
def send_message(self, message):
self._send_message(self._opcode_text, message)
def setup(self):
SimpleHTTPRequestHandler.setup(self)
self.connected = False
def finish(self):
#needed when wfile is used, or when self.close_connection is not used
# #
# #catch errors in SimpleHTTPRequestHandler.finish() after socket disappeared
# #due to loss of network connection
try:
SimpleHTTPRequestHandler.finish(self)
except (socket.error, TypeError) as err:
self.log_message("finish(): Exception: in SimpleHTTPRequestHandler.finish(): %s" % str(err.args))
def handle(self):
#needed when wfile is used, or when self.close_connection is not used
# #
# #catch errors in SimpleHTTPRequestHandler.handle() after socket disappeared
# #due to loss of network connection
try:
SimpleHTTPRequestHandler.handle(self)
except (socket.error, TypeError) as err:
self.log_message("handle(): Exception: in SimpleHTTPRequestHandler.handle(): %s" % str(err.args))
def checkAuthentication(self):
auth = self.headers.get('Authorization')
if auth != "Basic %s" % self.server.auth:
self.send_response(401)
self.send_header("WWW-Authenticate", 'Basic realm="Plugwise"')
self.end_headers();
return False
return True
def _read_messages(self):
while self.connected == True:
try:
self._read_next_message()
except (socket.error, WebSocketError) as e:
#websocket content error, time-out or disconnect.
self.log_message("RCV: Close connection: Socket Error %s" % str(e.args))
self._ws_close()
except Exception as err:
#unexpected error in websocket connection.
self.log_error("RCV: Exception: in _read_messages: %s" % str(err.args))
self._ws_close()
def _read_next_message(self):
#self.rfile.read(n) is blocking.
#it returns however immediately when the socket is closed.
try:
#print(self.rfile.read(1))
self.opcode = ord(self.rfile.read(1)) & 0x0F
length = ord(self.rfile.read(1)) & 0x7F
print('length %s opcode %s' %(length, self.opcode))
if length == 126:
length = struct.unpack(">H", self.rfile.read(2))[0]
elif length == 127:
length = struct.unpack(">Q", self.rfile.read(8))[0]
masks = [ord(chr(byte)) for byte in self.rfile.read(4)]
print(masks)
decoded = ""
for char in self.rfile.read(length):
#print(chr(ord(chr(char)) ^ masks[len(decoded) % 4]))
decoded += chr(ord(chr(char)) ^ masks[len(decoded) % 4])
#print(decoded)
self._on_message(decoded)
except (struct.error, TypeError) as e:
#catch exceptions from ord() and struct.unpack()
if self.connected:
raise WebSocketError("Websocket read aborted while listening")
else:
#the socket was closed while waiting for input
self.log_error("RCV: _read_next_message aborted after closed connection")
pass
def _send_message(self, opcode, message):
print('123')
try:
#use of self.wfile.write gives socket exception after socket is closed. Avoid.
self.wfile.write(chr(0x80 + opcode))
print('self.wfile.write(chr(0x80 + opcode)) %s' %(chr(0x80 + opcode)))
length = len(message)
if length <= 125:
self.wfile.write(chr(length))
print('self.wfile.write(chr(length)) %s' %(chr(length)))
elif length >= 126 and length <= 65535:
self.wfile.write(chr(126))
print('self.wfile.write(chr(126)) %s' %(chr(126)))
self.wfile.write(struct.pack(">H", length))
print('self.wfile.write(struct.pack(">H", length)) %s' %(struct.pack(">H", length)))
else:
self.wfile.write(chr(127))
print('self.wfile.write(chr(127)) %s' %(chr(127)))
self.wfile.write(struct.pack(">Q", length))
print('self.wfile.write(struct.pack(">Q", length)) %s' %(struct.pack(">Q", length)))
if length > 0:
self.wfile.write(message)
print('self.wfile.write(message) %s' %(message))
except socket.error as e:
#websocket content error, time-out or disconnect.
self.log_message("SND: Close connection: Socket Error %s" % str(e.args))
self._ws_close()
except Exception as err:
#unexpected error in websocket connection.
self.log_error("SND: Exception: in _send_message: %s" % str(err.args))
self._ws_close()
def _handshake(self):
headers=self.headers
if headers.get("Upgrade", None) != "websocket":
return
key = headers['Sec-WebSocket-Key']
digest = b64encode(sha1(key.encode('utf-8') + self._ws_GUID.encode('utf-8')).digest()).decode('ascii')
self.send_response(101, 'Switching Protocols')
self.send_header('Upgrade', 'websocket')
self.send_header('Connection', 'Upgrade')
self.send_header('Sec-WebSocket-Accept', str(digest))
self.send_header('Access-Control-Allow-Credentials', 'true')
self.send_header('Access-Control-Allow-Headers', 'content-type, authorization, x-websocket-extensions, x-websocket-version, x-websocket-protocol')
self.send_header('Access-Control-Allow-Origin', 'http://www.websocket.org')
self.end_headers()
self.connected = True
#self.close_connection = 0
self.on_ws_connected()
def _ws_close(self):
#avoid closing a single socket two time for send and receive.
self.mutex.acquire()
try:
if self.connected:
self.connected = False
#Terminate BaseHTTPRequestHandler.handle() loop:
self.close_connection = 1
#send close and ignore exceptions. An error may already have occurred.
try:
self._send_close()
except:
pass
self.on_ws_closed()
else:
self.log_message("_ws_close websocket in closed state. Ignore.")
pass
finally:
self.mutex.release()
def _on_message(self, message):
#self.log_message("_on_message: opcode: %02X msg: %s" % (self.opcode, message))
#print(self.opcode)
#print(self._opcode_close)
# close
if self.opcode == self._opcode_close:
self.connected = False
#Terminate BaseHTTPRequestHandler.handle() loop:
self.close_connection = 1
try:
self._send_close()
except:
pass
self.on_ws_closed()
# ping
elif self.opcode == self._opcode_ping:
_send_message(self._opcode_pong, message)
# pong
elif self.opcode == self._opcode_pong:
pass
# data
elif (self.opcode == self._opcode_continu or
self.opcode == self._opcode_text or
self.opcode == self._opcode_binary):
self.on_ws_message(message)
def _send_close(self):
#Dedicated _send_close allows for catch all exception handling
msg = bytearray()
msg.append(0x80 + self._opcode_close)
msg.append(0x00)
self.wfile.write(msg)
Add this line to do_GET:
Code:
if self.headers.get("Upgrade", None) == "websocket":
#print('000000')
self._handshake()
#This handler is in websocket mode now.
#do_GET only returns after client close or socket error.
self._read_messages()
return
This software has the same problem:
https://github.com/google/martian/issues/31