diff options
Diffstat (limited to 'shared')
-rw-r--r-- | shared/data.py | 141 | ||||
-rw-r--r-- | shared/error.py | 35 | ||||
-rw-r--r-- | shared/helper.py | 123 | ||||
-rw-r--r-- | shared/state.py | 83 |
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] |