summaryrefslogtreecommitdiff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/data.py141
-rw-r--r--shared/error.py35
-rw-r--r--shared/helper.py123
-rw-r--r--shared/state.py83
4 files changed, 382 insertions, 0 deletions
diff --git a/shared/data.py b/shared/data.py
new file mode 100644
index 0000000..11f52d4
--- /dev/null
+++ b/shared/data.py
@@ -0,0 +1,141 @@
+'''
+ A shared resource that takes care of sending
+ & receiving data from a connection. Handling
+ the security, but also the packaging of data.
+
+ Verified: 2021 February 8
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+ * Third Party Modules
+ * cryptography
+'''
+
+from shared.state import Static
+
+from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes
+from cryptography.fernet import Fernet
+import base64
+import time
+import json
+import zlib
+
+
+class Data:
+
+ BUFFER_SIZE = 81920
+
+ __SECRET = Fernet(base64.urlsafe_b64encode(PBKDF2HMAC(
+ algorithm=hashes.SHA256(), length=32,
+ salt=Static.SALT.encode(Static.ENCODING),
+ iterations=100000, backend=default_backend()
+ ).derive(Static.SECRET.encode(Static.ENCODING))))
+ __COMPRESSION_LEVEL = 6
+ __HEADER_SIZE = 10
+
+ @staticmethod
+ def send(conn, request='', serialize=True):
+ if serialize:
+ request = Data.__serialize(request)
+
+ request = Data.__compress(request)
+ request = Data.__encrypt(request)
+
+ conn.sendall('{:<{}}'.format(
+ len(request), Data.__HEADER_SIZE
+ ).encode(Static.ENCODING) + request)
+
+ @staticmethod
+ def recv(conn, server_sided=False, deserialize=True):
+ mode = [True, 0, ''.encode(Static.ENCODING)]
+
+ if server_sided:
+ timer = time.time()
+
+ while True:
+ data = conn.recv(Data.BUFFER_SIZE)
+
+ if mode[0]:
+ mode[:2] = False, int(data[:Data.__HEADER_SIZE])
+
+ mode[2] += data
+
+ if len(mode[2]) - Data.__HEADER_SIZE == mode[1]:
+ response = mode[2][Data.__HEADER_SIZE:]
+ response = Data.__decrypt(response)
+ response = Data.__decompress(response)
+
+ if deserialize:
+ response = Data.__deserialize(response)
+
+ return response
+
+ if server_sided:
+ if time.time() - timer > Static.TIMEOUT:
+ raise TimeoutError('Client Response Timeout')
+
+ @staticmethod
+ def parse(message, **kwargs):
+ headers = {
+ 'status': None,
+ 'raw': False,
+ 'end': False
+ }
+
+ for header in headers:
+ if header in kwargs:
+ headers.update({header: kwargs[header]})
+ del kwargs[header]
+
+ return {
+ 'message': message,
+ 'headers': headers,
+ **kwargs
+ }
+
+ @staticmethod
+ def lower(request, include_original=True):
+ message = request['message']
+
+ if include_original:
+ return (message, message.lower())
+ else:
+ return message.lower()
+
+ @staticmethod
+ def message(data=''):
+ return {'message': data}
+
+ @staticmethod
+ def b64encode(data):
+ return base64.b64encode(data).decode(Static.ENCODING)
+
+ @staticmethod
+ def b64decode(data):
+ return base64.b64decode(data.encode(Static.ENCODING))
+
+ @staticmethod
+ def __serialize(data):
+ return json.dumps(data).encode(Static.ENCODING)
+
+ @staticmethod
+ def __deserialize(data):
+ return json.loads(data.decode(Static.ENCODING))
+
+ @staticmethod
+ def __compress(data):
+ return zlib.compress(data, Data.__COMPRESSION_LEVEL)
+
+ @staticmethod
+ def __decompress(data):
+ return zlib.decompress(data)
+
+ @staticmethod
+ def __encrypt(data):
+ return Data.__SECRET.encrypt(data)
+
+ @staticmethod
+ def __decrypt(data):
+ return Data.__SECRET.decrypt(data)
diff --git a/shared/error.py b/shared/error.py
new file mode 100644
index 0000000..c116d3e
--- /dev/null
+++ b/shared/error.py
@@ -0,0 +1,35 @@
+'''
+ Error decorators to not disrupt the
+ general flow of either client or server
+ during runtime.
+
+ Verified: 2021 February 8
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+import sys
+
+
+class Error:
+
+ @staticmethod
+ def quiet(callback):
+ def wrapper(*args, **kwargs):
+ try:
+ return callback(*args, **kwargs)
+ except Exception:
+ pass
+
+ return wrapper
+
+ @staticmethod
+ def quiet_thread(callback):
+ def wrapper(*args, **kwargs):
+ try:
+ callback(*args, **kwargs)
+ except Exception:
+ sys.exit()
+
+ return wrapper
diff --git a/shared/helper.py b/shared/helper.py
new file mode 100644
index 0000000..a421ee0
--- /dev/null
+++ b/shared/helper.py
@@ -0,0 +1,123 @@
+'''
+ Very commonly used methods supporting
+ most things, starting threads, writing
+ or reading files or executing a program,
+ things that both the client & server do.
+
+ Verified: 2021 February 8
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+'''
+
+from shared.state import Static
+from shared.error import Error
+
+import subprocess
+import threading
+import tempfile
+import shutil
+import time
+import os
+
+
+class Store:
+
+ def __init__(self, **kwargs):
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+
+class Helper:
+
+ WRITE_BYTES = 'wb'
+ READ_BYTES = 'rb'
+ APPEND = 'a'
+ WRITE = 'w'
+ READ = 'r'
+
+ @staticmethod
+ @Error.quiet
+ def write_file(filepath, data, mode=APPEND):
+ if mode == Helper.WRITE_BYTES:
+ with open(filepath, mode=mode) as wf:
+ wf.write(data)
+ else:
+ with open(filepath, mode=mode,
+ encoding=Static.ENCODING,
+ errors=Static.ERRORS) as wf:
+ wf.write(data)
+
+ @staticmethod
+ @Error.quiet
+ def read_file(filepath, mode=READ):
+ if mode == Helper.READ_BYTES:
+ with open(filepath, mode=mode) as rf:
+ return rf.read()
+ else:
+ with open(filepath, mode=mode,
+ encoding=Static.ENCODING) as rf:
+ return rf.read()
+
+ @staticmethod
+ def clear_pyinstaller_temp():
+ if Static.EXE:
+ temp_dir = tempfile.gettempdir()
+
+ for filename in os.listdir(temp_dir):
+ if filename.startswith('_MEI'):
+ if filename != Static.MEI:
+ try:
+ shutil.rmtree(os.path.join(
+ temp_dir, filename), True)
+ except Exception:
+ pass
+
+ @staticmethod
+ def store(dictionary, keys):
+ for key in keys:
+ if key not in dictionary:
+ dictionary[key] = False
+
+ return (dictionary, Store(**dictionary))
+
+ @staticmethod
+ def run(args, shell=False):
+ process = subprocess.run(args, shell=shell,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL)
+
+ if process.returncode == 0:
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def start(filepath):
+ if Static.WINDOWS:
+ os.startfile(filepath)
+ elif Static.MAC:
+ Helper.run(('open', filepath))
+ else:
+ Helper.run(('xdg-open', filepath))
+
+ @staticmethod
+ def thread(callback, *args):
+ threading.Thread(target=callback,
+ args=args, daemon=True).start()
+
+ @staticmethod
+ def timestamp(date=time):
+ return date.strftime('%Y-%m-%d (%H:%M:%S)')
+
+ @staticmethod
+ def plural(iterable, end='s'):
+ if len(iterable) == 1:
+ return ''
+ else:
+ return end
+
+ @staticmethod
+ def join(*args):
+ return '\n'.join(args)
diff --git a/shared/state.py b/shared/state.py
new file mode 100644
index 0000000..7ac848f
--- /dev/null
+++ b/shared/state.py
@@ -0,0 +1,83 @@
+'''
+ Global variables that will guide important parts
+ of the execution behavior during runtime, for
+ both the client & server.
+
+ Verified: 2021 February 8
+ * Follows PEP8
+ * Tested Platforms
+ * Windows 10
+
+ CONSTANT : GUI dependence is for
+ INFO, SUCCESS, WARNING & DANGER.
+'''
+
+import platform
+import sys
+import os
+
+
+class Static:
+
+ IP = '127.0.0.1'
+ PORT = 5658
+ TIMEOUT = 45
+ LIVE_TIMEOUT = 15
+
+ WINDOWS = False
+ LINUX = False
+ MAC = False
+
+ INFO = 'INFO'
+ SUCCESS = 'SUCCESS'
+ WARNING = 'WARNING'
+ DANGER = 'DANGER'
+
+ ENCODING = 'utf-8'
+ ERRORS = 'replace'
+ RAW = 'raw:'
+
+ SECRET = '45799733-250a-4995-9d6c-998b1670929f'
+ SALT = '88fe3fdc-3009-4aad-a2c9-dd6e444c0986'
+
+ INTERVAL = '6dcd731d-3448-4b0c-8f11-2bee3accb024'
+ ALIVE = '46b700f2-1648-4935-9d2e-063d856609ae'
+
+ DISCONNECT = 'e97a46ad-b758-41c5-80e4-5473a169f6ea'
+ UNINSTALL = '22323c5d-1217-493d-90a6-bcbc84fcc3d5'
+ RECONNECT = '06a61bcc-b3ea-4a42-a543-73ea3a42a4fc'
+
+ @classmethod
+ def setup(cls):
+ system = platform.system()
+
+ if system == 'Windows':
+ cls.WINDOWS = True
+ elif system == 'Linux':
+ cls.LINUX = True
+ elif system == 'Darwin':
+ cls.MAC = True
+ else:
+ raise OSError
+
+ forward, backward = '/', '\\'
+ filepath = sys.argv[0]
+
+ if cls.WINDOWS:
+ if forward in filepath:
+ filepath = filepath.replace(forward, backward)
+ else:
+ if backward in filepath:
+ filepath = filepath.replace(backward, forward)
+
+ if os.path.isabs(filepath):
+ cls.ROOT_DIR, cls.ROOT = os.path.split(filepath)
+ else:
+ cls.ROOT_DIR, cls.ROOT = os.path.split(
+ os.path.abspath(filepath))
+
+ cls.ROOT = os.path.join(cls.ROOT_DIR, cls.ROOT)
+ cls.EXE = getattr(sys, 'frozen', False)
+
+ if cls.EXE:
+ cls.MEI = os.path.split(sys._MEIPASS)[1]