From 589a964698795fbb7a3fdb3529d702de1772c4fc Mon Sep 17 00:00:00 2001 From: WeDontKnow3 Date: Sat, 15 Nov 2025 09:41:13 -0300 Subject: [PATCH 1/5] Refactor server connection handling and address validation Refactor connection handling and error management in Server class. Improve address validation and registration logic. --- cmp.py | 298 ++++++++++++++++++++++----------------------------------- 1 file changed, 115 insertions(+), 183 deletions(-) diff --git a/cmp.py b/cmp.py index 0b3b062..29a4525 100644 --- a/cmp.py +++ b/cmp.py @@ -9,12 +9,6 @@ class Server: def __init__(self, host: str = '127.0.0.1', port: int = 16760, maximum_address_length: int = 24, allowed_address_characters: str = 'qwertyuiopasdfghjklzxcvbnm123456789') -> None: - """ - Sets server settings. - Arguments: - - host: The host to bind (str, default: 127.0.0.1) - - port: The port to bind (int, default: 16760) - """ self.host = host self.port = port self.addresses = {} @@ -23,102 +17,126 @@ def __init__(self, host: str = '127.0.0.1', port: int = 16760, maximum_address_l self.pool = {} async def start(self) -> None: - """ - Binds the server. - """ self.server = await asyncio.start_server( self.handle_connection, self.host, self.port ) async def handle_connection(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: - """ - Handles connections. - Arguments: - - reader: asyncio.StreamReader - - writer: asyncio.StreamWriter - """ client_address = writer.get_extra_info('peername') - while True: - data = await reader.read(1024) - if not data: - continue - try: - parsed = json.loads(data.decode()) - opcode = parsed['opcode'] - except: - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - continue - - if opcode not in OPCODES.values(): - writer.write(json.dumps({'opcode': OPCODES['UNKNOWN_OPCODE']}).encode()) - continue + try: + while True: + data = await reader.read(1024) + if not data: + break + try: + parsed = json.loads(data.decode()) + opcode = parsed.get('opcode') + except Exception: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + await writer.drain() + continue - if opcode == OPCODES['CLIENT_DISCONNECT']: - writer.write(json.dumps({'opcode': OPCODES['CONNECTION_CLOSED']}).encode()) - writer.close() - await writer.wait_closed() - return - - if opcode == OPCODES['CLIENT_INITIALIZE'] or opcode == OPCODES['PING']: - writer.write(json.dumps({'opcode': OPCODES['CONNECTION_INITIALIZED']}).encode()) - continue + if opcode not in OPCODES.values(): + writer.write(json.dumps({'opcode': OPCODES['UNKNOWN_OPCODE']}).encode()) + await writer.drain() + continue - if opcode == OPCODES['IS_AVAILABLE']: - available = await self.is_available(parsed['address']) - writer.write(json.dumps({'opcode': OPCODES['IS_AVAILABLE'], 'result': available}).encode()) - continue + if opcode == OPCODES['CLIENT_DISCONNECT']: + writer.write(json.dumps({'opcode': OPCODES['CONNECTION_CLOSED']}).encode()) + await writer.drain() + writer.close() + await writer.wait_closed() + return + + if opcode == OPCODES['CLIENT_INITIALIZE'] or opcode == OPCODES['PING']: + writer.write(json.dumps({'opcode': OPCODES['CONNECTION_INITIALIZED']}).encode()) + await writer.drain() + continue - if opcode == OPCODES['REGISTER']: - result = await self.register_address(parsed['address'], parsed['password']) - writer.write(json.dumps({'opcode': OPCODES['REGISTER'], 'result': result}).encode()) - continue + if opcode == OPCODES['IS_AVAILABLE']: + address = parsed.get('address') + if address is None: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + await writer.drain() + continue + available = await self.is_available(address) + writer.write(json.dumps({'opcode': OPCODES['IS_AVAILABLE'], 'result': available}).encode()) + await writer.drain() + continue - if opcode == OPCODES['SEND_MAIL']: - result = await self.send_mail(parsed['address'], parsed['password'], parsed['to_address'], parsed['text'], parsed['files']) - writer.write(json.dumps({'opcode': OPCODES['SEND_MAIL'], 'result': result}).encode()) - continue - - if opcode == OPCODES['GET_MAILS']: - result = await self.get_mails(parsed['address'], parsed['password']) - if result: - writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': True, 'data': result}).encode()) + if opcode == OPCODES['REGISTER']: + address = parsed.get('address') + password = parsed.get('password') + if address is None or password is None: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + await writer.drain() + continue + result = await self.register_address(address, password) + writer.write(json.dumps({'opcode': OPCODES['REGISTER'], 'result': result}).encode()) + await writer.drain() continue - writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': False}).encode()) - continue - if opcode == OPCODES['UPLOAD_FILE']: - ... - #ill do it tomorrow im tired + if opcode == OPCODES['SEND_MAIL']: + address = parsed.get('address') + password = parsed.get('password') + to_address = parsed.get('to_address') + text = parsed.get('text') + files = parsed.get('files', []) + if None in (address, password, to_address, text) or not isinstance(files, list): + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + await writer.drain() + continue + result = await self.send_mail(address, password, to_address, text, files) + writer.write(json.dumps({'opcode': OPCODES['SEND_MAIL'], 'result': result}).encode()) + await writer.drain() + continue + if opcode == OPCODES['GET_MAILS']: + address = parsed.get('address') + password = parsed.get('password') + if address is None or password is None: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + await writer.drain() + continue + result = await self.get_mails(address, password) + if result: + writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': True, 'data': result}).encode()) + await writer.drain() + continue + writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': False}).encode()) + await writer.drain() + continue + + if opcode == OPCODES['UPLOAD_FILE']: + writer.write(json.dumps({'opcode': OPCODES['UPLOAD_FILE'], 'result': False, 'message': STRINGS.get('NOT_IMPLEMENTED', 'Not implemented')}).encode()) + await writer.drain() + continue + finally: + try: + writer.close() + await writer.wait_closed() + except Exception: + pass + async def is_available(self, address: str) -> bool: - """ - Checks if address is available - Arguments: - - address: Address (str) - Returns: bool - """ - if address.lower() in self.addresses.keys(): + if not isinstance(address, str): + return {'result': False, 'message': STRINGS['ADDRESS_IS_BAD']} + al = address.lower() + if al in self.addresses.keys(): return {'result': False, 'message': STRINGS['ADDRESS_NOT_AVAILABLE']} - if len(address) > self.maximum_address_length: + if len(al) > self.maximum_address_length: return {'result': False, 'message': STRINGS['ADDRESS_TOO_LONG']} - if len(address) < 3: + if len(al) < 3: return {'result': False, 'message': STRINGS['ADDRESS_TOO_SHORT']} - for k in address: + for k in al: if k not in self.allowed_address_characters: return {'result': False, 'message': STRINGS['ADDRESS_IS_BAD']} return {'result': True, 'message': STRINGS['ADDRESS_AVAILABLE']} async def register_address(self, address: str, password: str) -> bool: - """ - Registers the address. - Arguments: - - address: Address (str, for example: someemail) - - password: Password (str, for example: verysecurepassword) - Returns: bool - """ available = await self.is_available(address) if not available['result']: return available @@ -132,35 +150,21 @@ async def register_address(self, address: str, password: str) -> bool: return {'result': True, 'message': STRINGS['REGISTER_SUCCESSFUL']} async def check_credentials(self, address: str, password: str) -> bool: - """ - Checks the credentials - Arguments: - - address: Address (str, for example: someemail) - - password: Password (str, for example: verysecurepassword) - Returns: bool - """ - if not address in self.addresses.keys(): + if not isinstance(address, str): + return False + addr = address.lower() + if addr not in self.addresses.keys(): return False - if self.addresses[address]['password'] != password: + if self.addresses[addr]['password'] != password: return False return True async def send_mail(self, address: str, password: str, to_address: str, text: str, files: list[dict]) -> bool: - """ - Sends the mail - Arguments: - - address: Address (str, for example: someemail) - - password: Password (str, for example: verysecurepassword) - - to_address: Address to send (str, for example: friendemail) - - text: Text to send (str, for example: Hey!) - - files: Files to send (list[dict], for example: [{"file_id": some_id}, ...] or [] if no files) - Returns: bool - """ is_valid = await self.check_credentials(address.lower(), password) if not is_valid: return {'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} - if to_address not in self.addresses.keys(): + if to_address.lower() not in self.addresses.keys(): return {'result': False, 'message': STRINGS['ADDRESS_NOT_FOUND']} if len(files) > 15: @@ -191,38 +195,20 @@ async def send_mail(self, address: str, password: str, to_address: str, text: st return {'result': True, 'message': STRINGS['MAIL_SENT']} async def get_mails(self, address: str, password: str) -> list[dict] | bool: - """ - Gets the mails - Arguments: - - address: Address (str, for example: someemail) - - password: Password (str, for example: verysecurepassword) - Returns: list[dict] | bool (on error) - """ is_valid = await self.check_credentials(address.lower(), password) if not is_valid: return {'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} - return json.dumps(self.addresses[address.lower()]['mails']) + return self.addresses[address.lower()]['mails'] class Client: def __init__(self, host: str, port: int) -> None: - """ - Sets connection settings. - Arguments: - - host: The host to connect (str, for example: 127.0.0.1) - - port: The port to connect (int, for example: 16760) - """ self.host = host self.port = port self.reader = None self.writer = None async def connect(self, timeout: float = 5) -> None: - """ - Connects to the server. - Arguments: - - timeout: Timeout (float, default: 5) - """ reader, writer = await asyncio.wait_for(asyncio.open_connection( self.host, self.port ), timeout=timeout) @@ -233,7 +219,7 @@ async def connect(self, timeout: float = 5) -> None: try: parsed = json.loads(result.decode()) opcode = parsed['opcode'] - except: + except Exception: raise errors.InitializingError(f"Got bad response from the server: {result}") if opcode != OPCODES['CONNECTION_INITIALIZED']: raise errors.InitializingError(f"Client was not initialized, got {opcode} instead of {OPCODES['CONNECTION_INITIALIZED']}.") @@ -242,9 +228,6 @@ async def connect(self, timeout: float = 5) -> None: self.writer = writer async def close(self) -> None: - """ - Closes the connection. - """ if not self.writer: return @@ -253,112 +236,61 @@ async def close(self) -> None: await self.writer.wait_closed() async def send_raw_message(self, message: bytes, timeout: float = 5) -> None: - """ - Sends the raw message without any response - Arguments: - - message: Message (bytes, for example: b'0x03') - - timeout: Timeout (float, default: 5) - """ if not self.writer: raise errors.InitializingError('Client was not initialized.') self.writer.write(message) await asyncio.wait_for(self.writer.drain(), timeout=timeout) - #print(f'Sent {message} to the server') async def wait_for_raw_message(self, bytes_: int = 1024, timeout: float = 5) -> bytes: - """ - Waits for the raw message. - Arguments: - - bytes_: Maximum number of bytes to get (int, default: 1024) - - timeout: Timeout in seconds (float, default: 5) - - Returns: bytes (b'' if got nothing) - """ if not self.reader: raise errors.InitializingError('Client was not initialized.') - - start_time = time.time() - while True: + try: result = await asyncio.wait_for(self.reader.read(bytes_), timeout=timeout) - if result: - break - if time.time() - start_time > timeout: - return b'' - + except asyncio.TimeoutError: + return b'' return result async def is_address_available(self, address: str, timeout: float = 5) -> bool: - """ - Checks if address is available - Arguments: - - address: Address (str) - - timeout: Timeout (float) - Returns: bool - """ await self.send_raw_message(json.dumps({'opcode': OPCODES['IS_AVAILABLE'], 'address': address}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) available = parsed['result'] - except: + except Exception: return False return available async def register_address(self, address: str, password: str, timeout: float = 5) -> bool: - """ - Checks if address is available - Arguments: - - address: Address (str) - - timeout: Timeout (float) - - password: Password (str, for example: verysecurepassword) - Returns: bool - """ await self.send_raw_message(json.dumps({'opcode': OPCODES['REGISTER'], 'address': address, 'password': password}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) result = parsed['result'] - except: + except Exception: return False return result - async def send_mail(self, address: str, password: str, to_address: str, text: str, files: list[dict] = [], timeout: float = 5) -> bool: - """ - Sends the mail - Arguments: - - address: Address (str, for example: someemail) - - password: Password (str, for example: verysecurepassword) - - to_address: Address to send (str, for example: friendemail) - - text: Text to send (str, for example: Hey!) - - files: Files to send (list[dict], for example: [{"file_id": some_id}, ...] or [] if no files) - Returns: bool - """ + async def send_mail(self, address: str, password: str, to_address: str, text: str, files: list[dict] | None = None, timeout: float = 5) -> bool: + files = files or [] await self.send_raw_message(json.dumps({'opcode': OPCODES['SEND_MAIL'], 'address': address, 'password': password, 'to_address': to_address, 'text': text, 'files': files}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) result = parsed['result'] - except: + except Exception: return False return result - async def get_mails(self, address: str, password: str, timeout: float = 5) -> list[dict] | bool: # list or None because on older py versions list[dict] | None gives error - """ - Gets the mails - Arguments: - - address: Address (str, for example: someemail) - - password: Password (str, for example: verysecurepassword) - Returns: list[dict] | bool (on error) - """ + async def get_mails(self, address: str, password: str, timeout: float = 5) -> list[dict] | bool: await self.send_raw_message(json.dumps({'opcode': OPCODES['GET_MAILS'], 'address': address, 'password': password}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) result = parsed['result'] if not result: - raise + raise Exception() mails = parsed['data'] - except: + except Exception: return False - return mails \ No newline at end of file + return mails From 3a59cea7b378ab87e4e418798a3a786b43516321 Mon Sep 17 00:00:00 2001 From: WeDontKnow3 Date: Sat, 15 Nov 2025 10:24:01 -0300 Subject: [PATCH 2/5] Refactor Server class and improve documentation Refactor connection handling and improve error management in the Server class. Update method signatures to include docstrings for better documentation. --- cmp.py | 229 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 149 insertions(+), 80 deletions(-) diff --git a/cmp.py b/cmp.py index 29a4525..188ec95 100644 --- a/cmp.py +++ b/cmp.py @@ -9,6 +9,12 @@ class Server: def __init__(self, host: str = '127.0.0.1', port: int = 16760, maximum_address_length: int = 24, allowed_address_characters: str = 'qwertyuiopasdfghjklzxcvbnm123456789') -> None: + """ + Sets server settings. + Arguments: + - host: The host to bind (str, default: 127.0.0.1) + - port: The port to bind (int, default: 16760) + """ self.host = host self.port = port self.addresses = {} @@ -17,126 +23,104 @@ def __init__(self, host: str = '127.0.0.1', port: int = 16760, maximum_address_l self.pool = {} async def start(self) -> None: + """ + Binds the server. + """ self.server = await asyncio.start_server( self.handle_connection, self.host, self.port ) async def handle_connection(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: + """ + Handles connections. + Arguments: + - reader: asyncio.StreamReader + - writer: asyncio.StreamWriter + """ client_address = writer.get_extra_info('peername') - try: - while True: + while True: + try: data = await reader.read(1024) if not data: - break - try: - parsed = json.loads(data.decode()) - opcode = parsed.get('opcode') - except Exception: - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - await writer.drain() continue + parsed = json.loads(data.decode()) + opcode = parsed['opcode'] if opcode not in OPCODES.values(): writer.write(json.dumps({'opcode': OPCODES['UNKNOWN_OPCODE']}).encode()) - await writer.drain() continue if opcode == OPCODES['CLIENT_DISCONNECT']: writer.write(json.dumps({'opcode': OPCODES['CONNECTION_CLOSED']}).encode()) - await writer.drain() writer.close() await writer.wait_closed() return if opcode == OPCODES['CLIENT_INITIALIZE'] or opcode == OPCODES['PING']: writer.write(json.dumps({'opcode': OPCODES['CONNECTION_INITIALIZED']}).encode()) - await writer.drain() continue if opcode == OPCODES['IS_AVAILABLE']: - address = parsed.get('address') - if address is None: - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - await writer.drain() - continue - available = await self.is_available(address) + available = await self.is_available(parsed['address']) writer.write(json.dumps({'opcode': OPCODES['IS_AVAILABLE'], 'result': available}).encode()) - await writer.drain() continue if opcode == OPCODES['REGISTER']: - address = parsed.get('address') - password = parsed.get('password') - if address is None or password is None: - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - await writer.drain() - continue - result = await self.register_address(address, password) + result = await self.register_address(parsed['address'], parsed['password']) writer.write(json.dumps({'opcode': OPCODES['REGISTER'], 'result': result}).encode()) - await writer.drain() continue if opcode == OPCODES['SEND_MAIL']: - address = parsed.get('address') - password = parsed.get('password') - to_address = parsed.get('to_address') - text = parsed.get('text') - files = parsed.get('files', []) - if None in (address, password, to_address, text) or not isinstance(files, list): - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - await writer.drain() - continue - result = await self.send_mail(address, password, to_address, text, files) + result = await self.send_mail(parsed['address'], parsed['password'], parsed['to_address'], parsed['text'], parsed.get('files', [])) writer.write(json.dumps({'opcode': OPCODES['SEND_MAIL'], 'result': result}).encode()) - await writer.drain() continue if opcode == OPCODES['GET_MAILS']: - address = parsed.get('address') - password = parsed.get('password') - if address is None or password is None: - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - await writer.drain() - continue - result = await self.get_mails(address, password) + result = await self.get_mails(parsed['address'], parsed['password']) if result: writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': True, 'data': result}).encode()) - await writer.drain() continue writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': False}).encode()) - await writer.drain() continue if opcode == OPCODES['UPLOAD_FILE']: - writer.write(json.dumps({'opcode': OPCODES['UPLOAD_FILE'], 'result': False, 'message': STRINGS.get('NOT_IMPLEMENTED', 'Not implemented')}).encode()) - await writer.drain() - continue - finally: - try: - writer.close() - await writer.wait_closed() + ... except Exception: - pass + try: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + except: + pass + continue async def is_available(self, address: str) -> bool: - if not isinstance(address, str): - return {'result': False, 'message': STRINGS['ADDRESS_IS_BAD']} - al = address.lower() - if al in self.addresses.keys(): + """ + Checks if address is available + Arguments: + - address: Address (str) + Returns: bool + """ + if address.lower() in self.addresses.keys(): return {'result': False, 'message': STRINGS['ADDRESS_NOT_AVAILABLE']} - if len(al) > self.maximum_address_length: + if len(address) > self.maximum_address_length: return {'result': False, 'message': STRINGS['ADDRESS_TOO_LONG']} - if len(al) < 3: + if len(address) < 3: return {'result': False, 'message': STRINGS['ADDRESS_TOO_SHORT']} - for k in al: + for k in address: if k not in self.allowed_address_characters: return {'result': False, 'message': STRINGS['ADDRESS_IS_BAD']} return {'result': True, 'message': STRINGS['ADDRESS_AVAILABLE']} async def register_address(self, address: str, password: str) -> bool: + """ + Registers the address. + Arguments: + - address: Address (str, for example: someemail) + - password: Password (str, for example: verysecurepassword) + Returns: bool + """ available = await self.is_available(address) if not available['result']: return available @@ -150,21 +134,35 @@ async def register_address(self, address: str, password: str) -> bool: return {'result': True, 'message': STRINGS['REGISTER_SUCCESSFUL']} async def check_credentials(self, address: str, password: str) -> bool: - if not isinstance(address, str): - return False - addr = address.lower() - if addr not in self.addresses.keys(): + """ + Checks the credentials + Arguments: + - address: Address (str, for example: someemail) + - password: Password (str, for example: verysecurepassword) + Returns: bool + """ + if not address in self.addresses.keys(): return False - if self.addresses[addr]['password'] != password: + if self.addresses[address]['password'] != password: return False return True async def send_mail(self, address: str, password: str, to_address: str, text: str, files: list[dict]) -> bool: + """ + Sends the mail + Arguments: + - address: Address (str, for example: someemail) + - password: Password (str, for example: verysecurepassword) + - to_address: Address to send (str, for example: friendemail) + - text: Text to send (str, for example: Hey!) + - files: Files to send (list[dict], for example: [{"file_id": some_id}, ...] or [] if no files) + Returns: bool + """ is_valid = await self.check_credentials(address.lower(), password) if not is_valid: return {'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} - if to_address.lower() not in self.addresses.keys(): + if to_address not in self.addresses.keys(): return {'result': False, 'message': STRINGS['ADDRESS_NOT_FOUND']} if len(files) > 15: @@ -195,20 +193,38 @@ async def send_mail(self, address: str, password: str, to_address: str, text: st return {'result': True, 'message': STRINGS['MAIL_SENT']} async def get_mails(self, address: str, password: str) -> list[dict] | bool: + """ + Gets the mails + Arguments: + - address: Address (str, for example: someemail) + - password: Password (str, for example: verysecurepassword) + Returns: list[dict] | bool (on error) + """ is_valid = await self.check_credentials(address.lower(), password) if not is_valid: return {'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} - return self.addresses[address.lower()]['mails'] + return json.dumps({'result': True, 'data': self.addresses[address.lower()]['mails']}) class Client: def __init__(self, host: str, port: int) -> None: + """ + Sets connection settings. + Arguments: + - host: The host to connect (str, for example: 127.0.0.1) + - port: The port to connect (int, for example: 16760) + """ self.host = host self.port = port self.reader = None self.writer = None async def connect(self, timeout: float = 5) -> None: + """ + Connects to the server. + Arguments: + - timeout: Timeout (float, default: 5) + """ reader, writer = await asyncio.wait_for(asyncio.open_connection( self.host, self.port ), timeout=timeout) @@ -219,7 +235,7 @@ async def connect(self, timeout: float = 5) -> None: try: parsed = json.loads(result.decode()) opcode = parsed['opcode'] - except Exception: + except: raise errors.InitializingError(f"Got bad response from the server: {result}") if opcode != OPCODES['CONNECTION_INITIALIZED']: raise errors.InitializingError(f"Client was not initialized, got {opcode} instead of {OPCODES['CONNECTION_INITIALIZED']}.") @@ -228,6 +244,9 @@ async def connect(self, timeout: float = 5) -> None: self.writer = writer async def close(self) -> None: + """ + Closes the connection. + """ if not self.writer: return @@ -236,6 +255,12 @@ async def close(self) -> None: await self.writer.wait_closed() async def send_raw_message(self, message: bytes, timeout: float = 5) -> None: + """ + Sends the raw message without any response + Arguments: + - message: Message (bytes, for example: b'0x03') + - timeout: Timeout (float, default: 5) + """ if not self.writer: raise errors.InitializingError('Client was not initialized.') @@ -243,54 +268,98 @@ async def send_raw_message(self, message: bytes, timeout: float = 5) -> None: await asyncio.wait_for(self.writer.drain(), timeout=timeout) async def wait_for_raw_message(self, bytes_: int = 1024, timeout: float = 5) -> bytes: + """ + Waits for the raw message. + Arguments: + - bytes_: Maximum number of bytes to get (int, default: 1024) + - timeout: Timeout in seconds (float, default: 5) + + Returns: bytes (b'' if got nothing) + """ if not self.reader: raise errors.InitializingError('Client was not initialized.') - try: + + start_time = time.time() + while True: result = await asyncio.wait_for(self.reader.read(bytes_), timeout=timeout) - except asyncio.TimeoutError: - return b'' + if result: + break + if time.time() - start_time > timeout: + return b'' + return result async def is_address_available(self, address: str, timeout: float = 5) -> bool: + """ + Checks if address is available + Arguments: + - address: Address (str) + - timeout: Timeout (float) + Returns: bool + """ await self.send_raw_message(json.dumps({'opcode': OPCODES['IS_AVAILABLE'], 'address': address}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) available = parsed['result'] - except Exception: + except: return False return available async def register_address(self, address: str, password: str, timeout: float = 5) -> bool: + """ + Checks if address is available + Arguments: + - address: Address (str) + - timeout: Timeout (float) + - password: Password (str, for example: verysecurepassword) + Returns: bool + """ await self.send_raw_message(json.dumps({'opcode': OPCODES['REGISTER'], 'address': address, 'password': password}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) result = parsed['result'] - except Exception: + except: return False return result - async def send_mail(self, address: str, password: str, to_address: str, text: str, files: list[dict] | None = None, timeout: float = 5) -> bool: - files = files or [] + async def send_mail(self, address: str, password: str, to_address: str, text: str, files: list[dict] = [], timeout: float = 5) -> bool: + """ + Sends the mail + Arguments: + - address: Address (str, for example: someemail) + - password: Password (str, for example: verysecurepassword) + - to_address: Address to send (str, for example: friendemail) + - text: Text to send (str, for example: Hey!) + - files: Files to send (list[dict], for example: [{"file_id": some_id}, ...] or [] if no files) + Returns: bool + """ await self.send_raw_message(json.dumps({'opcode': OPCODES['SEND_MAIL'], 'address': address, 'password': password, 'to_address': to_address, 'text': text, 'files': files}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) result = parsed['result'] - except Exception: + except: return False return result async def get_mails(self, address: str, password: str, timeout: float = 5) -> list[dict] | bool: + """ + Gets the mails + Arguments: + - address: Address (str, for example: someemail) + - password: Password (str, for example: verysecurepassword) + Returns: list[dict] | bool (on error) + """ await self.send_raw_message(json.dumps({'opcode': OPCODES['GET_MAILS'], 'address': address, 'password': password}).encode(), timeout=timeout) result = await self.wait_for_raw_message(1024, timeout=timeout) try: parsed = json.loads(result.decode()) result = parsed['result'] if not result: - raise Exception() + raise mails = parsed['data'] - except Exception: + except: return False return mails From 7c39c2f8c6251772e63a954898e5e5249d28ee38 Mon Sep 17 00:00:00 2001 From: WeDontKnow3 Date: Sat, 15 Nov 2025 10:31:20 -0300 Subject: [PATCH 3/5] Update test.py --- test.py | 83 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/test.py b/test.py index 2c91272..c63d24b 100644 --- a/test.py +++ b/test.py @@ -2,40 +2,61 @@ import asyncio import base64 from opcodes import OPCODES +import json +import sys async def main(): server = cmp.Server('127.0.0.1', 16760) client = cmp.Client('127.0.0.1', 16760) - await server.start() - await client.connect() - - available = await client.is_address_available('admin', 1) # True - print(available) - - result = await client.register_address('admin', 'admin', 1) # True - print(result) - - result = await client.register_address('admin2', 'admin2', 1) # True - print(result) - - with open('image.png', 'rb') as f: - image_content = f.read() - - result = await client.send_mail('admin', 'admin', 'admin2', 'Hey!', files=[{'file_id': 'image.png'}]) # True - print(result) - - - #mails = await client.get_mails('admin', 'admin') # - #print(mails) - - #await client.send_raw_message(OPCODES['PING']) - #result = await client.wait_for_raw_message(1024, 1) - #print(f'Message: {result}') - - - - - await client.close() + try: + await server.start() + await asyncio.sleep(0.05) + await client.connect() + + available = await client.is_address_available('admin', 1) + print('is "admin" available:', available) + + result = await client.register_address('admin', 'admin', 1) + print('register admin:', result) + + result = await client.register_address('admin2', 'admin2', 1) + print('register admin2:', result) + + with open('image.png', 'rb') as f: + image_content = f.read() + + result = await client.send_mail('admin', 'admin', 'admin2', 'Hey!', files=[{'file_id': 'image.png'}]) + print('send mail result:', result) + + mails_admin2 = await client.get_mails('admin2', 'admin2', 1) + print('admin2 mails raw:', mails_admin2) + try: + print('admin2 mails parsed:', json.dumps(mails_admin2, indent=2)) + except Exception: + pass + + mails_admin = await client.get_mails('admin', 'admin', 1) + print('admin mails raw:', mails_admin) + try: + print('admin mails parsed:', json.dumps(mails_admin, indent=2)) + except Exception: + pass + + await client.close() + except Exception as e: + print('Error during test run:', e, file=sys.stderr) + try: + await client.close() + except Exception: + pass + finally: + try: + srv = getattr(server, 'server', None) + if srv: + srv.close() + await srv.wait_closed() + except Exception: + pass if __name__ == '__main__': - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) From 250930e870e9300d4363bab374d54a91d768d3f1 Mon Sep 17 00:00:00 2001 From: WeDontKnow3 Date: Sat, 15 Nov 2025 10:33:12 -0300 Subject: [PATCH 4/5] Update strings.py --- strings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/strings.py b/strings.py index 36c6b1b..b50d393 100644 --- a/strings.py +++ b/strings.py @@ -6,12 +6,12 @@ 'ADDRESS_AVAILABLE': 'The address is available.', 'ADDRESS_NOT_FOUND': 'The address you provided was not found.', - 'REGISTER_SUCCESSFUL': 'The address was successfuly registered.', + 'REGISTER_SUCCESSFUL': 'The address was successfully registered.', - 'INVALID_CREDENTIALS': 'The credentials you provided are wrong.', + 'INVALID_CREDENTIALS': 'The credentials you provided are incorrect.', - 'FILES_LIMIT': 'You reached the files limit.', + 'FILES_LIMIT': 'You have reached the files limit.', 'INVALID_FILE': 'You provided invalid file data.', - 'MAIL_SENT': 'The mail was successfuly sent.', -} \ No newline at end of file + 'MAIL_SENT': 'The mail was successfully sent.', +} From 0cf1a809e7c06f134209f6e7ae35a7fb6b46c559 Mon Sep 17 00:00:00 2001 From: Sonys9 <137277008+Sonys9@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:39:11 +0300 Subject: [PATCH 5/5] Some fixes --- cmp.py | 97 ++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/cmp.py b/cmp.py index 188ec95..b035670 100644 --- a/cmp.py +++ b/cmp.py @@ -4,6 +4,7 @@ import random import string import json +import traceback from opcodes import OPCODES from strings import STRINGS @@ -44,14 +45,22 @@ async def handle_connection(self, reader: asyncio.StreamReader, writer: asyncio. data = await reader.read(1024) if not data: continue - parsed = json.loads(data.decode()) - opcode = parsed['opcode'] + try: + parsed = json.loads(data.decode()) + except: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + continue + + opcode = parsed.get('opcode') + if opcode is None: # 0 equals to False or None and it gives an error + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + continue if opcode not in OPCODES.values(): writer.write(json.dumps({'opcode': OPCODES['UNKNOWN_OPCODE']}).encode()) continue - if opcode == OPCODES['CLIENT_DISCONNECT']: + if opcode == OPCODES['CLIENT_DISCONNECT']: writer.write(json.dumps({'opcode': OPCODES['CONNECTION_CLOSED']}).encode()) writer.close() await writer.wait_closed() @@ -62,22 +71,44 @@ async def handle_connection(self, reader: asyncio.StreamReader, writer: asyncio. continue if opcode == OPCODES['IS_AVAILABLE']: - available = await self.is_available(parsed['address']) + address = parsed.get('address') + if not address: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + continue + available = await self.is_available(address.lower()) writer.write(json.dumps({'opcode': OPCODES['IS_AVAILABLE'], 'result': available}).encode()) continue if opcode == OPCODES['REGISTER']: - result = await self.register_address(parsed['address'], parsed['password']) + address = parsed.get('address') + password = parsed.get('password') + if not address or not password: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + continue + result = await self.register_address(address.lower(), password) writer.write(json.dumps({'opcode': OPCODES['REGISTER'], 'result': result}).encode()) continue if opcode == OPCODES['SEND_MAIL']: - result = await self.send_mail(parsed['address'], parsed['password'], parsed['to_address'], parsed['text'], parsed.get('files', [])) + address = parsed.get('address') + password = parsed.get('password') + to_address = parsed.get('to_address') + text = parsed.get('text') + files = parsed.get('files') + if not address or not password or not to_address or not text or not files: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + continue + result = await self.send_mail(address.lower(), password, to_address.lower(), text, files) writer.write(json.dumps({'opcode': OPCODES['SEND_MAIL'], 'result': result}).encode()) continue if opcode == OPCODES['GET_MAILS']: - result = await self.get_mails(parsed['address'], parsed['password']) + address = parsed.get('address') + password = parsed.get('password') + if not address or not password: + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + continue + result = await self.get_mails(address.lower(), password) if result: writer.write(json.dumps({'opcode': OPCODES['GET_MAILS'], 'result': True, 'data': result}).encode()) continue @@ -86,13 +117,11 @@ async def handle_connection(self, reader: asyncio.StreamReader, writer: asyncio. if opcode == OPCODES['UPLOAD_FILE']: ... - except Exception: - try: - writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) - except: - pass - continue - + #ill do it tomorrow im tired + except: + traceback.print_exc() + writer.write(json.dumps({'opcode': OPCODES['PARSE_ERROR']}).encode()) + async def is_available(self, address: str) -> bool: """ Checks if address is available @@ -100,18 +129,18 @@ async def is_available(self, address: str) -> bool: - address: Address (str) Returns: bool """ - if address.lower() in self.addresses.keys(): - return {'result': False, 'message': STRINGS['ADDRESS_NOT_AVAILABLE']} + if address in self.addresses.keys(): + return {'opcode': OPCODES['IS_AVAILABLE'], 'result': False, 'message': STRINGS['ADDRESS_NOT_AVAILABLE']} if len(address) > self.maximum_address_length: - return {'result': False, 'message': STRINGS['ADDRESS_TOO_LONG']} + return {'opcode': OPCODES['IS_AVAILABLE'], 'result': False, 'message': STRINGS['ADDRESS_TOO_LONG']} if len(address) < 3: - return {'result': False, 'message': STRINGS['ADDRESS_TOO_SHORT']} + return {'opcode': OPCODES['IS_AVAILABLE'], 'result': False, 'message': STRINGS['ADDRESS_TOO_SHORT']} for k in address: if k not in self.allowed_address_characters: return {'result': False, 'message': STRINGS['ADDRESS_IS_BAD']} - return {'result': True, 'message': STRINGS['ADDRESS_AVAILABLE']} + return {'opcode': OPCODES['IS_AVAILABLE'], 'result': True, 'message': STRINGS['ADDRESS_AVAILABLE']} async def register_address(self, address: str, password: str) -> bool: """ @@ -123,15 +152,16 @@ async def register_address(self, address: str, password: str) -> bool: """ available = await self.is_available(address) if not available['result']: + available['opcode'] = OPCODES['REGISTER'] return available - self.addresses[address.lower()] = { + self.addresses[address] = { 'password': password, 'mails': [], 'register_date': time.time(), 'admin': False } - return {'result': True, 'message': STRINGS['REGISTER_SUCCESSFUL']} + return {'opcode': OPCODES['REGISTER'], 'result': True, 'message': STRINGS['REGISTER_SUCCESSFUL']} async def check_credentials(self, address: str, password: str) -> bool: """ @@ -158,23 +188,23 @@ async def send_mail(self, address: str, password: str, to_address: str, text: st - files: Files to send (list[dict], for example: [{"file_id": some_id}, ...] or [] if no files) Returns: bool """ - is_valid = await self.check_credentials(address.lower(), password) + is_valid = await self.check_credentials(address, password) if not is_valid: - return {'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} + return {'opcode': OPCODES['SEND_MAIL'], 'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} if to_address not in self.addresses.keys(): - return {'result': False, 'message': STRINGS['ADDRESS_NOT_FOUND']} + return {'opcode': OPCODES['SEND_MAIL'], 'result': False, 'message': STRINGS['ADDRESS_NOT_FOUND']} if len(files) > 15: - return {'result': False, 'message': STRINGS['FILES_LIMIT']} + return {'opcode': OPCODES['SEND_MAIL'], 'result': False, 'message': STRINGS['FILES_LIMIT']} for file in files: if not file: continue if 'file_id' not in file.keys() or len(file.keys()) > 1: - return {'result': False, 'message': STRINGS['INVALID_FILE']} + return {'opcode': OPCODES['SEND_MAIL'], 'result': False, 'message': STRINGS['INVALID_FILE']} - self.addresses[to_address.lower()]['mails'].append({ + self.addresses[to_address]['mails'].append({ 'out': False, 'to_address': None, 'from_address': address, @@ -182,7 +212,7 @@ async def send_mail(self, address: str, password: str, to_address: str, text: st 'files': files, 'sent_at': time.time() }) - self.addresses[address.lower()]['mails'].append({ + self.addresses[address]['mails'].append({ 'out': True, 'to_address': to_address, 'from_address': None, @@ -190,7 +220,7 @@ async def send_mail(self, address: str, password: str, to_address: str, text: st 'files': files, 'sent_at': time.time() }) - return {'result': True, 'message': STRINGS['MAIL_SENT']} + return {'opcode': OPCODES['SEND_MAIL'], 'result': True, 'message': STRINGS['MAIL_SENT']} async def get_mails(self, address: str, password: str) -> list[dict] | bool: """ @@ -200,11 +230,11 @@ async def get_mails(self, address: str, password: str) -> list[dict] | bool: - password: Password (str, for example: verysecurepassword) Returns: list[dict] | bool (on error) """ - is_valid = await self.check_credentials(address.lower(), password) + is_valid = await self.check_credentials(address, password) if not is_valid: - return {'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} + return {'opcode': OPCODES['GET_MAILS'], 'result': False, 'message': STRINGS['INVALID_CREDENTIALS']} - return json.dumps({'result': True, 'data': self.addresses[address.lower()]['mails']}) + return {'opcode': OPCODES['GET_MAILS'], 'result': True, 'data': self.addresses[address]['mails']} class Client: def __init__(self, host: str, port: int) -> None: @@ -266,6 +296,7 @@ async def send_raw_message(self, message: bytes, timeout: float = 5) -> None: self.writer.write(message) await asyncio.wait_for(self.writer.drain(), timeout=timeout) + #print(f'Sent {message} to the server') async def wait_for_raw_message(self, bytes_: int = 1024, timeout: float = 5) -> bytes: """ @@ -344,7 +375,7 @@ async def send_mail(self, address: str, password: str, to_address: str, text: st return False return result - async def get_mails(self, address: str, password: str, timeout: float = 5) -> list[dict] | bool: + async def get_mails(self, address: str, password: str, timeout: float = 5) -> list[dict] | bool: # list or None because on older py versions list[dict] | None gives error """ Gets the mails Arguments: