From f308898a4a74befb26da8ab889c591a69f1e7f5c Mon Sep 17 00:00:00 2001 From: Jonathan Heathcote Date: Wed, 14 Aug 2013 17:56:46 +0100 Subject: [PATCH 1/3] Make receive function only get the exact data it needs. The socket receive function originally grabbed an (essentially) arbitrary amount of data from the socket meaning that all but the first message received would be corrupt. In particular, this meant that watching for subscriptions would crash because the data read would be garbage. --- i3.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/i3.py b/i3.py index 48555d2..77c6903 100644 --- a/i3.py +++ b/i3.py @@ -127,23 +127,18 @@ class Socket(object): - path of the i3 socket. Path is retrieved from i3-wm itself via "i3.get_socket_path()" if not provided. - timeout in seconds - - chunk_size in bytes - magic_string as a safety string for i3-ipc. Set to 'i3-ipc' by default. """ magic_string = 'i3-ipc' # safety string for i3-ipc - chunk_size = 1024 # in bytes timeout = 0.5 # in seconds buffer = b'' # byte string - def __init__(self, path=None, timeout=None, chunk_size=None, - magic_string=None): + def __init__(self, path=None, timeout=None, magic_string=None): if not path: path = get_socket_path() self.path = path if timeout: self.timeout = timeout - if chunk_size: - self.chunk_size = chunk_size if magic_string: self.magic_string = magic_string # Socket initialization and connection @@ -208,12 +203,17 @@ def receive(self): successful. Returns None on failure. """ try: - data = self.socket.recv(self.chunk_size) + data = self.socket.recv(self.struct_header_size) msg_magic, msg_length, msg_type = self.unpack_header(data) + + # Sanity check + assert msg_magic == self.magic_string.encode('utf-8') \ + , "Incorrect magic string received!" + msg_size = self.struct_header_size + msg_length # Keep receiving data until the whole message gets through while len(data) < msg_size: - data += self.socket.recv(msg_length) + data += self.socket.recv(msg_size - len(data)) data = self.buffer + data return self.unpack(data) except socket.timeout: From cc5c139b93f526adf09ce8a12bdab7ae8078d14e Mon Sep 17 00:00:00 2001 From: Jonathan Heathcote Date: Wed, 9 Apr 2014 13:16:21 +0100 Subject: [PATCH 2/3] Fixed random use of utf-8 as bytes. --- i3.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/i3.py b/i3.py index 77c6903..309346d 100644 --- a/i3.py +++ b/i3.py @@ -129,7 +129,7 @@ class Socket(object): - timeout in seconds - magic_string as a safety string for i3-ipc. Set to 'i3-ipc' by default. """ - magic_string = 'i3-ipc' # safety string for i3-ipc + magic_string = str.encode('i3-ipc') # safety string for i3-ipc timeout = 0.5 # in seconds buffer = b'' # byte string @@ -145,7 +145,7 @@ def __init__(self, path=None, timeout=None, magic_string=None): self.initialize() self.connect() # Struct format initialization, length of magic string is in bytes - self.struct_header = '<%dsII' % len(self.magic_string.encode('utf-8')) + self.struct_header = '<%dsII' % len(self.magic_string) self.struct_header_size = struct.calcsize(self.struct_header) def initialize(self): @@ -168,7 +168,7 @@ def connect(self, path=None): except socket.error: raise ConnectionError(path) - def get(self, msg_type, payload=''): + def get(self, msg_type, payload=str.encode('')): """ Convenience method, calls "socket.send(msg_type, payload)" and returns data from "socket.receive()". @@ -185,7 +185,7 @@ def subscribe(self, event_type, event=None): payload = [event_type] if event: payload.append(event) - payload = json.dumps(payload) + payload = str.encode(json.dumps(payload)) return self.get('subscribe', payload) def send(self, msg_type, payload=''): @@ -207,7 +207,7 @@ def receive(self): msg_magic, msg_length, msg_type = self.unpack_header(data) # Sanity check - assert msg_magic == self.magic_string.encode('utf-8') \ + assert msg_magic == self.magic_string \ , "Incorrect magic string received!" msg_size = self.struct_header_size + msg_length @@ -226,14 +226,14 @@ def pack(self, msg_type, payload): """ msg_magic = self.magic_string # Get the byte count instead of number of characters - msg_length = len(payload.encode('utf-8')) + msg_length = len(payload) msg_type = parse_msg_type(msg_type) - # "struct.pack" returns byte string, decoding it for concatenation - msg_length = struct.pack('I', msg_length).decode('utf-8') - msg_type = struct.pack('I', msg_type).decode('utf-8') - message = '%s%s%s%s' % (msg_magic, msg_length, msg_type, payload) + # "struct.pack" returns byte string + msg_length = struct.pack('I', msg_length) + msg_type = struct.pack('I', msg_type) + message = msg_magic + msg_length + msg_type + payload # Encoding the message back to byte string - return message.encode('utf-8') + return message def unpack(self, data): """ @@ -245,8 +245,8 @@ def unpack(self, data): msg_size = self.struct_header_size + msg_length # Message shouldn't be any longer than the data if data_size >= msg_size: - payload = data[self.struct_header_size:msg_size].decode('utf-8') - payload = json.loads(payload) + payload = data[self.struct_header_size:msg_size] + payload = json.loads(payload.decode('utf-8')) self.buffer = data[msg_size:] return payload else: @@ -365,7 +365,7 @@ def __call_cmd__(cmd): output = subprocess.check_output(cmd) except subprocess.CalledProcessError as error: output = error.output - output = output.decode('utf-8') # byte string decoding + output = output # byte string decoding return output.strip() @@ -406,7 +406,7 @@ def function(*args2, **crit2): criteria.update(crit2) if criteria: msg_full = '%s %s' % (container(**criteria), msg_full) - response = msg(type, msg_full) + response = msg(type, str.encode(msg_full)) response = success(response) if isinstance(response, i3Exception): raise response @@ -505,7 +505,7 @@ def filter(tree=None, function=None, **conditions): internally). """ if tree is None: - tree = msg('get_tree') + tree = msg(str.encode('get_tree')) elif isinstance(tree, list): tree = {'list': tree} if function: From f58a7af532ff6583f47576e30232c23647ac9180 Mon Sep 17 00:00:00 2001 From: Jonathan Heathcote Date: Wed, 9 Apr 2014 16:00:27 +0100 Subject: [PATCH 3/3] Fixed missing byte-conversion. --- i3.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i3.py b/i3.py index 309346d..1020e6a 100644 --- a/i3.py +++ b/i3.py @@ -188,7 +188,7 @@ def subscribe(self, event_type, event=None): payload = str.encode(json.dumps(payload)) return self.get('subscribe', payload) - def send(self, msg_type, payload=''): + def send(self, msg_type, payload=str.encode('')): """ Sends the given message type with given message by packing them and continuously sending bytes from the packed message. @@ -384,7 +384,7 @@ def default_socket(socket=None): return __socket__ -def msg(type, message=''): +def msg(type, message=str.encode('')): """ Takes a message type and a message itself. Talks to the i3 via socket and returns the response from the socket. @@ -505,7 +505,7 @@ def filter(tree=None, function=None, **conditions): internally). """ if tree is None: - tree = msg(str.encode('get_tree')) + tree = msg('get_tree') elif isinstance(tree, list): tree = {'list': tree} if function: