Example #1
0
    def __init__(self, socket):
        super(PugdebugServerConnection, self).__init__()

        self.socket = socket

        self.mutex = QMutex()

        self.parser = PugdebugMessageParser()
Example #2
0
    def __init__(self, socket):
        super(PugdebugServerConnection, self).__init__()

        self.socket = socket

        self.mutex = QMutex()

        self.parser = PugdebugMessageParser()
Example #3
0
 def setUp(self):
     self.parser = PugdebugMessageParser()
Example #4
0
class PugdebugMessageParserTest(unittest.TestCase):

    #maxDiff = None

    def setUp(self):
        self.parser = PugdebugMessageParser()

    def test_parse_init_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<init xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" fileuri="file:///home/robert/www/pxdebug/index.php" language="PHP" protocol_version="1.0" appid="3696" idekey="1"><engine version="2.2.7"><![CDATA[Xdebug]]></engine><author><![CDATA[Derick Rethans]]></author><url><![CDATA[http://xdebug.org]]></url><copyright><![CDATA[Copyright (c) 2002-2015 by Derick Rethans]]></copyright></init>'

        result = self.parser.parse_init_message(message)

        expected = {
            'fileuri': '/home/robert/www/pxdebug/index.php',
            'idekey': '1',
            'engine': 'Xdebug 2.2.7',
            'author': 'Derick Rethans',
            'url': 'http://xdebug.org',
            'copyright': 'Copyright (c) 2002-2015 by Derick Rethans'
        }

        self.assertEqual(expected, result)

    def test_parse_status_break_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="step_into" transaction_id="1" status="break" reason="ok"><xdebug:message filename="file:///home/robert/www/pxdebug/index.php" lineno="3"></xdebug:message></response>'

        result = self.parser.parse_continuation_message(message)

        expected = {
            'command': 'step_into',
            'transaction_id': '1',
            'status': 'break',
            'reason': 'ok',
            'filename': '/home/robert/www/pxdebug/index.php',
            'lineno': '3'
        }

        self.assertEqual(expected, result)

    def test_parse_status_stopping_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="step_into" transaction_id="28" status="stopping" reason="ok"></response>'

        result = self.parser.parse_continuation_message(message)

        expected = {
            'command': 'step_into',
            'transaction_id': '28',
            'status': 'stopping',
            'reason': 'ok'
        }

        self.assertEqual(expected, result)

    def test_parse_variable_contexts_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="context_names" transaction_id="2"><context name="Locals" id="0"></context><context name="Superglobals" id="1"></context></response>'

        result = self.parser.parse_variable_contexts_message(message)

        expected = [{
            'name': 'Locals',
            'id': '0'
        }, {
            'name': 'Superglobals',
            'id': '1'
        }]

        self.assertEqual(expected, result)

    def test_parse_variables_simple_local(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="context_get" transaction_id="2;" context="0"><property name="$i" fullname="$i" type="int"><![CDATA[1]]></property></response>'

        result = self.parser.parse_variables_message(message)

        expected = [{'name': '$i', 'type': 'int', 'value': '1'}]

        self.assertEqual(expected, result)

    def test_parse_variables_superglobals(self):
        file = open('./pugdebug/tests/_files/superglobals.xml', 'r')
        message = file.read()
        file.close()

        result = self.parser.parse_variables_message(message)

        expected = [{
            'name': '$_COOKIE',
            'type': 'array',
            'variables': [],
            'numchildren': '0'
        }, {
            'name': '$_ENV',
            'type': 'array',
            'variables': [],
            'numchildren': '0'
        }, {
            'name': '$_FILES',
            'type': 'array',
            'variables': [],
            'numchildren': '0'
        }, {
            'name':
            '$_GET',
            'type':
            'array',
            'variables': [{
                'name': 'XDEBUG_SESSION_START',
                'type': 'string',
                'encoding': 'base64',
                'value': 'MQ==',
                'size': '1'
            }],
            'numchildren':
            '1'
        }, {
            'name': '$_POST',
            'type': 'array',
            'variables': [],
            'numchildren': '0'
        }, {
            'name':
            '$_REQUEST',
            'type':
            'array',
            'variables': [{
                'name': 'XDEBUG_SESSION_START',
                'type': 'string',
                'encoding': 'base64',
                'value': 'MQ==',
                'size': '1'
            }],
            'numchildren':
            '1'
        }, {
            'name':
            '$_SERVER',
            'type':
            'array',
            'variables': [{
                'name': 'UNIQUE_ID',
                'type': 'string',
                'encoding': 'base64',
                'value': 'VlBBajZpYWgxVGtGQGlDVzFuNzhCZ0FBQUFB',
                'size': '27'
            }, {
                'name': 'HTTP_HOST',
                'type': 'string',
                'encoding': 'base64',
                'value': 'bG9jYWxob3N0',
                'size': '9'
            }, {
                'name': 'HTTP_USER_AGENT',
                'type': 'string',
                'encoding': 'base64',
                'value':
                'TW96aWxsYS81LjAgKFgxMTsgRmVkb3JhOyBMaW51eCB4ODZfNjQ7IHJ2OjM2LjApIEdlY2tvLzIwMTAwMTAxIEZpcmVmb3gvMzYuMA==',
                'size': '76'
            }, {
                'name': 'HTTP_ACCEPT',
                'type': 'string',
                'encoding': 'base64',
                'value':
                'dGV4dC9odG1sLGFwcGxpY2F0aW9uL3hodG1sK3htbCxhcHBsaWNhdGlvbi94bWw7cT0wLjksKi8qO3E9MC44',
                'size': '63'
            }, {
                'name': 'HTTP_ACCEPT_LANGUAGE',
                'type': 'string',
                'encoding': 'base64',
                'value': 'ZW4tVVMsZW47cT0wLjU=',
                'size': '14'
            }, {
                'name': 'HTTP_ACCEPT_ENCODING',
                'type': 'string',
                'encoding': 'base64',
                'value': 'Z3ppcCwgZGVmbGF0ZQ==',
                'size': '13'
            }, {
                'name': 'HTTP_CONNECTION',
                'type': 'string',
                'encoding': 'base64',
                'value': 'a2VlcC1hbGl2ZQ==',
                'size': '10'
            }, {
                'name': 'PATH',
                'type': 'string',
                'encoding': 'base64',
                'value':
                'L3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbg==',
                'size': '49'
            }, {
                'name': 'SERVER_SIGNATURE',
                'type': 'string',
                'encoding': 'base64',
                'value': None,
                'size': '0'
            }, {
                'name': 'SERVER_SOFTWARE',
                'type': 'string',
                'encoding': 'base64',
                'value':
                'QXBhY2hlLzIuNC4xMCAoRmVkb3JhKSBPcGVuU1NMLzEuMC4xay1maXBzIFBIUC81LjYuNg==',
                'size': '52'
            }, {
                'name': 'SERVER_NAME',
                'type': 'string',
                'encoding': 'base64',
                'value': 'bG9jYWxob3N0',
                'size': '9'
            }, {
                'name': 'SERVER_ADDR',
                'type': 'string',
                'encoding': 'base64',
                'value': 'MTI3LjAuMC4x',
                'size': '9'
            }, {
                'name': 'SERVER_PORT',
                'type': 'string',
                'encoding': 'base64',
                'value': 'ODA=',
                'size': '2'
            }, {
                'name': 'REMOTE_ADDR',
                'type': 'string',
                'encoding': 'base64',
                'value': 'MTI3LjAuMC4x',
                'size': '9'
            }, {
                'name': 'DOCUMENT_ROOT',
                'type': 'string',
                'encoding': 'base64',
                'value': 'L2hvbWUvcm9iZXJ0L3d3dy9weGRlYnVn',
                'size': '24'
            }, {
                'name': 'REQUEST_SCHEME',
                'type': 'string',
                'encoding': 'base64',
                'value': 'aHR0cA==',
                'size': '4'
            }, {
                'name': 'CONTEXT_PREFIX',
                'type': 'string',
                'encoding': 'base64',
                'value': None,
                'size': '0'
            }, {
                'name': 'CONTEXT_DOCUMENT_ROOT',
                'type': 'string',
                'encoding': 'base64',
                'value': 'L2hvbWUvcm9iZXJ0L3d3dy9weGRlYnVn',
                'size': '24'
            }, {
                'name': 'SERVER_ADMIN',
                'type': 'string',
                'encoding': 'base64',
                'value': 'd2VibWFzdGVyQGxvY2FsaG9zdA==',
                'size': '19'
            }, {
                'name': 'SCRIPT_FILENAME',
                'type': 'string',
                'encoding': 'base64',
                'value': 'L2hvbWUvcm9iZXJ0L3d3dy9weGRlYnVnL2luZGV4LnBocA==',
                'size': '34'
            }, {
                'name': 'REMOTE_PORT',
                'type': 'string',
                'encoding': 'base64',
                'value': 'NTg3MDI=',
                'size': '5'
            }, {
                'name': 'GATEWAY_INTERFACE',
                'type': 'string',
                'encoding': 'base64',
                'value': 'Q0dJLzEuMQ==',
                'size': '7'
            }, {
                'name': 'SERVER_PROTOCOL',
                'type': 'string',
                'encoding': 'base64',
                'value': 'SFRUUC8xLjE=',
                'size': '8'
            }, {
                'name': 'REQUEST_METHOD',
                'type': 'string',
                'encoding': 'base64',
                'value': 'R0VU',
                'size': '3'
            }, {
                'name': 'QUERY_STRING',
                'type': 'string',
                'encoding': 'base64',
                'value': 'WERFQlVHX1NFU1NJT05fU1RBUlQ9MQ==',
                'size': '22'
            }, {
                'name': 'REQUEST_URI',
                'type': 'string',
                'encoding': 'base64',
                'value': 'Lz9YREVCVUdfU0VTU0lPTl9TVEFSVD0x',
                'size': '24'
            }, {
                'name': 'SCRIPT_NAME',
                'type': 'string',
                'encoding': 'base64',
                'value': 'L2luZGV4LnBocA==',
                'size': '10'
            }, {
                'name': 'PHP_SELF',
                'type': 'string',
                'encoding': 'base64',
                'value': 'L2luZGV4LnBocA==',
                'size': '10'
            }, {
                'name': 'REQUEST_TIME_FLOAT',
                'type': 'float',
                'value': '1425023978.289'
            }, {
                'name': 'REQUEST_TIME',
                'type': 'int',
                'value': '1425023978'
            }],
            'numchildren':
            '30'
        }]

        #self.assertEqual(expected, result)
        self.assertEqual(expected[0], result[0])
        self.assertEqual(expected[1], result[1])
        self.assertEqual(expected[2], result[2])
        self.assertEqual(expected[3], result[3])
        self.assertEqual(expected[4], result[4])
        self.assertEqual(expected[5], result[5])
        self.assertEqual(expected[6], result[6])
        self.assertEqual(expected[6]['variables'][28],
                         result[6]['variables'][28])

    def test_parse_successful_breakpoint_set_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_set" transaction_id="9" id="32310001"></response>'

        result = self.parser.parse_breakpoint_set_message(message)

        self.assertTrue(result)

    def test_parse_unsuccessful_breakpoint_set_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_set" transaction_id="9" status="break" reason="ok"><error code="3"><message><![CDATA[invalid or missing options]]></message></error></response>'

        result = self.parser.parse_breakpoint_set_message(message)

        self.assertFalse(result)

    def test_parse_successful_breakpoint_remove_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_remove" transaction_id="11"><breakpoint type="line" filename="file:///home/robert/www/pugdebug/index.php" lineno="10" state="enabled" hit_count="0" hit_value="0" id="41240003"></breakpoint></response>'

        result = self.parser.parse_breakpoint_remove_message(message)

        expected = 41240003

        self.assertEqual(expected, result)

    def test_parse_unsuccessful_breakpoint_remove_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_remove" transaction_id="11" status="break" reason="ok"><error code="205"><message><![CDATA[no such breakpoint]]></message></error></response>'

        result = self.parser.parse_breakpoint_remove_message(message)

        self.assertFalse(result)

    def test_parse_breakpoint_list_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_list" transaction_id="12"><breakpoint type="line" filename="file:///home/robert/www/pugdebug/index.php" lineno="3" state="enabled" hit_count="0" hit_value="0" id="32350002"></breakpoint><breakpoint type="line" filename="file:///home/robert/www/pugdebug/index.php" lineno="10" state="enabled" hit_count="0" hit_value="0" id="32350001"></breakpoint></response>'

        result = self.parser.parse_breakpoint_list_message(message)

        expected = [{
            'filename': '/home/robert/www/pugdebug/index.php',
            'id': '32350002',
            'lineno': '3',
            'state': 'enabled',
            'type': 'line'
        }, {
            'filename': '/home/robert/www/pugdebug/index.php',
            'id': '32350001',
            'lineno': '10',
            'state': 'enabled',
            'type': 'line'
        }]

        self.assertEqual(expected, result)

    def test_parse_stacktraces_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="118"><stack where="{main}" level="0" type="file" filename="file:///home/robert/www/pugdebug/index.php" lineno="30"></stack></response>'

        result = self.parser.parse_stacktraces_message(message)

        expected = [{
            'filename': '/home/robert/www/pugdebug/index.php',
            'lineno': '30',
            'where': '{main}',
            'level': '0'
        }]

        self.assertEqual(expected, result)

        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="22"><stack where="include_once" level="0" type="file" filename="file:///home/robert/www/pugdebug/dir/foo.php" lineno="3"></stack><stack where="include_once" level="1" type="file" filename="file:///home/robert/www/pugdebug/file.php" lineno="3"></stack><stack where="{main}" level="2" type="file" filename="file:///home/robert/www/pugdebug/index.php" lineno="3"></stack></response>'

        result = self.parser.parse_stacktraces_message(message)

        expected = [{
            'filename': '/home/robert/www/pugdebug/dir/foo.php',
            'lineno': '3',
            'where': 'include_once',
            'level': '0'
        }, {
            'filename': '/home/robert/www/pugdebug/file.php',
            'lineno': '3',
            'where': 'include_once',
            'level': '1'
        }, {
            'filename': '/home/robert/www/pugdebug/index.php',
            'lineno': '3',
            'where': '{main}',
            'level': '2'
        }]

        self.assertEqual(expected, result)

        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="54"><stack where="foo" level="0" type="file" filename="file:///home/robert/www/pugdebug/dir/foo.php" lineno="4"></stack><stack where="call_foo" level="1" type="file" filename="file:///home/robert/www/pugdebug/file.php" lineno="6"></stack><stack where="{main}" level="2" type="file" filename="file:///home/robert/www/pugdebug/index.php" lineno="34"></stack></response>'

        result = self.parser.parse_stacktraces_message(message)

        expected = [{
            'filename': '/home/robert/www/pugdebug/dir/foo.php',
            'lineno': '4',
            'where': 'foo',
            'level': '0'
        }, {
            'filename': '/home/robert/www/pugdebug/file.php',
            'lineno': '6',
            'where': 'call_foo',
            'level': '1'
        }, {
            'filename': '/home/robert/www/pugdebug/index.php',
            'lineno': '34',
            'where': '{main}',
            'level': '2'
        }]

        self.assertEqual(expected, result)
Example #5
0
class PugdebugServerConnection(QObject):

    socket = None

    mutex = None

    parser = None

    is_valid = False
    init_message = None

    transaction_id = 0

    xdebug_encoding = 'iso-8859-1'

    post_start_signal = pyqtSignal()
    stopped_signal = pyqtSignal()
    detached_signal = pyqtSignal()
    stepped_signal = pyqtSignal(dict)
    got_variables_signal = pyqtSignal(object)
    got_stacktraces_signal = pyqtSignal(object)
    set_breakpoint_signal = pyqtSignal(bool)
    removed_breakpoint_signal = pyqtSignal(object)
    listed_breakpoints_signal = pyqtSignal(list)
    expression_evaluated_signal = pyqtSignal(int, dict)
    expressions_evaluated_signal = pyqtSignal(list)

    connection_error_signal = pyqtSignal(str, str)

    def __init__(self, socket):
        super(PugdebugServerConnection, self).__init__()

        self.socket = socket

        self.mutex = QMutex()

        self.parser = PugdebugMessageParser()

    def init_connection(self):
        """Init a new connection

        Read in the init message from xdebug and decide based on the
        idekey should this connection be accepted or not.

        Do note that it is not needed to call it from a new thread, as
        it is already called from a thread separate from the main application
        thread and thus should not block the main thread.
        """
        idekey = get_setting('debugger/idekey')

        response = self.__receive_message()

        init_message = self.parser.parse_init_message(response)

        # See if the init message from xdebug is meant for us
        if idekey != '' and init_message['idekey'] != idekey:
            return False

        self.init_message = init_message

        return True

    def start(self, action, data=None):
        QThreadPool.globalInstance().start(
            PugdebugAsyncTask(self, action, data))

    def perform(self, action, data):
        self.mutex.lock()

        try:
            if action == 'post_start':
                response = self.__post_start(data)

                self.listed_breakpoints_signal.emit(response['breakpoints'])
                self.post_start_signal.emit()
            elif action == 'stop':
                response = self.__stop()
                self.stopped_signal.emit()
            elif action == 'detach':
                response = self.__detach()
                self.detached_signal.emit()
            elif action == 'step_run':
                response = self.__step_run()
                self.stepped_signal.emit(response)
            elif action == 'step_into':
                response = self.__step_into()
                self.stepped_signal.emit(response)
            elif action == 'step_over':
                response = self.__step_over()
                self.stepped_signal.emit(response)
            elif action == 'step_out':
                response = self.__step_out()
                self.stepped_signal.emit(response)
            elif action == 'post_step':
                response = self.__post_step(data)

                self.got_variables_signal.emit(response['variables'])
                self.got_stacktraces_signal.emit(response['stacktraces'])
                self.expressions_evaluated_signal.emit(response['expressions'])
            elif action == 'breakpoint_set':
                response = self.__set_breakpoint(data)
                self.set_breakpoint_signal.emit(response)
            elif action == 'breakpoint_remove':
                response = self.__remove_breakpoint(data)
                self.removed_breakpoint_signal.emit(response)
            elif action == 'breakpoint_list':
                response = self.__list_breakpoints()
                self.listed_breakpoints_signal.emit(response)
            elif action == 'evaluate_expression':
                (index, expression) = data
                response = self.__evaluate_expression(expression)
                self.expression_evaluated_signal.emit(index, response)
            elif action == 'set_debugger_features':
                self.__set_debugger_features()
        except OSError as error:
            self.disconnect()
            self.connection_error_signal.emit(action, error.strerror)

        self.mutex.unlock()

    def disconnect(self):
        if self.socket is not None:
            self.socket.close()

    def post_start_command(self, post_start_data):
        self.start('post_start', post_start_data)

    def stop(self):
        self.start('stop')

    def detach(self):
        self.start('detach')

    def step_run(self):
        self.start('step_run')

    def step_into(self):
        self.start('step_into')

    def step_over(self):
        self.start('step_over')

    def step_out(self):
        self.start('step_out')

    def post_step_command(self, post_step_data):
        self.start('post_step', post_step_data)

    def set_breakpoint(self, breakpoint):
        self.start('breakpoint_set', breakpoint)

    def remove_breakpoint(self, breakpoint_id):
        self.start('breakpoint_remove', breakpoint_id)

    def list_breakpoints(self):
        self.start('breakpoint_list')

    def evaluate_expression(self, index, expression):
        self.start('evaluate_expression', (index, expression))

    def set_debugger_features(self):
        self.start('set_debugger_features')

    def load_typemap(self):
        command = 'typemap_get -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)
        self.parser.set_typemap(self.parser.parse_typemap_message(response))

        return True

    def __post_start(self, data):
        self.__set_breakpoints(data['breakpoints'])

        post_start_response = {
            'debugger_features': self.__set_debugger_features(),
            'breakpoints': self.__list_breakpoints()
        }

        return post_start_response

    def __stop(self):
        command = 'stop -i %d' % self.__get_transaction_id()
        self.__send_command(command)

        return True

    def __detach(self):
        command = 'detach -i %d' % self.__get_transaction_id()
        self.__send_command(command)

        return True

    def __step_run(self):
        command = 'run -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __step_into(self):
        command = 'step_into -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __step_over(self):
        command = 'step_over -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __step_out(self):
        command = 'step_out -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __do_step_command(self, command):
        response = self.__send_command(command)

        response = self.parser.parse_continuation_message(response)

        return response

    def __post_step(self, data):
        post_step_response = {
            'variables': self.__get_variables(),
            'stacktraces': self.__get_stacktraces(),
            'expressions': self.__evaluate_expressions(data['expressions'])
        }

        return post_step_response

    def __get_variables(self):
        command = 'context_names -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)

        contexts = self.parser.parse_variable_contexts_message(response)

        variables = {}

        for context in contexts:
            context_id = int(context['id'])
            command = 'context_get -i %d -c %d' % (self.__get_transaction_id(),
                                                   context_id)
            response = self.__send_command(command)

            var = self.parser.parse_variables_message(response)
            variables[context['name']] = var

        return variables

    def __get_stacktraces(self):
        command = 'stack_get -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)

        stacktraces = self.parser.parse_stacktraces_message(response)

        return stacktraces

    def __set_breakpoints(self, breakpoints):
        all_successful = True

        for breakpoint in breakpoints:
            response = self.__set_breakpoint(breakpoint)
            if response is False:
                all_successful = False

        return all_successful

    def __set_breakpoint(self, breakpoint):
        command = 'breakpoint_set -i %d -t %s -f %s -n %d' % (
            self.__get_transaction_id(), 'line', breakpoint['filename'],
            int(breakpoint['lineno']))
        response = self.__send_command(command)

        return self.parser.parse_breakpoint_set_message(response)

    def __remove_breakpoint(self, breakpoint_id):
        command = 'breakpoint_remove -i %d -d %d' % (
            self.__get_transaction_id(), breakpoint_id)
        response = self.__send_command(command)

        return self.parser.parse_breakpoint_remove_message(response)

    def __list_breakpoints(self):
        command = 'breakpoint_list -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)

        breakpoints = self.parser.parse_breakpoint_list_message(response)

        return breakpoints

    def __evaluate_expressions(self, expressions):
        results = []
        for index, expression in enumerate(expressions):
            results.append(self.__evaluate_expression(expression))

        return results

    def __evaluate_expression(self, expression):
        tid = self.__get_transaction_id()
        b64 = b64encode(bytes(expression, 'UTF-8')).decode()
        command = 'eval -i %d -- %s' % (tid, b64)
        response = self.__send_command(command)

        return self.parser.parse_eval_message(response)

    def __set_debugger_features(self):
        max_depth = int(get_setting('debugger/max_depth'))
        command = 'feature_set -i %d -n max_depth -v %d' % (
            self.__get_transaction_id(), max_depth)
        self.__send_command(command)

        max_children = int(get_setting('debugger/max_children'))
        command = 'feature_set -i %d -n max_children -v %d' % (
            self.__get_transaction_id(), max_children)
        self.__send_command(command)

        max_data = int(get_setting('debugger/max_data'))
        command = 'feature_set -i %d -n max_data -v %d' % (
            self.__get_transaction_id(), max_data)
        self.__send_command(command)

        return True

    def __send_command(self, command):
        self.socket.send(bytes(command + '\0', 'utf-8'))
        return self.__receive_message()

    def __receive_message(self):
        length = self.__get_message_length()
        body = self.__get_message_body(length)

        return body

    def __get_message_length(self):
        length = ''

        while True:
            character = self.socket.recv(1)

            if self.__is_eof(character):
                self.disconnect()

            if character.isdigit():
                length = length + character.decode(self.xdebug_encoding)

            if character.decode(self.xdebug_encoding) == '\0':
                if length == '':
                    return 0
                return int(length)

    def __get_message_body(self, length):
        body = ''

        while length > 0:
            data = self.socket.recv(length)

            if self.__is_eof(data):
                self.disconnect()

            body = body + data.decode(self.xdebug_encoding)

            length = length - len(data)

        self.__get_null()

        return body

    def __get_null(self):
        while True:
            character = self.socket.recv(1)

            if self.__is_eof(character):
                self.disconnect()

            if character.decode(self.xdebug_encoding) == '\0':
                return

    def __is_eof(self, data):
        return data.decode(self.xdebug_encoding) == ''

    def __get_transaction_id(self):
        self.transaction_id += 1
        return self.transaction_id
Example #6
0
 def setUp(self):
     self.parser = PugdebugMessageParser()
Example #7
0
class PugdebugMessageParserTest(unittest.TestCase):

    #maxDiff = None

    def setUp(self):
        self.parser = PugdebugMessageParser()

    def test_parse_init_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<init xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" fileuri="file:///home/robert/www/pxdebug/index.php" language="PHP" protocol_version="1.0" appid="3696" idekey="1"><engine version="2.2.7"><![CDATA[Xdebug]]></engine><author><![CDATA[Derick Rethans]]></author><url><![CDATA[http://xdebug.org]]></url><copyright><![CDATA[Copyright (c) 2002-2015 by Derick Rethans]]></copyright></init>'

        result = self.parser.parse_init_message(message)

        expected = {
            'fileuri': '/home/robert/www/pxdebug/index.php',
            'idekey': '1',
            'engine': 'Xdebug 2.2.7',
            'author': 'Derick Rethans',
            'url': 'http://xdebug.org',
            'copyright': 'Copyright (c) 2002-2015 by Derick Rethans'
        }

        self.assertEqual(expected, result)

    def test_parse_status_break_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="step_into" transaction_id="1" status="break" reason="ok"><xdebug:message filename="file:///home/robert/www/pxdebug/index.php" lineno="3"></xdebug:message></response>'

        result = self.parser.parse_continuation_message(message)

        expected = {
            'command': 'step_into',
            'transaction_id': '1',
            'status': 'break',
            'reason': 'ok',
            'filename': '/home/robert/www/pxdebug/index.php',
            'lineno': '3'
        }

        self.assertEqual(expected, result)

    def test_parse_status_stopping_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="step_into" transaction_id="28" status="stopping" reason="ok"></response>'

        result = self.parser.parse_continuation_message(message)

        expected = {
            'command': 'step_into',
            'transaction_id': '28',
            'status': 'stopping',
            'reason': 'ok'
        }

        self.assertEqual(expected, result)

    def test_parse_variable_contexts_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="context_names" transaction_id="2"><context name="Locals" id="0"></context><context name="Superglobals" id="1"></context></response>'

        result = self.parser.parse_variable_contexts_message(message)

        expected = [
            {
                'name': 'Locals',
                'id': '0'
            },
            {
                'name': 'Superglobals',
                'id': '1'
            }
        ]

        self.assertEqual(expected, result)

    def test_parse_variables_simple_local(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="context_get" transaction_id="2;" context="0"><property name="$i" fullname="$i" type="int"><![CDATA[1]]></property></response>'

        result = self.parser.parse_variables_message(message)

        expected = [
            {
                'name': '$i',
                'type': 'int',
                'value': '1'
            }
        ]

        self.assertEqual(expected, result)

    def test_parse_variables_superglobals(self):
        file = open('./pugdebug/tests/_files/superglobals.xml', 'r')
        message = file.read()
        file.close()

        result = self.parser.parse_variables_message(message)

        expected = [
            {
                'name': '$_COOKIE',
                'type': 'array',
                'variables': [],
                'numchildren': '0'
            },
            {
                'name': '$_ENV',
                'type': 'array',
                'variables': [],
                'numchildren': '0'
            },
            {
                'name': '$_FILES',
                'type': 'array',
                'variables': [],
                'numchildren': '0'
            },
            {
                'name': '$_GET',
                'type': 'array',
                'variables': [
                    {
                        'name': 'XDEBUG_SESSION_START',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'MQ==',
                        'size': '1'
                    }
                ],
                'numchildren': '1'
            },
            {
                'name': '$_POST',
                'type': 'array',
                'variables': [],
                'numchildren': '0'
            },
            {
                'name': '$_REQUEST',
                'type': 'array',
                'variables': [
                    {
                        'name': 'XDEBUG_SESSION_START',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'MQ==',
                        'size': '1'
                    }
                ],
                'numchildren': '1'
            },
            {
                'name': '$_SERVER',
                'type': 'array',
                'variables': [
                    {
                        'name': 'UNIQUE_ID',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'VlBBajZpYWgxVGtGQGlDVzFuNzhCZ0FBQUFB',
                        'size': '27'
                    },
                    {
                        'name': 'HTTP_HOST',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'bG9jYWxob3N0',
                        'size': '9'
                    },
                    {
                        'name': 'HTTP_USER_AGENT',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'TW96aWxsYS81LjAgKFgxMTsgRmVkb3JhOyBMaW51eCB4ODZfNjQ7IHJ2OjM2LjApIEdlY2tvLzIwMTAwMTAxIEZpcmVmb3gvMzYuMA==',
                        'size': '76'
                    },
                    {
                        'name': 'HTTP_ACCEPT',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'dGV4dC9odG1sLGFwcGxpY2F0aW9uL3hodG1sK3htbCxhcHBsaWNhdGlvbi94bWw7cT0wLjksKi8qO3E9MC44',
                        'size': '63'
                    },
                    {
                        'name': 'HTTP_ACCEPT_LANGUAGE',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'ZW4tVVMsZW47cT0wLjU=',
                        'size': '14'
                    },
                    {
                        'name': 'HTTP_ACCEPT_ENCODING',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'Z3ppcCwgZGVmbGF0ZQ==',
                        'size': '13'
                    },
                    {
                        'name': 'HTTP_CONNECTION',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'a2VlcC1hbGl2ZQ==',
                        'size': '10'
                    },
                    {
                        'name': 'PATH',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'L3Vzci9sb2NhbC9zYmluOi91c3IvbG9jYWwvYmluOi91c3Ivc2JpbjovdXNyL2Jpbg==',
                        'size': '49'
                    },
                    {
                        'name': 'SERVER_SIGNATURE',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': None,
                        'size': '0'
                    },
                    {
                        'name': 'SERVER_SOFTWARE',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'QXBhY2hlLzIuNC4xMCAoRmVkb3JhKSBPcGVuU1NMLzEuMC4xay1maXBzIFBIUC81LjYuNg==',
                        'size': '52'
                    },
                    {
                        'name': 'SERVER_NAME',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'bG9jYWxob3N0',
                        'size': '9'
                    },
                    {
                        'name': 'SERVER_ADDR',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'MTI3LjAuMC4x',
                        'size': '9'
                    },
                    {
                        'name': 'SERVER_PORT',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'ODA=',
                        'size': '2'
                    },
                    {
                        'name': 'REMOTE_ADDR',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'MTI3LjAuMC4x',
                        'size': '9'
                    },
                    {
                        'name': 'DOCUMENT_ROOT',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'L2hvbWUvcm9iZXJ0L3d3dy9weGRlYnVn',
                        'size': '24'
                    },
                    {
                        'name': 'REQUEST_SCHEME',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'aHR0cA==',
                        'size': '4'
                    },
                    {
                        'name': 'CONTEXT_PREFIX',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': None,
                        'size': '0'
                    },
                    {
                        'name': 'CONTEXT_DOCUMENT_ROOT',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'L2hvbWUvcm9iZXJ0L3d3dy9weGRlYnVn',
                        'size': '24'
                    },
                    {
                        'name': 'SERVER_ADMIN',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'd2VibWFzdGVyQGxvY2FsaG9zdA==',
                        'size': '19'
                    },
                    {
                        'name': 'SCRIPT_FILENAME',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'L2hvbWUvcm9iZXJ0L3d3dy9weGRlYnVnL2luZGV4LnBocA==',
                        'size': '34'
                    },
                    {
                        'name': 'REMOTE_PORT',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'NTg3MDI=',
                        'size': '5'
                    },
                    {
                        'name': 'GATEWAY_INTERFACE',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'Q0dJLzEuMQ==',
                        'size': '7'
                    },
                    {
                        'name': 'SERVER_PROTOCOL',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'SFRUUC8xLjE=',
                        'size': '8'
                    },
                    {
                        'name': 'REQUEST_METHOD',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'R0VU',
                        'size': '3'
                    },
                    {
                        'name': 'QUERY_STRING',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'WERFQlVHX1NFU1NJT05fU1RBUlQ9MQ==',
                        'size': '22'
                    },
                    {
                        'name': 'REQUEST_URI',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'Lz9YREVCVUdfU0VTU0lPTl9TVEFSVD0x',
                        'size': '24'
                    },
                    {
                        'name': 'SCRIPT_NAME',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'L2luZGV4LnBocA==',
                        'size': '10'
                    },
                    {
                        'name': 'PHP_SELF',
                        'type': 'string',
                        'encoding': 'base64',
                        'value': 'L2luZGV4LnBocA==',
                        'size': '10'
                    },
                    {
                        'name': 'REQUEST_TIME_FLOAT',
                        'type': 'float',
                        'value': '1425023978.289'
                    },
                    {
                        'name': 'REQUEST_TIME',
                        'type': 'int',
                        'value': '1425023978'
                    }
                ],
                'numchildren': '30'
            }
        ]

        #self.assertEqual(expected, result)
        self.assertEqual(expected[0], result[0])
        self.assertEqual(expected[1], result[1])
        self.assertEqual(expected[2], result[2])
        self.assertEqual(expected[3], result[3])
        self.assertEqual(expected[4], result[4])
        self.assertEqual(expected[5], result[5])
        self.assertEqual(expected[6], result[6])
        self.assertEqual(expected[6]['variables'][28], result[6]['variables'][28])

    def test_parse_successful_breakpoint_set_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_set" transaction_id="9" id="32310001"></response>'

        result = self.parser.parse_breakpoint_set_message(message)

        self.assertTrue(result)

    def test_parse_unsuccessful_breakpoint_set_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_set" transaction_id="9" status="break" reason="ok"><error code="3"><message><![CDATA[invalid or missing options]]></message></error></response>'

        result = self.parser.parse_breakpoint_set_message(message)

        self.assertFalse(result)

    def test_parse_successful_breakpoint_remove_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_remove" transaction_id="11"><breakpoint type="line" filename="file:///home/robert/www/pugdebug/index.php" lineno="10" state="enabled" hit_count="0" hit_value="0" id="41240003"></breakpoint></response>'

        result = self.parser.parse_breakpoint_remove_message(message)

        expected = 41240003

        self.assertEqual(expected, result)

    def test_parse_unsuccessful_breakpoint_remove_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_remove" transaction_id="11" status="break" reason="ok"><error code="205"><message><![CDATA[no such breakpoint]]></message></error></response>'

        result = self.parser.parse_breakpoint_remove_message(message)

        self.assertFalse(result)

    def test_parse_breakpoint_list_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="breakpoint_list" transaction_id="12"><breakpoint type="line" filename="file:///home/robert/www/pugdebug/index.php" lineno="3" state="enabled" hit_count="0" hit_value="0" id="32350002"></breakpoint><breakpoint type="line" filename="file:///home/robert/www/pugdebug/index.php" lineno="10" state="enabled" hit_count="0" hit_value="0" id="32350001"></breakpoint></response>'

        result = self.parser.parse_breakpoint_list_message(message)

        expected = [
            {
                'filename': '/home/robert/www/pugdebug/index.php',
                'id': '32350002',
                'lineno': '3',
                'state': 'enabled',
                'type': 'line'
            },
            {
                'filename': '/home/robert/www/pugdebug/index.php',
                'id': '32350001',
                'lineno': '10',
                'state': 'enabled',
                'type': 'line'
            }
        ]

        self.assertEqual(expected, result)

    def test_parse_stacktraces_message(self):
        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="118"><stack where="{main}" level="0" type="file" filename="file:///home/robert/www/pugdebug/index.php" lineno="30"></stack></response>'

        result = self.parser.parse_stacktraces_message(message)

        expected = [
            {
                'filename': '/home/robert/www/pugdebug/index.php',
                'lineno': '30',
                'where': '{main}',
                'level': '0'
            }
        ]

        self.assertEqual(expected, result)

        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="22"><stack where="include_once" level="0" type="file" filename="file:///home/robert/www/pugdebug/dir/foo.php" lineno="3"></stack><stack where="include_once" level="1" type="file" filename="file:///home/robert/www/pugdebug/file.php" lineno="3"></stack><stack where="{main}" level="2" type="file" filename="file:///home/robert/www/pugdebug/index.php" lineno="3"></stack></response>'

        result = self.parser.parse_stacktraces_message(message)

        expected = [
            {
                'filename': '/home/robert/www/pugdebug/dir/foo.php',
                'lineno': '3',
                'where': 'include_once',
                'level': '0'
            },
            {
                'filename': '/home/robert/www/pugdebug/file.php',
                'lineno': '3',
                'where': 'include_once',
                'level': '1'
            },
            {
                'filename': '/home/robert/www/pugdebug/index.php',
                'lineno': '3',
                'where': '{main}',
                'level': '2'
            }
        ]

        self.assertEqual(expected, result)

        message = '<?xml version="1.0" encoding="iso-8859-1"?>\
<response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http://xdebug.org/dbgp/xdebug" command="stack_get" transaction_id="54"><stack where="foo" level="0" type="file" filename="file:///home/robert/www/pugdebug/dir/foo.php" lineno="4"></stack><stack where="call_foo" level="1" type="file" filename="file:///home/robert/www/pugdebug/file.php" lineno="6"></stack><stack where="{main}" level="2" type="file" filename="file:///home/robert/www/pugdebug/index.php" lineno="34"></stack></response>'

        result = self.parser.parse_stacktraces_message(message)

        expected = [
            {
                'filename': '/home/robert/www/pugdebug/dir/foo.php',
                'lineno': '4',
                'where': 'foo',
                'level': '0'
            },
            {
                'filename': '/home/robert/www/pugdebug/file.php',
                'lineno': '6',
                'where': 'call_foo',
                'level': '1'
            },
            {
                'filename': '/home/robert/www/pugdebug/index.php',
                'lineno': '34',
                'where': '{main}',
                'level': '2'
            }
        ]

        self.assertEqual(expected, result)
Example #8
0
class PugdebugServerConnection(QThread):

    socket = None

    mutex = None

    parser = None

    action = None
    data = None

    is_valid = False
    init_message = None

    transaction_id = 0

    xdebug_encoding = 'iso-8859-1'

    post_start_signal = pyqtSignal()
    stopped_signal = pyqtSignal()
    detached_signal = pyqtSignal()
    stepped_signal = pyqtSignal(dict)
    got_variables_signal = pyqtSignal(object)
    got_stacktraces_signal = pyqtSignal(object)
    set_breakpoint_signal = pyqtSignal(bool)
    removed_breakpoint_signal = pyqtSignal(object)
    listed_breakpoints_signal = pyqtSignal(list)
    expression_evaluated_signal = pyqtSignal(int, dict)
    expressions_evaluated_signal = pyqtSignal(list)

    connection_error_signal = pyqtSignal(str, str)

    def __init__(self, socket):
        super(PugdebugServerConnection, self).__init__()

        self.socket = socket

        self.mutex = QMutex()

        self.parser = PugdebugMessageParser()

    def init_connection(self):
        """Init a new connection

        Read in the init message from xdebug and decide based on the
        idekey should this connection be accepted or not.

        Do note that it is not needed to call it from a new thread, as
        it is already called from a thread separate from the main application
        thread and thus should not block the main thread.
        """
        idekey = get_setting('debugger/idekey')

        response = self.__receive_message()

        init_message = self.parser.parse_init_message(response)

        # See if the init message from xdebug is meant for us
        if idekey != '' and init_message['idekey'] != idekey:
            return False

        self.init_message = init_message

        return True

    def run(self):
        self.mutex.lock()

        data = self.data
        action = self.action

        try:
            if action == 'post_start':
                response = self.__post_start(data)

                self.listed_breakpoints_signal.emit(
                    response['breakpoints']
                )
                self.post_start_signal.emit()
            elif action == 'stop':
                response = self.__stop()
                self.stopped_signal.emit()
            elif action == 'detach':
                response = self.__detach()
                self.detached_signal.emit()
            elif action == 'step_run':
                response = self.__step_run()
                self.stepped_signal.emit(response)
            elif action == 'step_into':
                response = self.__step_into()
                self.stepped_signal.emit(response)
            elif action == 'step_over':
                response = self.__step_over()
                self.stepped_signal.emit(response)
            elif action == 'step_out':
                response = self.__step_out()
                self.stepped_signal.emit(response)
            elif action == 'post_step':
                response = self.__post_step(data)

                self.got_variables_signal.emit(response['variables'])
                self.got_stacktraces_signal.emit(response['stacktraces'])
                self.expressions_evaluated_signal.emit(
                    response['expressions']
                )
            elif action == 'breakpoint_set':
                response = self.__set_breakpoint(data)
                self.set_breakpoint_signal.emit(response)
            elif action == 'breakpoint_remove':
                response = self.__remove_breakpoint(data)
                self.removed_breakpoint_signal.emit(response)
            elif action == 'breakpoint_list':
                response = self.__list_breakpoints()
                self.listed_breakpoints_signal.emit(response)
            elif action == 'evaluate_expression':
                (index, expression) = data
                response = self.__evaluate_expression(expression)
                self.expression_evaluated_signal.emit(index, response)
            elif action == 'set_debugger_features':
                self.__set_debugger_features()
        except OSError as error:
            self.disconnect()
            self.connection_error_signal.emit(action, error.strerror)

        self.mutex.unlock()

    def disconnect(self):
        if self.socket is not None:
            self.socket.close()

    def post_start_command(self, post_start_data):
        self.data = post_start_data
        self.action = 'post_start'
        self.start()

    def stop(self):
        self.action = 'stop'
        self.start()

    def detach(self):
        self.action = 'detach'
        self.start()

    def step_run(self):
        self.action = 'step_run'
        self.start()

    def step_into(self):
        self.action = 'step_into'
        self.start()

    def step_over(self):
        self.action = 'step_over'
        self.start()

    def step_out(self):
        self.action = 'step_out'
        self.start()

    def post_step_command(self, post_step_data):
        self.data = post_step_data
        self.action = 'post_step'
        self.start()

    def set_breakpoint(self, breakpoint):
        self.action = 'breakpoint_set'
        self.data = breakpoint
        self.start()

    def remove_breakpoint(self, breakpoint_id):
        self.action = 'breakpoint_remove'
        self.data = breakpoint_id
        self.start()

    def list_breakpoints(self):
        self.action = 'breakpoint_list'
        self.start()

    def evaluate_expression(self, index, expression):
        self.action = 'evaluate_expression'
        self.data = (index, expression)
        self.start()

    def set_debugger_features(self):
        self.action = 'set_debugger_features'
        self.start()

    def __post_start(self, data):
        post_start_response = {
            'debugger_features': self.__set_debugger_features(),
            'breakpoints': self.__set_breakpoints(
                data['breakpoints']
            ),
            'breakpoints': self.__list_breakpoints()
        }

        return post_start_response

    def __stop(self):
        command = 'stop -i %d' % self.__get_transaction_id()
        self.__send_command(command)

        return True

    def __detach(self):
        command = 'detach -i %d' % self.__get_transaction_id()
        self.__send_command(command)

        return True

    def __step_run(self):
        command = 'run -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __step_into(self):
        command = 'step_into -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __step_over(self):
        command = 'step_over -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __step_out(self):
        command = 'step_out -i %d' % self.__get_transaction_id()
        return self.__do_step_command(command)

    def __do_step_command(self, command):
        response = self.__send_command(command)

        response = self.parser.parse_continuation_message(response)

        return response

    def __post_step(self, data):
        post_step_response = {
            'variables': self.__get_variables(),
            'stacktraces': self.__get_stacktraces(),
            'expressions': self.__evaluate_expressions(data['expressions'])
        }

        return post_step_response

    def __get_variables(self):
        command = 'context_names -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)

        contexts = self.parser.parse_variable_contexts_message(response)

        variables = {}

        for context in contexts:
            context_id = int(context['id'])
            command = 'context_get -i %d -c %d' % (
                self.__get_transaction_id(),
                context_id
            )
            response = self.__send_command(command)

            var = self.parser.parse_variables_message(response)
            variables[context['name']] = var

        return variables

    def __get_stacktraces(self):
        command = 'stack_get -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)

        stacktraces = self.parser.parse_stacktraces_message(response)

        return stacktraces

    def __set_breakpoints(self, breakpoints):
        all_successful = True

        for breakpoint in breakpoints:
            response = self.__set_breakpoint(breakpoint)
            if response is False:
                all_successful = False

        return all_successful

    def __set_breakpoint(self, breakpoint):
        command = 'breakpoint_set -i %d -t %s -f %s -n %d' % (
            self.__get_transaction_id(),
            'line',
            breakpoint['filename'],
            int(breakpoint['lineno'])
        )
        response = self.__send_command(command)

        return self.parser.parse_breakpoint_set_message(response)

    def __remove_breakpoint(self, breakpoint_id):
        command = 'breakpoint_remove -i %d -d %d' % (
            self.__get_transaction_id(),
            breakpoint_id
        )
        response = self.__send_command(command)

        return self.parser.parse_breakpoint_remove_message(response)

    def __list_breakpoints(self):
        command = 'breakpoint_list -i %d' % self.__get_transaction_id()
        response = self.__send_command(command)

        breakpoints = self.parser.parse_breakpoint_list_message(response)

        return breakpoints

    def __evaluate_expressions(self, expressions):
        results = []
        for index, expression in enumerate(expressions):
            results.append(
                self.__evaluate_expression(expression)
            )

        return results

    def __evaluate_expression(self, expression):
        tid = self.__get_transaction_id()
        b64 = b64encode(bytes(expression, 'UTF-8')).decode()
        command = 'eval -i %d -- %s' % (tid, b64)
        response = self.__send_command(command)

        return self.parser.parse_eval_message(response)

    def __set_debugger_features(self):
        max_depth = int(get_setting('debugger/max_depth'))
        command = 'feature_set -i %d -n max_depth -v %d' % (
            self.__get_transaction_id(),
            max_depth
        )
        response = self.__send_command(command)

        max_children = int(get_setting('debugger/max_children'))
        command = 'feature_set -i %d -n max_children -v %d' % (
            self.__get_transaction_id(),
            max_children
        )
        response = self.__send_command(command)

        max_data = int(get_setting('debugger/max_data'))
        command = 'feature_set -i %d -n max_data -v %d' % (
            self.__get_transaction_id(),
            max_data
        )
        response = self.__send_command(command)

        return True

    def __send_command(self, command):
        self.socket.send(bytes(command + '\0', 'utf-8'))
        return self.__receive_message()

    def __receive_message(self):
        length = self.__get_message_length()
        body = self.__get_message_body(length)

        return body

    def __get_message_length(self):
        length = ''

        while True:
            character = self.socket.recv(1)

            if self.__is_eof(character):
                self.disconnect()

            if character.isdigit():
                length = length + character.decode(self.xdebug_encoding)

            if character.decode(self.xdebug_encoding) == '\0':
                if length == '':
                    return 0
                return int(length)

    def __get_message_body(self, length):
        body = ''

        while length > 0:
            data = self.socket.recv(length)

            if self.__is_eof(data):
                self.disconnect()

            body = body + data.decode(self.xdebug_encoding)

            length = length - len(data)

        self.__get_null()

        return body

    def __get_null(self):
        while True:
            character = self.socket.recv(1)

            if self.__is_eof(character):
                self.disconnect()

            if character.decode(self.xdebug_encoding) == '\0':
                return

    def __is_eof(self, data):
        return data.decode(self.xdebug_encoding) == ''

    def __get_transaction_id(self):
        self.transaction_id += 1
        return self.transaction_id