Post Reply 
Another Filtering Proxy
Jan. 07, 2016, 02:25 AM
Post: #31
RE: Another Filtering Proxy
I use py2exe to compile to exe. The last time of a successful exe was built with below versions:

- python 3.4.3
- cryptography (0.8.2)
- py2exe (0.9.2.2)

For other components you should could use the latest versions.
Add Thank You Quote this message in a reply
Jan. 07, 2016, 08:41 AM
Post: #32
RE: Another Filtering Proxy
Thank you very much.
Add Thank You Quote this message in a reply
Jan. 10, 2016, 07:27 AM (This post was last modified: Jan. 10, 2016 10:09 AM by cattleyavns.)
Post: #33
RE: Another Filtering Proxy
I just want to report a bug, that is we current cannot serve chunked Transfer-Encoding, I found this bug when I was uploading on https://dropbox.com, the upload progressbar show "2 secs remaining" but it always like this from the start, but without AFProxy the progressbar will always update itself (1min then 6 then 7 then 8 then lower..):

AFProxy: http://prntscr.com/9o8o1k
No Proxy: http://prntscr.com/9o8oms

I looks like "stream_to_client®" didn't use its "if need_chunked:" but stream the content normally, i tried to debug with

Code:
def stream_to_client(self, response):
        bufsize = 4096
        need_chunked = 'Transfer-Encoding' in response.headers
        written = 0
        while True:
            data = response.read(bufsize)
            if not data:
                if need_chunked:
                    self.wfile.write(b'0\r\n\r\n')
                break
            if need_chunked:
                self.wfile.write(('%x\r\n' % len(data)).encode('ascii'))
            print(data)
            self.wfile.write(data)
            if need_chunked:
                self.wfile.write(b'\r\n')
            written += len(data)
        return written

This page works very well (print a lots data): https://jigsaw.w3.org/HTTP/ChunkedScript
But not with Dropbox's Upload
Add Thank You Quote this message in a reply
Jan. 25, 2016, 04:37 PM (This post was last modified: Jan. 25, 2016 04:52 PM by cattleyavns.)
Post: #34
RE: Another Filtering Proxy
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
Add Thank You Quote this message in a reply
May. 16, 2016, 08:42 AM
Post: #35
RE: Another Filtering Proxy
@cattleyavns, thank you for the bug reports.

I'm sorry for the late reply and I have to let you know that I don't have time to maintain the program any more. Feel free to play with it Smile! or forget it Sad .
Add Thank You Quote this message in a reply
Post Reply 


Forum Jump: