JSON server and client eric7

Thu, 25 Jan 2024 14:13:36 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 25 Jan 2024 14:13:36 +0100
branch
eric7
changeset 10524
ed4fd87c4d4c
parent 10523
e4069ddd7dc7
child 10525
e2b37b8ae403

JSON server and client
- Changed the code code to secure the data with an Adler32 checksum.

src/eric7/EricNetwork/EricJsonClient.py file | annotate | diff | comparison | revisions
src/eric7/EricNetwork/EricJsonServer.py file | annotate | diff | comparison | revisions
--- a/src/eric7/EricNetwork/EricJsonClient.py	Wed Jan 24 18:52:50 2024 +0100
+++ b/src/eric7/EricNetwork/EricJsonClient.py	Thu Jan 25 14:13:36 2024 +0100
@@ -12,8 +12,10 @@
 import json
 import select
 import socket
+import struct
 import sys
 import traceback
+import zlib
 
 
 class EricJsonClient:
@@ -52,8 +54,28 @@
             "method": command,
             "params": params,
         }
-        cmd = json.dumps(commandDict) + "\n"
-        self.__connection.sendall(cmd.encode("utf8", "backslashreplace"))
+        data = json.dumps(commandDict).encode("utf8", "backslashreplace")
+        header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF)
+        self.__connection.sendall(header)
+        self.__connection.sendall(data)
+
+    def __receiveBytes(self, length):
+        """
+        Private method to receive the given length of bytes.
+
+        @param length bytes to receive
+        @type int
+        @return received bytes or None if connection closed
+        @rtype bytes
+        """
+        data = bytearray()
+        while len(data) < length:
+            newData = self.__connection.recv(length - len(data))
+            if not newData:
+                return None
+
+            data += newData
+        return data
 
     def __receiveJson(self):
         """
@@ -65,32 +87,36 @@
         @rtype tuple of (str, dict)
         """
         # step 1: receive the data
-        # The JSON RPC string is prefixed by a 9 character long length field.
-        length = self.__connection.recv(9)
-        if len(length) < 9:
-            # invalid length string received
+        header = self.__receiveBytes(struct.calcsize(b"!II"))
+        if not header:
             return None, None
 
+        length, datahash = struct.unpack(b"!II", header)
+
         length = int(length)
-        data = bytearray()
-        while len(data) < length:
-            newData = self.__connection.recv(length - len(data))
-            if not newData:
-                return None, None
-
-            data += newData
+        data = self.__receiveBytes(length)
+        if not data or zlib.adler32(data) & 0xFFFFFFFF != datahash:
+            self.sendJson(
+                "ClientException",
+                {
+                    "ExceptionType": "ProtocolError",
+                    "ExceptionValue": "The checksum of the data does not match.",
+                    "ProtocolData": data.decode("utf8", "backslashreplace"),
+                },
+            )
+            return None, None
 
         # step 2: decode and convert the data
-        line = data.decode("utf8", "backslashreplace")
+        jsonString = data.decode("utf8", "backslashreplace")
         try:
-            commandDict = json.loads(line.strip())
+            commandDict = json.loads(jsonString.strip())
         except (TypeError, ValueError) as err:
             self.sendJson(
                 "ClientException",
                 {
                     "ExceptionType": "ProtocolError",
                     "ExceptionValue": str(err),
-                    "ProtocolData": line.strip(),
+                    "ProtocolData": jsonString.strip(),
                 },
             )
             return None, None
--- a/src/eric7/EricNetwork/EricJsonServer.py	Wed Jan 24 18:52:50 2024 +0100
+++ b/src/eric7/EricNetwork/EricJsonServer.py	Thu Jan 25 14:13:36 2024 +0100
@@ -9,6 +9,8 @@
 
 import contextlib
 import json
+import struct
+import zlib
 
 from PyQt6.QtCore import (
     QCoreApplication,
@@ -155,15 +157,28 @@
         else:
             connection = self.__connection
 
-        while connection and connection.canReadLine():
-            data = connection.readLine()
-            jsonLine = bytes(data).decode("utf-8", "backslashreplace")
+        while connection and connection.bytesAvailable():
+            header = connection.read(struct.calcsize(b"!II"))
+            length, datahash = struct.unpack(b"!II", header)
 
-            # - print("JSON Server ({0}): {1}".format(self.__name, jsonLine))
+            data = bytearray()
+            while len(data) < length:
+                maxSize = length - len(data)
+                if connection.bytesAvailable() < maxSize:
+                    connection.waitForReadyRead(50)
+                data += connection.read(maxSize)
+
+            if zlib.adler32(data) & 0xFFFFFFFF != datahash:
+                # corrupted data -> discard and continue
+                continue
+
+            jsonString = data.decode("utf-8", "backslashreplace")
+
+            # - print("JSON Server ({0}): {1}".format(self.__name, jsonString))
             # - this is for debugging only
 
             try:
-                clientDict = json.loads(jsonLine.strip())
+                clientDict = json.loads(jsonString.strip())
             except (TypeError, ValueError) as err:
                 EricMessageBox.critical(
                     None,
@@ -175,7 +190,7 @@
                         """ eric bugs email address.</p>"""
                         """<p>Error: {0}</p>"""
                         """<p>Data:<br/>{1}</p>"""
-                    ).format(str(err), Utilities.html_encode(jsonLine.strip())),
+                    ).format(str(err), Utilities.html_encode(jsonString.strip())),
                     EricMessageBox.Ok,
                 )
                 return
@@ -212,8 +227,9 @@
 
         if connection is not None:
             data = cmd.encode("utf8", "backslashreplace")
-            length = "{0:09d}".format(len(data))
-            connection.write(length.encode() + data)
+            header = struct.pack(b"!II", len(data), zlib.adler32(data) & 0xFFFFFFFF)
+            connection.write(header)
+            connection.write(data)
             if flush:
                 connection.flush()
 

eric ide

mercurial