Esempio n. 1
0
def _get_render_data():
    return {
        'version':
        get_version(),
        'license_email':
        check_get_license_email(),
        'debug':
        DEBUG,
        'debug_sentry':
        DEBUG_SENTRY,
        'debug_posthog':
        DEBUG_POSTHOG,
        'frameless':
        FRAMELESS,
        'frozen':
        FROZEN,
        'is_app':
        IS_APP,
        'platform':
        PLATFORM,
        'session_token':
        SESSION_TOKEN,
        'website_url':
        WEBSITE_URL,
        'device_id':
        get_device_id(),
        'sentry_dsn':
        '' if DEACTIVATE_SENTRY else get_hidden_value('SENTRY_DSN'),
        'posthog_api_key':
        '' if DEACTIVATE_POSTHOG else get_hidden_value('POSTHOG_API_KEY'),
        'disable_error_logging':
        get_system_setting('disable_error_logging'),
        'disable_analytics':
        get_system_setting('disable_analytics'),
    }
Esempio n. 2
0
    def get_emails(self, reset=False, batch_size=None):
        '''
        Get slices of emails from our email list, fetching more if needed.
        '''

        # If we don't exist, we have nothing
        if not self.exists:
            return []

        if reset:
            self.log('debug', 'Resetting folder')
            self.seen_email_uids = set()

        if not batch_size:
            batch_size = get_system_setting('batch_size')

        unseen_email_uids = self.email_uids - self.seen_email_uids
        sorted_unseen_email_uids = sorted(unseen_email_uids, reverse=True)

        # Select the slice of UIDs
        email_uids = sorted_unseen_email_uids[:batch_size]

        # Nothing to fetch? Shortcut!
        if not email_uids:
            return []

        # Actually fetch the emails
        emails = self.get_email_headers(email_uids)
        self.seen_email_uids.update(email_uids)

        return emails
Esempio n. 3
0
    def get_email_uids(self, use_cache=True):
        # If we're not a query folder we can try for cached UIDs
        if use_cache and not self.query:
            cached_uids = self.cache.get_uids()
            if cached_uids:
                self.log(
                    'debug',
                    f'Loaded {len(cached_uids)} cached message IDs',
                )
                return cached_uids

        # Searching
        if isinstance(self.query, (bytes, str)):
            # Use Gmails X-GM-RAW search extension if available - supports full
            # Gmail style search queries.
            if b'X-GM-EXT-1' in self.account.get_capabilities():
                search_query = ['X-GM-RAW', self.query]
            else:
                # IMAP uses polish notation (operator on the left)
                search_query = [
                    'OR', 'SUBJECT', self.query, 'BODY', self.query
                ]

        # Syncing
        else:
            sync_days = get_system_setting('sync_days')
            if sync_days and sync_days > 0:
                days_ago = date.today() - timedelta(days=sync_days)
                search_query = ['SINCE', days_ago]
            else:
                search_query = ['ALL']

        self.log('debug', 'Fetching message IDs')

        with self.get_connection() as connection:
            message_uids = connection.search(search_query)

        self.log('debug', f'Fetched {len(message_uids)} message UIDs')

        uids = set(message_uids)
        return uids
Esempio n. 4
0
    def get_email_uids(self, use_cache=True):
        if use_cache and not self.query:
            cached_uids = self.get_cached_uids()
            if cached_uids:
                return cached_uids

        # Searching
        if isinstance(self.query, (bytes, str)):
            # Use Gmails X-GM-RAW search extension if available - supports full
            # Gmail style search queries.
            if b'X-GM-EXT-1' in self.account.get_capabilities():
                search_query = ['X-GM-RAW', self.query]
            else:
                # IMAP uses polish notation (operator on the left)
                search_query = ['OR', 'SUBJECT', self.query, 'BODY', self.query]

        # Syncing
        else:
            sync_days = get_system_setting('sync_days')
            if sync_days and sync_days > 0:
                days_ago = date.today() - timedelta(days=sync_days)
                search_query = ['SINCE', days_ago]
            else:
                search_query = ['ALL']

        self.log('debug', 'Fetching message IDs')

        with self.get_connection() as connection:
            try:
                # Certain IMAP servers (Outlook) don't support UTF-8, even in 2022.
                message_uids = connection.search(search_query)
            except UnicodeEncodeError:
                message_uids = connection.search(search_query, charset='utf-8')

        self.log('debug', f'Fetched {len(message_uids)} message UIDs')

        uids = set(message_uids)
        return uids
Esempio n. 5
0
    def sync_emails(self, expected_uid_count=None, check_unread_uids=None):
        '''
        Get new and deleted emails for this folder.
        '''

        # If we don't exist, try again or we have nothing
        if not self.exists:
            if not self.check_exists():
                return [], [], []

        message_uids = self.get_email_uids(use_cache=False)

        # Check the folder UIDVALIDITY (busts the cache if needed)
        uids_valid = self.check_cache_validity()
        uids_changed = False

        if uids_valid:
            # Remove existing from new to get anything new
            new_message_uids = message_uids - self.email_uids
            # Remove new from existing to get deleted
            deleted_message_uids = self.email_uids - message_uids

            uids_changed = (
                len(new_message_uids) > 0
                or len(deleted_message_uids) > 0
            )
        else:
            uids_changed = True

            # All old uids invalid, so set all old to deleted
            deleted_message_uids = self.email_uids

            batch_size = get_system_setting('batch_size')
            sorted_message_uids = sorted(message_uids, reverse=True)
            new_message_uids = sorted_message_uids[:batch_size]

        self.email_uids = message_uids

        if uids_changed:
            self.cache_uids()

        for uid in deleted_message_uids:
            self.cache.delete_headers(uid)

        if expected_uid_count:
            new_message_uids = fix_missing_uids(
                expected_uid_count, new_message_uids,
            )

        self.log('debug', (
            f'Fetched {len(new_message_uids)} new'
            f'/{len(deleted_message_uids)} deleted message IDs'
        ))

        new_emails = {}

        if new_message_uids:
            # Now actually fetch & return those emails
            new_emails = self.get_email_headers(new_message_uids)
            self.seen_email_uids.update(new_message_uids)

        read_uids = []
        if check_unread_uids:
            check_unread_uids = [  # remove any deleted UIDs
                uid for uid in check_unread_uids
                if uid in message_uids
            ]
            read_uids = self.check_update_unread_emails(check_unread_uids)

        # Return the enw emails & any deleted uids
        return new_emails, list(deleted_message_uids), read_uids
Esempio n. 6
0

class JsonEncoder(JSONEncoder):
    def default(self, obj) -> Union[str, int]:
        if isinstance(obj, bytes):
            try:
                return obj.decode()
            except UnicodeDecodeError:
                return obj.decode('utf-8', 'ignore')

        return super(JsonEncoder, self).default(obj)


if DEBUG and not DEBUG_SENTRY:
    logger.debug('Not enabling Sentry error logging in debug mode...')
elif get_system_setting('disable_error_logging') or DEACTIVATE_SENTRY:
    logger.debug('Disabling Sentry per user settings')
else:
    sentry_sdk.init(
        dsn=get_hidden_value('SENTRY_DSN'),
        release=f'kanmail-app@{get_version()}',
        integrations=[FlaskIntegration()],
        # Don't send stack variables, potentially containing email data
        with_locals=False,
    )
    # Random identifier for this Kanmail install (no PII)
    sentry_sdk.set_user({'id': get_device_id()})

app = Flask(
    APP_NAME,
    static_folder=path.join(CLIENT_ROOT, 'static'),