Add a protocol version to IPC.

Fixes #909.
This commit is contained in:
Florian Bruhin 2015-09-06 18:43:24 +02:00
parent b95fd2c814
commit bfd8faafef
2 changed files with 65 additions and 25 deletions

View File

@ -36,6 +36,7 @@ from qutebrowser.utils import log, usertypes, error, objreg
CONNECT_TIMEOUT = 100
WRITE_TIMEOUT = 1000
READ_TIMEOUT = 5000
PROTOCOL_VERSION = 1
def _get_socketname(basedir, user=None):
@ -228,6 +229,13 @@ class IPCServer(QObject):
# Maybe another connection is waiting.
self.handle_connection()
def _handle_invalid_data(self):
"""Handle invalid data we got from a QLocalSocket."""
log.ipc.error("Ignoring invalid IPC data.")
self.got_invalid_data.emit()
self._socket.error.connect(self.on_error)
self._socket.disconnectFromServer()
@pyqtSlot()
def on_ready_read(self):
"""Read json data from the client."""
@ -241,35 +249,44 @@ class IPCServer(QObject):
data = bytes(self._socket.readLine())
self.got_raw.emit(data)
log.ipc.debug("Read from socket: {}".format(data))
try:
decoded = data.decode('utf-8')
except UnicodeDecodeError:
log.ipc.error("Ignoring invalid IPC data.")
log.ipc.debug("invalid data: {}".format(
log.ipc.error("invalid utf-8: {}".format(
binascii.hexlify(data)))
self.got_invalid_data.emit()
self._socket.error.connect(self.on_error)
self._socket.disconnectFromServer()
self._handle_invalid_data()
return
log.ipc.debug("Processing: {}".format(decoded))
try:
json_data = json.loads(decoded)
except ValueError:
log.ipc.error("Ignoring invalid IPC data.")
log.ipc.debug("invalid json: {}".format(decoded.strip()))
self.got_invalid_data.emit()
self._socket.error.connect(self.on_error)
self._socket.disconnectFromServer()
log.ipc.error("invalid json: {}".format(decoded.strip()))
self._handle_invalid_data()
return
try:
args = json_data['args']
except KeyError:
log.ipc.error("Ignoring invalid IPC data.")
log.ipc.debug("no args: {}".format(decoded.strip()))
self.got_invalid_data.emit()
self._socket.error.connect(self.on_error)
self._socket.disconnectFromServer()
log.ipc.error("no args: {}".format(decoded.strip()))
self._handle_invalid_data()
return
try:
protocol_version = int(json_data['protocol_version'])
except (KeyError, ValueError):
log.ipc.error("invalid version: {}".format(decoded.strip()))
self._handle_invalid_data()
return
if protocol_version != PROTOCOL_VERSION:
log.ipc.error("incompatible version: expected {}, "
"got {}".format(
PROTOCOL_VERSION, protocol_version))
self._handle_invalid_data()
return
cwd = json_data.get('cwd', None)
self.got_args.emit(args, cwd)
@ -310,7 +327,8 @@ def send_to_running_instance(socketname, command, *, socket=None):
connected = socket.waitForConnected(100)
if connected:
log.ipc.info("Opening in existing instance")
json_data = {'args': command, 'version': qutebrowser.__version__}
json_data = {'args': command, 'version': qutebrowser.__version__,
'protocol_version': PROTOCOL_VERSION}
try:
cwd = os.getcwd()
except OSError:

View File

@ -296,7 +296,9 @@ class TestHandleConnection:
assert msg in all_msgs
def test_read_line_immediately(self, qtbot, ipc_server, caplog):
socket = FakeSocket(data=b'{"args": ["foo"]}\n')
data = '{{"args": ["foo"], "protocol_version": {}}}\n'.format(
ipc.PROTOCOL_VERSION)
socket = FakeSocket(data=data.encode('utf-8'))
ipc_server._server = FakeServer(socket)
@ -333,28 +335,47 @@ def test_partial_line(connected_socket):
connected_socket.write(b'foo')
@pytest.mark.parametrize('data', [
b'\x80\n', # invalid UTF8
b'\n',
b'{"is this invalid json?": true\n',
b'{"valid json without args": true}\n',
OLD_VERSION = str(ipc.PROTOCOL_VERSION - 1).encode('utf-8')
NEW_VERSION = str(ipc.PROTOCOL_VERSION + 1).encode('utf-8')
@pytest.mark.parametrize('data, msg', [
(b'\x80\n', 'invalid utf-8'),
(b'\n', 'invalid json'),
(b'{"is this invalid json?": true\n', 'invalid json'),
(b'{"valid json without args": true}\n', 'no args'),
(b'{"args": [], "protocol_version": ' + OLD_VERSION + b'}\n',
'incompatible version'),
(b'{"args": [], "protocol_version": ' + NEW_VERSION + b'}\n',
'incompatible version'),
(b'{"args": [], "protocol_version": "foo"}\n', 'invalid version'),
(b'{"args": []}\n', 'invalid version'),
])
def test_invalid_data(qtbot, ipc_server, connected_socket, caplog, data):
def test_invalid_data(qtbot, ipc_server, connected_socket, caplog, data, msg):
got_args_spy = QSignalSpy(ipc_server.got_args)
signals = [ipc_server.got_invalid_data, connected_socket.disconnected]
with caplog.atLevel(logging.ERROR):
with qtbot.waitSignals(signals, raising=True):
connected_socket.write(data)
messages = [r.message for r in caplog.records()]
assert messages[-1] == 'Ignoring invalid IPC data.'
assert messages[-2].startswith(msg)
assert not got_args_spy
def test_multiline(qtbot, ipc_server, connected_socket):
spy = QSignalSpy(ipc_server.got_args)
error_spy = QSignalSpy(ipc_server.got_invalid_data)
data = ('{{"args": ["one"], "protocol_version": {version}}}\n'
'{{"args": ["two"], "protocol_version": {version}}}\n'.format(
version=ipc.PROTOCOL_VERSION))
with qtbot.waitSignals([ipc_server.got_args, ipc_server.got_args],
raising=True):
connected_socket.write(b'{"args": ["one"]}\n{"args": ["two"]}\n')
connected_socket.write(data.encode('utf-8'))
assert len(spy) == 2
assert not error_spy
@ -396,7 +417,8 @@ class TestSendToRunningInstance:
assert len(raw_spy) == 1
assert len(raw_spy[0]) == 1
raw_expected = {'args': ['foo'], 'version': qutebrowser.__version__}
raw_expected = {'args': ['foo'], 'version': qutebrowser.__version__,
'protocol_version': ipc.PROTOCOL_VERSION}
if has_cwd:
raw_expected['cwd'] = str(tmpdir)
parsed = json.loads(raw_spy[0][0].decode('utf-8'))