def test_flag_happy_path(logged_in_client: IMAPClient, random_mail, logger, caplog): mark_processed(logged_in_client, random_mail, logger) assert random_mail in logged_in_client.get_flags(random_mail) assert FLAG_TO_CHECK in logged_in_client.get_flags(random_mail).get( random_mail) assert f"Flag added to {random_mail}\n" in caplog.text
def test_flag_if_already_exists(logged_in_client: IMAPClient, flagged_random_mail, logger, caplog): mark_processed(logged_in_client, flagged_random_mail, logger) assert FLAG_TO_CHECK in logged_in_client.get_flags( flagged_random_mail).get(flagged_random_mail) assert not caplog.text
def flagged_random_mail(logged_in_client: IMAPClient, random_mail): logged_in_client.select_folder(EmailFolders.INBOX) logged_in_client.add_flags(random_mail, FLAG_TO_CHECK) assert FLAG_TO_CHECK in logged_in_client.get_flags(random_mail).get( random_mail) # no need to cleanup, as the message will get deleted anyway return random_mail
def test_flag_if_already_exists_with_not_existing_mail_id( logged_in_client: IMAPClient, random_mail, logger, caplog, teardown): mail_uid = random_mail + 10 mark_processed(logged_in_client, mail_uid, logger) assert not logged_in_client.get_flags(mail_uid) assert f"Mail with uid: {mail_uid} does not exist" in caplog.text
def mark_processed(client: IMAPClient, uid: int, logger: logging.Logger): """Add flags to not check the emails again.""" client.select_folder(EmailFolders.INBOX) flags = client.get_flags(uid) if uid not in flags: logger.warn("Mail with uid: %s does not exist", uid) return if FLAG_TO_CHECK not in flags.get(uid): flags = client.add_flags(uid, FLAG_TO_CHECK) if uid not in flags: logger.warn( "Mail (uid: %s) could not added " "Weights might get updated twice", uid) else: logger.info("Flag added to %s", uid)
def test_email_shift(logged_in_client: IMAPClient, random_folder, random_mail, logger, teardown, caplog): next_uid = logged_in_client.select_folder(random_folder, readonly=True)[b"UIDNEXT"] shift_mail( client=logged_in_client, uid=random_mail, source=EmailFolders.INBOX, destination=random_folder, logger=logger, ) assert f"Email (uid: {random_mail}) moved to {random_folder} folder." in caplog.text logged_in_client.select_folder(random_folder, readonly=True) assert next_uid in logged_in_client.search("ALL") assert SEEN_FLAG not in logged_in_client.get_flags(next_uid)[next_uid] logged_in_client.select_folder(EmailFolders.INBOX, readonly=True) assert random_mail not in logged_in_client.search("ALL")
class IMAP(): """ Central class for IMAP server communication """ Retval = namedtuple('Retval', 'code data') def __init__(self, logger, username, password, server='localhost', port=143, starttls=False, imaps=False, tlsverify=True, test=False, timeout=None): self.logger = logger self.username = username self.password = password self.server = server self.port = port self.imaps = imaps self.starttls = starttls self.timeout = timeout self.sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) # TODO add proto arg if tlsverify: self.sslcontext.verify_mode = ssl.CERT_REQUIRED else: self.sslcontext.verify_mode = ssl.CERT_NONE # TODO improve? self.test = test self.conn = None def do_select_mailbox(func): """ Decorator to do a fresh mailbox SELECT """ def wrapper(*args, **kwargs): if len(args) != 1: raise AttributeError( 'Size of *args tuple "{0}" isn\'t 1. It looks like you haven\'t specified all ' 'method arguments as named arguments!'.format( args)) mailbox = None for key in ['mailbox', 'source']: if key in kwargs.keys(): mailbox = kwargs[key] break if mailbox is None: raise KeyError('Unable to SELECT a mailbox, kwargs "{0}" doesn\'t contain a mailbox name'.format(kwargs)) result = args[0].select_mailbox(mailbox) if not result.code: raise RuntimeError(result.data) return func(*args, **kwargs) return wrapper def process_error(self, exception, simple_return=False): """ Process Python exception by logging a message and optionally showing traceback """ trace_info = exc_info() err_msg = str(exception) if isinstance(exception, IMAPClient.Error): err_msg = Helper().byte_to_str(exception.args[0]) self.logger.error('Catching IMAP exception %s: %s', type(exception), err_msg) if self.logger.isEnabledFor(loglevel_DEBUG): print_exception(*trace_info) del trace_info if simple_return: return exception else: return self.Retval(False, err_msg) def connect(self, retry=True, logout=False): """ Connect to IMAP server and login """ if self.starttls: self.logger.debug('Establishing IMAP connection using STARTTLS/%s to %s and logging in with user %s', self.port, self.server, self.username) elif self.imaps: self.logger.debug('Establishing IMAP connection using SSL/%s (imaps) to %s and logging in with user %s', self.port, self.server, self.username) login = '' err_return = None try: self.conn = IMAPClient(host=self.server, port=self.port, use_uid=True, ssl=self.imaps, ssl_context=self.sslcontext, timeout=self.timeout) if self.starttls: self.conn.starttls(ssl_context=self.sslcontext) login = self.conn.login(self.username, self.password) login_response = Helper().byte_to_str(login) # Test login/auth status login_success = False noop = self.noop() if noop.code and noop.data: login_success = True if logout: return self.disconnect() elif login_success: return self.Retval(True, login_response) else: return self.Retval(False, login_response) # pragma: no cover except Exception as e: err_return = self.process_error(e) if err_return.data == '[AUTHENTICATIONFAILED] Authentication failed.': return err_return if retry: self.logger.error('Trying one more time to login') sleep(2) return self.connect(retry=False, logout=logout) return err_return def noop(self): """ Do a noop to test login status """ try: noop = self.conn.noop() noop_response = Helper().byte_to_str(noop[0]) noop_resp_pattern_re = regex_compile('^(Success|NOOP completed)') login_success = noop_resp_pattern_re.match(noop_response) return self.Retval(True, login_success) except IMAPClient.Error as e: return self.process_error(e) def disconnect(self): """ Disconnect from IMAP server """ result = self.conn.logout() response = Helper().byte_to_str(result) return self.Retval(response == 'Logging out', response) def list_mailboxes(self, directory='', pattern='*'): """ Get a listing of folders (mailboxes) on the server """ try: raw_list = self.conn.list_folders(directory, pattern) nice_list = [] for mailbox in raw_list: flags = [] for flag in mailbox[0]: flags.append(flag.decode('utf-8')) nice_list.append({'name': mailbox[2], 'flags': flags, 'delimiter': mailbox[1].decode("utf-8")}) return self.Retval(True, nice_list) except IMAPClient.Error as e: return self.process_error(e) def select_mailbox(self, mailbox): """ Select a mailbox to work on """ self.logger.debug('Switching to mailbox %s', mailbox) try: result = self.conn.select_folder(mailbox) response = {} for key, value in result.items(): unicode_key = Helper().byte_to_str(key) if unicode_key == 'FLAGS': flags = [] for flag in value: flags.append(Helper().byte_to_str(flag)) response[unicode_key] = tuple(flags) else: response[unicode_key] = value return self.Retval(True, response) except IMAPClient.Error as e: return self.process_error(e) def add_mail(self, mailbox, message, flags=(), msg_time=None): """ Add/append a mail to a mailbox """ self.logger.debug('Adding a mail into mailbox %s', mailbox) try: if not isinstance(message, Mail): message = Mail(logger=self.logger, mail_native=message) message_native = message.get_native() #self.conn.append(mailbox, message, flags, msg_time) self._append(mailbox, str(message_native), flags, msg_time) # According to rfc4315 we must not return the UID from the response, so we are fetching it ourselves uids = self.search_mails(mailbox=mailbox, criteria='HEADER Message-Id "{0}"'.format(message.get_header('Message-Id'))).data[0] return self.Retval(True, uids) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def search_mails(self, mailbox, criteria='ALL', autocreate_mailbox=False): """ Search for mails in a mailbox """ self.logger.debug('Searching for mails in mailbox %s and criteria=\'%s\'', mailbox, criteria) try: return self.Retval(True, list(self.conn.search(criteria=criteria))) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def fetch_mails(self, uids, mailbox, return_fields=None): """ Retrieve mails from a mailbox """ self.logger.debug('Fetching mails with uids %s', uids) return_raw = True if return_fields is None: return_raw = False return_fields = [b'RFC822'] mails = {} try: for uid in uids: result = self.conn.fetch(uid, return_fields) if not result: continue if return_raw: mails[uid] = result[uid] else: #mails[uid] = Mail(logger=self.logger, uid=uid, mail_native=email.message_from_bytes(result[uid][b'RFC822'])) mails[uid] = Mail(logger=self.logger, mail_native=email.message_from_bytes(result[uid][b'RFC822'])) return self.Retval(True, mails) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def get_mailflags(self, uids, mailbox): """ Retrieve flags from mails """ try: result = self.conn.get_flags(uids) flags = {} for uid in uids: flags[uid] = [] if uid not in result.keys(): self.logger.error('Failed to get flags for mail with uid=%s: %s', uid, result) return self.Retval(False, None) for flag in result[uid]: flags[uid].append(flag.decode('utf-8')) return self.Retval(True, flags) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def set_mailflags(self, uids, mailbox, flags=[]): """ Set and retrieve flags from mails """ if self.test: self.logger.info('Would have set mail flags on message uids "%s"', str(uids)) return self.Retval(True, None) else: self.logger.debug('Setting flags=%s on mails uid=%s', flags, uids) try: result = self.conn.set_flags(uids, flags) _flags = {} for uid in uids: _flags[uid] = [] if uid not in result.keys(): self.logger.error('Failed to set and get flags for mail with uid=%s: %s', uid, result) return self.Retval(False, None) for flag in result[uid]: _flags[uid].append(flag.decode('utf-8')) return self.Retval(True, _flags) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def add_mailflags(self, uids, mailbox, flags=[]): """ Add and retrieve flags from mails """ if self.test: self.logger.info('Would have added mail flags on message uids "%s"', str(uids)) return self.Retval(True, None) else: self.logger.debug('Adding flags=%s on mails uid=%s', flags, uids) try: result = self.conn.add_flags(uids, flags) _flags = {} for uid in uids: _flags[uid] = [] if uid not in result.keys(): self.logger.error('Failed to add and get flags for mail with uid=%s: %s', uid, result) return self.Retval(False, None) for flag in result[uid]: _flags[uid].append(flag.decode('utf-8')) return self.Retval(True, _flags) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def move_mail(self, message_ids, source, destination, delete_old=True, expunge=True, add_flags=None, set_flags=None): """ Move a mail from a mailbox to another """ return self.copy_mails(message_ids=message_ids, source=source, destination=destination, delete_old=delete_old, expunge=expunge, add_flags=add_flags, set_flags=set_flags) @do_select_mailbox def copy_mails(self, source, destination, message_ids=None, delete_old=False, expunge=False, add_flags=None, set_flags=None): """ Copies one or more mails from a mailbox into another """ if self.test: if delete_old: self.logger.info('Would have moved mail Message-Ids="%s" from "%s" to "%s", skipping because of beeing in testmode', message_ids, source, destination) else: self.logger.info('Would have copied mails with Message-Ids="%s" from "%s" to "%s", skipping because of beeing in testmode', message_ids, source, destination) return self.Retval(True, None) else: try: if delete_old: self.logger.debug('Moving mail Message-Ids="%s" from "%s" to "%s"', message_ids, source, destination) else: self.logger.debug('Copying mail Message-Ids="%s" from "%s" to "%s"', message_ids, source, destination) #if message_ids is None: # message_ids = [] # result = self.fetch_mails(uids=uids, mailbox=source) # if not result.code: # self.logger.error('Failed to determine Message-Id by uids for mail with uids "%s"', uids) # return result # message_ids.append(result.data.keys()) if not self.mailbox_exists(destination).data: self.logger.info('Destination mailbox %s doesn\'t exist, creating it for you', destination) result = self.create_mailbox(mailbox=destination) if not result.code: self.logger.error('Failed to create the mailbox %s: %s', source, result.data) # pragma: no cover return result # pragma: no cover uids = [] for message_id in message_ids: result = self.search_mails(mailbox=source, criteria='HEADER Message-Id "{0}"'.format(message_id)) if not result.code or len(result.data) == 0: self.logger.error('Failed to determine uid by Message-Id for mail with Message-Id "%s"', message_id) return self.Retval(False, result.data) uids.append(result.data[0]) result = self.select_mailbox(source) if not result.code: return result # pragma: no cover self.conn.copy(uids, destination) if delete_old: result = self.delete_mails(uids=uids, mailbox=source) if not result.code: self.logger.error('Failed to remove old mail with Message-Id="%s"/uids="%s": %s', message_ids, uids, result.data) # pragma: no cover return result # pragma: no cover if expunge: # TODO don't expunge by default result = self.expunge(mailbox=source) if not result.code: self.logger.error('Failed to expunge on mailbox %s: %s', source, result.data) # pragma: no cover return result # pragma: no cover dest_uids = [] for message_id in message_ids: result = self.search_mails(mailbox=destination, criteria='HEADER Message-Id "{0}"'.format(message_id)) if not result.code: self.logger.error('Failed to determine uid by Message-Id for mail with Message-Id "%s"', message_id) # pragma: no cover return result # pragma: no cover dest_uids.append(result.data[0]) if isinstance(set_flags, list): self.set_mailflags(uids=dest_uids, mailbox=destination, flags=set_flags) if add_flags: self.add_mailflags(uids=dest_uids, mailbox=destination, flags=add_flags) return self.Retval(True, dest_uids) except IMAPClient.Error as e: return self.process_error(e) def _append(self, folder, msg, flags=(), msg_time=None): # TODO """ FORKED FORM IMAPCLIENT """ if msg_time: if not msg_time.tzinfo: # pragma: no cover msg_time = msg_time.replace(tzinfo=FixedOffset.for_system()) # pragma: no cover time_val = '"{0}"'.format(msg_time.strftime("%d-%b-%Y %H:%M:%S %z")) time_val = imapclient.imapclient.to_unicode(time_val) else: time_val = None return self.conn._command_and_check('append', self.conn._normalise_folder(folder), imapclient.imapclient.seq_to_parenstr(flags), time_val, Helper.str_to_bytes(msg), unpack=True) @do_select_mailbox def expunge(self, mailbox): """ Expunge mails form a mailbox """ self.logger.debug('Expunge mails from mailbox %s', mailbox) try: return self.Retval(True, b'Expunge completed.' in self.conn.expunge()) except IMAPClient.Error as e: # pragma: no cover return self.process_error(e) # pragma: no cover def create_mailbox(self, mailbox): """ Create a mailbox """ self.logger.debug('Creating mailbox %s', mailbox) try: return self.Retval(True, self.conn.create_folder(mailbox) == b'Create completed.') except IMAPClient.Error as e: return self.process_error(e) def mailbox_exists(self, mailbox): """ Check whether a mailbox exists """ try: return self.Retval(True, self.conn.folder_exists(mailbox)) except IMAPClient.Error as e: # pragma: no cover return self.process_error(e) # pragma: no cover @do_select_mailbox def delete_mails(self, uids, mailbox): """ Delete mails """ self.logger.debug('Deleting mails with uid="%s"', uids) try: result = self.conn.delete_messages(uids) flags = {} for uid in uids: flags[uid] = [] if uid not in result.keys(): self.logger.error('Failed to get flags for mail with uid=%s after deleting it: %s', uid, result) return self.Retval(False, None) for flag in result[uid]: flags[uid].append(flag.decode('utf-8')) return self.Retval(True, flags) except IMAPClient.Error as e: return self.process_error(e)
class IMAP(): """ Central class for IMAP server communication """ Retval = namedtuple('Retval', 'code data') def __init__(self, logger, username, password, server='localhost', port=143, starttls=False, imaps=False, tlsverify=True, test=False, timeout=None): self.logger = logger self.username = username self.password = password self.server = server self.port = port self.imaps = imaps self.starttls = starttls self.timeout = timeout self.sslcontext = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) if tlsverify: self.sslcontext.verify_mode = ssl.CERT_REQUIRED else: self.sslcontext.check_hostname = False self.sslcontext.verify_mode = ssl.CERT_NONE self.test = test self.conn = None def do_select_mailbox(func): """ Decorator to do a fresh mailbox SELECT """ def wrapper(*args, **kwargs): if len(args) != 1: raise AttributeError( 'Size of *args tuple "{0}" isn\'t 1. It looks like you haven\'t specified all ' 'method arguments as named arguments!'.format(args)) mailbox = None for key in ['mailbox', 'source']: if key in kwargs.keys(): mailbox = kwargs[key] break if mailbox is None: raise KeyError( 'Unable to SELECT a mailbox, kwargs "{0}" doesn\'t contain a mailbox name' .format(kwargs)) result = args[0].select_mailbox(mailbox) if not result.code: raise RuntimeError(result.data) return func(*args, **kwargs) return wrapper def process_error(self, exception, simple_return=False): """ Process Python exception by logging a message and optionally showing traceback """ trace_info = exc_info() err_msg = str(exception) if isinstance(exception, IMAPClient.Error): err_msg = Helper().byte_to_str(exception.args[0]) self.logger.error("Catching IMAP exception {}: {}".format( type(exception), err_msg)) if self.logger.isEnabledFor(loglevel_DEBUG): print_exception(*trace_info) del trace_info if simple_return: return exception else: return self.Retval(False, err_msg) def connect(self, retry=True, logout=False): """ Connect to IMAP server and login """ if self.starttls: self.logger.debug( 'Establishing IMAP connection using STARTTLS/{} to {} and logging in with user {}' .format(self.port, self.server, self.username)) elif self.imaps: self.logger.debug( 'Establishing IMAP connection using SSL/{} (imaps) to {} and logging in with user {}' .format(self.port, self.server, self.username)) try: self.conn = IMAPClient(host=self.server, port=self.port, use_uid=True, ssl=self.imaps, ssl_context=self.sslcontext, timeout=self.timeout) if self.starttls: self.conn.starttls(ssl_context=self.sslcontext) login = self.conn.login(self.username, self.password) login_response = Helper().byte_to_str(login) # Test login/auth status login_success = False noop = self.noop() if noop.code and noop.data: login_success = True if logout: return self.disconnect() elif login_success: return self.Retval(True, login_response) else: return self.Retval(False, login_response) # pragma: no cover except exceptions.LoginError as e: return self.process_error(e) except Exception as e: err_return = self.process_error(e) if retry: self.logger.error('Trying one more time to login') sleep(2) return self.connect(retry=False, logout=logout) return err_return def noop(self): """ Do a noop to test login status """ try: noop = self.conn.noop() noop_response = Helper().byte_to_str(noop[0]) noop_resp_pattern_re = regex_compile('^(Success|NOOP completed)') login_success = noop_resp_pattern_re.match(noop_response) return self.Retval(True, login_success) except IMAPClient.Error as e: return self.process_error(e) def disconnect(self): """ Disconnect from IMAP server """ result = self.conn.logout() response = Helper().byte_to_str(result) return self.Retval(response == 'Logging out', response) def list_mailboxes(self, directory='', pattern='*'): """ Get a listing of folders (mailboxes) on the server """ try: raw_list = self.conn.list_folders(directory, pattern) nice_list = [] for mailbox in raw_list: flags = [] for flag in mailbox[0]: flags.append(flag.decode('utf-8')) nice_list.append({ 'name': mailbox[2], 'flags': flags, 'delimiter': mailbox[1].decode("utf-8") }) return self.Retval(True, nice_list) except IMAPClient.Error as e: return self.process_error(e) def select_mailbox(self, mailbox): """ Select a mailbox to work on """ self.logger.debug('Switching to mailbox {}'.format(mailbox)) try: result = self.conn.select_folder(mailbox) response = {} for key, value in result.items(): unicode_key = Helper().byte_to_str(key) if unicode_key == 'FLAGS': flags = [] for flag in value: flags.append(Helper().byte_to_str(flag)) response[unicode_key] = tuple(flags) else: response[unicode_key] = value return self.Retval(True, response) except IMAPClient.Error as e: return self.process_error(e) def add_mail(self, mailbox, message, flags=(), msg_time=None): """ Add/append a mail to a mailbox """ self.logger.debug('Adding a mail into mailbox {}'.format(mailbox)) try: if not isinstance(message, Mail): message = Mail(logger=self.logger, mail_native=message) self.conn.append(mailbox, str(message.get_native()), flags, msg_time) # According to rfc4315 we must not return the UID from the response, so we are fetching it ourselves uids = self.search_mails(mailbox=mailbox, criteria='HEADER Message-Id "{}"'.format( message.get_message_id())).data[0] return self.Retval(True, uids) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def search_mails(self, mailbox, criteria='ALL', autocreate_mailbox=False): """ Search for mails in a mailbox """ self.logger.debug( 'Searching for mails in mailbox {} and criteria=\'{}\''.format( mailbox, criteria)) try: return self.Retval(True, list(self.conn.search(criteria=criteria))) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def fetch_mails(self, uids, mailbox, return_fields=None): """ Retrieve mails from a mailbox """ self.logger.debug('Fetching mails with uids {}'.format(uids)) return_raw = True if return_fields is None: return_raw = False return_fields = [b'RFC822'] mails = {} try: for uid in uids: result = self.conn.fetch(uid, return_fields) if not result: continue if return_raw: mails[uid] = result[uid] else: # mails[uid] = Mail(logger=self.logger, uid=uid, mail_native=email.message_from_bytes(result[uid][b'RFC822'])) mails[uid] = Mail(logger=self.logger, mail_native=email.message_from_bytes( result[uid][b'RFC822'])) return self.Retval(True, mails) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def get_mailflags(self, uids, mailbox): """ Retrieve flags from mails """ try: result = self.conn.get_flags(uids) flags = {} for uid in uids: flags[uid] = [] if uid not in result.keys(): self.logger.error( 'Failed to get flags for mail with uid={}: {}'.format( uid, result)) return self.Retval(False, None) for flag in result[uid]: flags[uid].append(flag.decode('utf-8')) return self.Retval(True, flags) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def set_mailflags(self, uids, mailbox, flags=[]): """ Set and retrieve flags from mails """ if self.test: self.logger.info( 'Would have set mail flags on message uids "{}"'.format( str(uids))) return self.Retval(True, None) else: self.logger.debug('Setting flags={} on mails uid={}', flags, uids) try: result = self.conn.set_flags(uids, flags) _flags = {} for uid in uids: _flags[uid] = [] if uid not in result.keys(): self.logger.error( 'Failed to set and get flags for mail with uid={}: {}' .format(uid, result)) return self.Retval(False, None) for flag in result[uid]: _flags[uid].append(flag.decode('utf-8')) return self.Retval(True, _flags) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def add_mailflags(self, uids, mailbox, flags=[]): """ Add and retrieve flags from mails """ if self.test: self.logger.info( 'Would have added mail flags on message uids "{}"'.format( str(uids))) return self.Retval(True, None) else: self.logger.debug('Adding flags={} on mails uid={}', flags, uids) try: result = self.conn.add_flags(uids, flags) _flags = {} for uid in uids: _flags[uid] = [] if uid not in result.keys(): self.logger.error( 'Failed to add and get flags for mail with uid={}: {}' .format(uid, result)) return self.Retval(False, None) for flag in result[uid]: _flags[uid].append(flag.decode('utf-8')) return self.Retval(True, _flags) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def move_mail(self, message_ids, source, destination, delete_old=True, expunge=True, add_flags=None, set_flags=None): """ Move a mail from a mailbox to another """ return self.copy_mails(message_ids=message_ids, source=source, destination=destination, delete_old=delete_old, expunge=expunge, add_flags=add_flags, set_flags=set_flags) @do_select_mailbox def copy_mails(self, source, destination, message_ids=None, delete_old=False, expunge=False, add_flags=None, set_flags=None): """ Copies one or more mails from a mailbox into another """ if self.test: if delete_old: self.logger.info( 'Would have moved mail Message-Ids="{}" from "{}" to "{}", skipping because of beeing in testmode' .format(message_ids, source, destination)) else: self.logger.info( 'Would have copied mails with Message-Ids="{}" from "{}" to "{}", skipping because of beeing in testmode' .format(message_ids, source, destination)) return self.Retval(True, None) else: try: if delete_old: self.logger.debug( 'Moving mail Message-Ids="{}" from "{}" to "{}"'. format(message_ids, source, destination)) else: self.logger.debug( 'Copying mail Message-Ids="{}" from "{}" to "{}"'. format(message_ids, source, destination)) # if message_ids is None: # message_ids = [] # result = self.fetch_mails(uids=uids, mailbox=source) # if not result.code: # self.logger.error('Failed to determine Message-Id by uids for mail with uids "{}"', uids) # return result # message_ids.append(result.data.keys()) if not self.mailbox_exists(destination).data: self.logger.info( 'Destination mailbox {} doesn\'t exist, creating it for you' .format(destination)) result = self.create_mailbox(mailbox=destination) if not result.code: self.logger.error( 'Failed to create the mailbox {}: {}'.format( source, result.data)) # pragma: no cover return result # pragma: no cover uids = [] for message_id in message_ids: result = self.search_mails( mailbox=source, criteria='HEADER Message-Id "{}"'.format(message_id)) if not result.code or len(result.data) == 0: self.logger.error( 'Failed to determine uid by Message-Id for mail with Message-Id "{}"' .format(message_id)) return self.Retval(False, result.data) uids.append(result.data[0]) result = self.select_mailbox(source) if not result.code: return result # pragma: no cover self.conn.copy(uids, destination) if delete_old: result = self.delete_mails(uids=uids, mailbox=source) if not result.code: self.logger.error( 'Failed to remove old mail with Message-Id="{}"/uids="{}": {}' .format(message_ids, uids, result.data)) # pragma: no cover return result # pragma: no cover if expunge: # TODO don't expunge by default result = self.expunge(mailbox=source) if not result.code: self.logger.error( 'Failed to expunge on mailbox {}: {}'.format( source, result.data)) # pragma: no cover return result # pragma: no cover dest_uids = [] for message_id in message_ids: result = self.search_mails( mailbox=destination, criteria='HEADER Message-Id "{}"'.format(message_id)) if not result.code: self.logger.error( 'Failed to determine uid by Message-Id for mail with Message-Id "{}"' .format(message_id)) # pragma: no cover return result # pragma: no cover dest_uids.append(result.data[0]) if isinstance(set_flags, list): self.set_mailflags(uids=dest_uids, mailbox=destination, flags=set_flags) if add_flags: self.add_mailflags(uids=dest_uids, mailbox=destination, flags=add_flags) return self.Retval(True, dest_uids) except IMAPClient.Error as e: return self.process_error(e) @do_select_mailbox def expunge(self, mailbox): """ Expunge mails form a mailbox """ self.logger.debug('Expunge mails from mailbox {}'.format(mailbox)) try: return self.Retval(True, b'Expunge completed.' in self.conn.expunge()) except IMAPClient.Error as e: # pragma: no cover return self.process_error(e) # pragma: no cover def create_mailbox(self, mailbox): """ Create a mailbox """ self.logger.debug('Creating mailbox {}'.format(mailbox)) try: return self.Retval( True, self.conn.create_folder(mailbox) == b'Create completed.') except IMAPClient.Error as e: return self.process_error(e) def mailbox_exists(self, mailbox): """ Check whether a mailbox exists """ try: return self.Retval(True, self.conn.folder_exists(mailbox)) except IMAPClient.Error as e: # pragma: no cover return self.process_error(e) # pragma: no cover @do_select_mailbox def delete_mails(self, uids, mailbox): """ Delete mails """ self.logger.debug('Deleting mails with uid="{}"'.format(uids)) try: result = self.conn.delete_messages(uids) flags = {} for uid in uids: flags[uid] = [] if uid not in result.keys(): self.logger.error( 'Failed to get flags for mail with uid={} after deleting it: {}' .format(uid, result)) return self.Retval(False, None) for flag in result[uid]: flags[uid].append(flag.decode('utf-8')) return self.Retval(True, flags) except IMAPClient.Error as e: return self.process_error(e)