Browse Source

events: fix parsing events with empty parameters

Empty parameter value is encoded as b'parameter\0\0', so we can't simply
read the data until b'\0\0', because it isn't necessary event end.
Instead, read event parts separately, according to specification.
Marek Marczykowski-Górecki 7 years ago
parent
commit
065eb036df
2 changed files with 24 additions and 15 deletions
  1. 20 13
      qubesadmin/events/__init__.py
  2. 4 2
      qubesadmin/tests/events.py

+ 20 - 13
qubesadmin/events/__init__.py

@@ -147,25 +147,32 @@ class EventsDispatcher(object):
             some_event_received = False
             while not reader.at_eof():
                 try:
-                    event_data = yield from reader.readuntil(b'\0\0')
-                    if event_data == b'1\0\0':
-                        # event with non-VM subject contains \0\0 inside of
-                        # event, need to receive rest of the data
-                        event_data += yield from reader.readuntil(b'\0\0')
+                    event_header = yield from reader.readuntil(b'\0')
+                    if event_header != b'1\0':
+                        raise qubesadmin.exc.QubesDaemonCommunicationError(
+                            'Non-event received on events connection: '
+                            + repr(event_header))
+                    subject = (yield from reader.readuntil(b'\0'))[:-1].decode(
+                        'utf-8')
+                    event = (yield from reader.readuntil(b'\0'))[:-1].decode(
+                        'utf-8')
+                    kwargs = {}
+                    while True:
+                        key = (yield from reader.readuntil(b'\0'))[:-1].decode(
+                            'utf-8')
+                        if not key:
+                            break
+                        value = (yield from reader.readuntil(b'\0'))[:-1].\
+                            decode('utf-8')
+                        kwargs[key] = value
                 except asyncio.IncompleteReadError as err:
                     if err.partial == b'':
                         break
                     else:
                         raise
 
-                if not event_data.startswith(b'1\0'):
-                    raise qubesadmin.exc.QubesDaemonCommunicationError(
-                        'Non-event received on events connection: '
-                        + repr(event_data))
-                event_data = event_data.decode('utf-8')
-                _, subject, event, *kwargs = event_data.split('\0')
-                # convert list to dict, remove last empty entry
-                kwargs = dict(zip(kwargs[:-2:2], kwargs[1:-2:2]))
+                if not subject:
+                    subject = None
                 self.handle(subject, event, **kwargs)
 
                 some_event_received = True

+ 4 - 2
qubesadmin/tests/events.py

@@ -107,7 +107,7 @@ class TC_00_Events(qubesadmin.tests.QubesTestCase):
         events = [
             b'1\0\0some-event\0arg1\0value1\0\0',
             b'1\0some-vm\0some-event\0arg1\0value1\0\0',
-            b'1\0some-vm\0some-event\0\0',
+            b'1\0some-vm\0some-event\0arg_without_value\0\0arg2\0value\0\0',
             b'1\0some-vm\0other-event\0\0',
         ]
         asyncio.ensure_future(self.send_events(stream, events))
@@ -117,7 +117,9 @@ class TC_00_Events(qubesadmin.tests.QubesTestCase):
             unittest.mock.call(None, 'some-event', arg1='value1'),
             unittest.mock.call(
                 self.app.domains['some-vm'], 'some-event', arg1='value1'),
-            unittest.mock.call(self.app.domains['some-vm'], 'some-event'),
+            unittest.mock.call(
+                self.app.domains['some-vm'], 'some-event',
+                arg_without_value='', arg2='value'),
         ])
         cleanup_func.assert_called_once_with()
         loop.close()